The State of Logging in ActionScript 3

I’ve been looking at Logging Frameworks in ActionScript 3 over the past few days and I’m afraid that I’m slightly at a loss as for which approach is the best to take. There are a few Logging Frameworks out there, but Adobe has kindly supplied an intrinic Logging Framework, however, it’s not without it’s shortcomings.

The biggest problem that I’ve come up against is that there is no easy way to set the Category for the Log.  The Flex Logging Framework allows you to set the category of the log when you retrieve the ILogger instance.  This can in turn be used by the Log Filtering system to specify which categories you would like to log.  Ideally you set the category String to be the name of the Class you are currently Logging, however, due to the rather shaky reflection tools available, there’s no easy way to automatically get the name of the current Class.  To compound the problem even further, getQualifiedClassName returns illegal characters (The semi-colon separating the Package and the Class Name) and Flex Logging Framework will throw and Illegal Arguments error if you try and use it as the category name.  So, in order to have automatic categories generated, we have to do a bit of a curly shuffle with a StackTrace and a Helper Method

package uk.co.jonnyreeves.logging
{
	import mx.logging.ILogger;
	import mx.logging.ILoggingTarget;
	import mx.logging.Log;	
	import flash.utils.getQualifiedClassName;	
 
	/**
	 * Flex Logger Helper, automatically returns the Category Name when used inside
	 * a Class.
	 *
	 * @author jonny reeves
	 */
	public class Logger 
	{
		/**
		 * Returns an instance of Flex Logger with the correct category name for the calling Class
		 * 
		 * @return ILogger instance with the correct category set for the calling Class.
		 */
		public static function getLogger() : ILogger
		{
			var category : String = "";
			category = getCallerFromStackTrace();
			return Log.getLogger(category);
		}
 
 
		/**
		 * Triggers a StackTrace and extracts the calling method from it, neat.
		 */
		private static function getCallerFromStackTrace() : String
		{
			var callerMethod : String = "";
			var nullArray : Array;
 
			// Pop a StackTrace by creating a RunTime error.
			try
			{
				nullArray.push("whoops");
			}
			catch (e : Error)
			{
				var stackTrace : String = new Error().getStackTrace();
			}
 
			// Extract the ClassName from the StackTrack
			if (stackTrace != null)
			{
				var parts : Array = new Array();
				stackTrace.split("n").join("");
				parts = stackTrace.split("	at ");
				if (parts[3] != null)
				{
					// Handle Both Class Init and internal method cases
					if (parts[3].indexOf("$cinit") != -1)
					{
						callerMethod = parts[3].split("$cinit")[0];
					}
					else
					{
						callerMethod = parts[3].split("/")[0];
					}
				}
			}
 
			return getCategoryNameFor(callerMethod);
		}
 
 
		/**
		 * Helper method which takes an ActionScript 3 Formatted Class Name
		 * and replaces the semi-colons seperating the package and Class name
		 * with a Single Period for use as teh Flex Logging Category name
		 * 
		 * @param qualifiedClassName
		 * @return String Legal Flex Logging Category String
		 */
		private static function getCategoryNameFor(qualifiedClassName : String) : String
		{
			return qualifiedClassName.indexOf("::") > -1 ? qualifiedClassName.split("::").join(".") : qualifiedClassName;
		}
	}
}

This can now be used inside any Class you wish to Log by delcaring a private, static instance of ILogger, for example:

package uk.co.jonnyreeves.logging
{
	public class LoggingExample
	{
		/**
		 * Instance of the Flex Logging Framework via the Logger Helper Class which will
		 * automatically set the Category name to uk.co.jonnyreeves.logging.LoggingExample
		 */
		private static var Logger : ILogger = Logger.getLogger();
 
 
		/**
		 * You can now log away, don't forget to initialise your Flex Logger Instance (Log) first
		 * by setting the Target Publisher (not shown).
		 */
		public function LoggingExample()
		{
			Logger.info("Hello World!");
		}
	}
}

Although this is helpful it still does not address a couple of other fundamental issues:

  • The output from the Flex Logging Framework does not include the enclosing method or line number from which the error was triggered (although this information can extracted from a StackTrace and automatically appended to the Logged Message by the Publisher).
  • The Flex Logging Framework does not intelligently handle datatypes – unfortunately we can only passing String’s to Flex Logger – however, we can get around this by making use of 3rd Party Consoles such as Thunderbolt (which also, helpfully includes a LoggingTarget).
  • Although the Flex Logging Framework allows you to supply a custom Filters Array (and this can even be updated at runtime), it does not appear to include a simple way to configure this via an external XML configuration file. However, I’m fairly confident that I can roll my own (just a shame that I have to!)

I’m going to be working on the three points above over the next few days and see if I can come up with a solution which stays as faithful to the intrinsic Logging framework as possible but allows the flexibility of some of the other Loggers out there (Luminic Box, XRay, etc).

Posted in ActionScript 3 | Tagged , , | 2 Comments

Flash on the Beach 2008 – Tuesday Write Up

I went down to the Flash on the Beach 08 conference yesterday, here’s some of the notes that I wrote up on the train back:

Creating The Next Generation Happy Meal Toy

Julain Dolce

Julian is lead Developer for Fuel Industies, a creative agency that worked on a large scale campaign for the fast food chain McDonalds. The project was a rich multimedia experience centred on magical fairies and dragons. The application was delivered on CD which was bundled with Happy Meals. The campaign proved very successful pushing over 80 million units in 30 countries. He began his presentation with a brief overview of the product (marred somewhat by the fact the projector was not playing ball and he had to show it to the audience from the screen of his 15” Macbook).

Julian’s project had several key requirements which were taken into consideration during the initial planning stage:

  • App. must be Cross Platform (Win32 and Mac OS)
  • Installed from a CD, with no internet access required
  • Handle multiple windows (for performance) and alpha transparency

This ruled out Adobe Air 1.0 as it can’t be installed solely from a CD, it also has issues with full frame transparency. As a result they settled on a mixture of MDM Zinc 3 and mProjector. They also used Install Builder, which is a Cross Platform (Win32, Linux and Mac) installation creator. Their main Flash tools were FDT and Eclipse, they made use of SVN and constructed a couple of custom tools in C# using WPF on Win32

Julian’s team ended up building most of their application in ActionScript 2, Julain cited this was because of a lack of a mature ActionScript 3 development environment when the project was started in the summer of 2007. They used SVN to keep code versioned, Mantis to track bugs and took advantage of an automated build process to get the builds out to testers. They also kept Excel spreadsheets for each library asset which detailed file locations, settings (offsets, alpha, and compression ratios for all Cell animations, sounds and linkageid’s, etc.

The team made use of JSFL, which can be used to create custom scripts inside Adobe CS Applications – one such example was the ability to apply an affect to the selected symbols in the Document’s Library.

Their major testing problem was performance bound – using fullscreen alpha transparency bought things to a crawl and meant click events went unnoticed on slower machines – as a result they split the animations down into separate transparent windows which moved across the stage (the user’s desktop). They used LocalConnection calls to get these panels to talk to each other and sync up.

Although the project appeared to go smoothly and was obviously successful, Julian did admit to one major failing. In the time between sending the CD’s off for pressing in China and the returned product – Mac OS X 10.5 shipped – and unfortunately, their application did not work on it – whoops.

Adobe Town Hall Meeting

Richard Galvan, Mike Downey, Paul Betlem, Lee Brimelow and Matt Millar.

The second talk of the day consisted of 5 members of the Adobe Team – 3 platform evangelists and two Flash Player engineers. The group asked questions asked by the audience. I managed to ask a question in the session:

“The company I work for are currently porting a large scale AS2 project to AS3 and were disheartened by the lack of support and tools made available by Adobe to assist in this. By using the Proxy class we have managed to create shims for most the intrinsic AS2 classes, but I Would like to know why Adobe did not do this and release it to the community.”

Their reply wasn’t what I was hoping for (but was to be expected). I was told that Actionscript 3 was designed to target new customers and that they wanted to add as many features as possible to the language. I’m not quite sure what to infer from this seeing as AS2 obviously had a massive user base. The group did, however, acknowledge that the lack of community support in the transition from AS2 to AS3 was a problem (although, again, made no indication that any tools would be forthcoming) . They rounded off my question by assuring that ASVM1 Content (AS1 and 2 SWFs) would continue to be usable in Flash Player releases in the future.

Another interesting question that was asked was about the support for Dynamic Runtime Languages to script the published flash applications and allow greater control at runtime. The team responded by saying that a new project – FLACC (FLAsh C++) is in the works which will allows C++ code to run natively inside the Flash Player. This could lead to other language interpreters being made available and will prove a very powerful tool for ActionScript developers.

Adobe also confirmed that Flash 10 is being worked on for the iPhone – but Apple has not confirmed it will be approved. Flash Player 10 will include the new unloadAndStop() method to help with Garbage Collection when unloading external content in the Player and that there is no AS4 on the cards – instead they will work on improving AS3 whilst pushing ECMAScript 4 forwards (they hinted that Private Constructors might be coming back)

After the Session I spoke to an Adobe Flash Player engineer, Matt Millar. I told him about our Library Linkage Problem where Child Objects placed on the stage in frame 2 (or greater) are not linked to the Display Chain on the first tick. Whilst I was asking this question, another developer stepped in and claimed it was a major problem for his team. I gave Matt my business card and the told me he would be in touch – I’m hoping that this will be fixed before FP 10 is released, but I am not holding my breath!

Grant Skinner

Things every ActionScript developer should know.

Grant is a well revered programmer in the ActionScript community, his Blog provides an excellent resource and he heads up his own development team. Grant’s talk was aimed at developers of all skill levels, but focused mainly on the grass roots. Grant gave an interesting presentation which, in his words, he wished someone had given to him 5 years ago – I’m not sure I can agree it was as life changing as Grant made out – but that may be in part due to the best practices which Mind Candy’s dev team strives towards ;)

Grant started off by assuring the audience that were no right answers in programming – don’t be driven to fake absolutes, although there are no definitive right answers there are plenty of wrong ones! Code is art, Grant stressed, learn the rules before you can start to break them and don’t get hooked on Design Patterns – they’re not the answer to everything.

Grant then proceeded to lay down some programming fundamentals – Enforce code standards as it makes inheriting files easier. Think of code like Lego, not Playdoh. If you are building a space ship in Playdoh and want to change the engine you have to pull it all apart – with Lego you can take apart and reassemble with ease – your code should be like this too. Code flows down in specialisation – at the very top of the Object stack is the Main Application Controller which includes very specific logic for this one particular app – however as you move outwards, objects should become more generic and reusable. When working with reusable code, remember inside looking out – prefer composition and event dispatching over tight coupling and inheritance.

Grant also recommended the use of Service delegates to act as a layer between Server Side logic and Controller Logic in Flash, that way, the server side logic can change with only the one class needing modification on the Flash side.

Moving towards Flash Player specifics, Grant told the audience to always keep learning – read every API you can – you don’t have to specialise but at least know what FP10 is capable of. Also remember that AS3 needs you to be very particular about Garbage Collection – always create destructors in your AS3 objects to handle this in one place.

Grant bought up the concept of using Timeline based Event dispatching. His example was a simple graphical dialogue box (like we use in Moshi), where the box just dispatches events when it finished tweening in / out – that way Classes using this Timeline code / Library Linage just need to register for its events removing any form of tight coupling. Grant proceeded to talk about he makes use of JSFL to create custom Panels and “Graphical Objects” – a single click from the command line laid out all the frames, labels and AS3 timeline code for creating a Graphical Button (including the Linkage name!)

Finally, Grant reminded the audience that we are blessed to have the Flash IDE – we can create prototype and concepts in seconds – get out there an experiment!

You can view Grant’s slides for his presentation over on his site – note that you will need Flash Player 10 beta installed as he makes use of the new 3D features present

Platform Jujitsu

Lee Brimelow

Lee (who runs theflashblog.com) came back in the afternoon to evangelise about the new CS4 platform, showing us some of the new features available across the entire product line (Flash CS4, Flex Builder 4 and Flash Player 10). He started off his speech by outlining that Flash Developers much be proficient in 5 main areas:

  • Motion and Video
  • Visual Design (using Photoshop and Illustrator)
  • Object Orientated ActionScript
  • Flash and Flash IDE’s
  • Server Side Integration

With the introduction out of the way, Lee went on to talk about some of the new features in Flash Player 10. First up was native 3D – this provides full antialiased 3D inside with AS3, and, as it’s intrinsic to the player, it’s very fast! Lee stressed that this was meant to compliment, not replace more mature and capable 3D engines like PaperVision (but mentioned that there were plenty of new API’s that those guys would be hooking in to to speed things up). To accompany this, the drawing API has received an overhaul to allow drawing and manipulating programmatically generated 3D Objects

Next up was Adobe’s Advanced Text Engine – this is in the form of an AS3 API which Lee described as bewildering to anyone except “Text Nerds” – he said it provided an incredible amount of control – so much so that they were planning to release a CS4 component shortly after release which would allow “normal people” to harness it’s power – he did however say that this component would not ship with CS4.

Flash Player 10 will incorporate a host of “tweaks” including true Hardware Acceleration which will improve the speed of Video and Bitmap Manipulation (Filters, Pixel Bender operations). Local file access will be included (but will require user prompts), the previously mentioned unloadAndStop() method will be added and the new Vector datatype for strongly typed Arrays.

One of the new features that Lee was getting excited about was Pixel Bender – this allows image processing with shaders and filters and looked very interesting. Lee showed a custom demo where he created his own filter in HLSL (High Level Shader Language – which looked a bit like C#). One of the most exciting things about Pixel Bender is the fact that it is threaded separately from Flash Player – Lee said that complex calculations could be passed off to the Pixel Bener to allow for Mutli Threading in Flash Applications – if this is true, and it’s as easy as Lee hinted then this will be a major break through for Flash Player and will be immensely useful for complex Flash Apps.

Lee proceeded to talk about Adobe Air 1.5 and Flex Builder 4, neither of which sounded very exciting – alough Flex 4 will feature (yet another) XML based language for handling graphics in Flex.

Finally, Lee showed us some of the features of the new Flash CS4 IDE including live previews of 3d Images. The Flash Properties Panel now includes a new Z property alongside the traditional x and y which allows you to move symbols in space (away from the camera) – he whipped up a simple, live demonstration of this which was very impressive, however, he insisted the really cold 3D features would only be available via AS3 scripting. He wrapped up his Flash CS4 presentation by mentioning that the Flash IDE ActionScript Panel had not been touched in this release – in his own words – fail.

The Best 8 to 12 Hours of My Life

Robert Hodgin

The day ended with a visual presentation by Robert Hodgin who specialises in Audio Visualisation code – whilst not ActionScript based, his visuals provided some inspiring ideas and concepts along with a recommendation that the audience branch out and try a hit of ‘cid if they haven’t had the privilege.

Magneto Visulisation of Trentemoller’s Miss You

Posted in ActionScript 2 | Tagged , | 1 Comment

Ubuntu Netboot install with Windows (PXE)

I’m the proud owner of a Dell X200 Laptop, which, due to it’s compact form factor (incredible seeing as it dates back to 2003!) lacks a CD-Rom and Floppy Disc Drive.  This makes installing operating systems a bit of a chore and seeing how my USB Pen Drive packed in at the weekend, the only install method I had available to me was a NetBoot install via PXE (Preboot Executing Environment).

Usually this is a pretty straight forward affair, but I had a bit of a problem – may laptop would only find the TFTP server if it was directly wired to it via a crossover cable (no doubt my POS Speedlink router was partly to blame).  After a couple of hours of faffing I finally managed to get a successful install with the following steps:

  1. Download a copy of TFTP32 for Windows
  2. Directly wire your laptop to your desktop pc (which is running the TFTP server) with a crossover cable
  3. Follow the Ubuntu Windows Server Netboot Wiki Entry to get the TFTP Server configured – make sure you set everything up to point towards your Router.  (ie: My router defaults to 192.168.1.254, so I set that as the gateway and gave the DHCP Server in TFTP32 a sensible range of IP Addresses to serve out.
  4. When the laptop boots via PXE, follow the guide right up to the Network interface selection (should be just after the Keyboard layout).
  5. At this point, unplug the cross over cable and wire the laptop directly into the router, wait a couple of seconds for it to autosense the connection and then proceed with the Ubuntu install – it should discover your router and receive a new IP Address via DHCP
  6. Sit back and smile as your laptop successfully downloads the required packages from the Ubuntu archives.

Hope this helps someone out there

Posted in Linux | Tagged , , | 3 Comments

Typesafe Enums in ActionScript 2

Typesafe Enums are perfect when you want to get the message across loud and clear, with no room for error. The classic use for an Enum is handling events, to give some context I’ll demonstrate how you could handle events without using an Enum:

/**
 * This example method will send an event back to a Registered Listener, in this
 * case we're going to inform the listener that we have loaded our data and 
 * are ready for use.
 *
 * 	@param Void
 * 	@return Void
*/
private function __onLoadComplete():Void
{
	// This forms the basis of the event we are going to send.
	var eventObject:Object = new Object();
 
	// Populate our eventObject with its type & the event message to send
	eventObject.type = 'ModelEvent';
	eventObject.event = 'data_loaded'
 
	// And send the object via mx.events.EventDispatcher
	this.dispatchEvent(eventObject);
}

The listener object (in this case, a Controller), will then have a similar method for handling callbacks, again, without using an enum we would be left to switch on a string, something like this:

/**
 * Example function, showing us registering the event listener to the Model with
 * a delegated callback to the __onModelEvent() function.
 *
 *	@param Void
 *	@return Void
*/
private function registerModel()
{
	model.addEventListener("modelEvent", Delegate.create(this, __onModelEvent));
}
 
 
/**
 * Callback method which was registered at the same time as adding the eventListener
 * this method handles incoming events from registered models
 *
 *	@param Object event object dispatched from registered model.
 *	@return Void
*/
private function __onModelEvent(event:Object):Void
{
	switch (event.event)
	{
		case 'data_loaded':
			trace ('Controller::__onModelEvent -- Data Loaded!');
			break;
 
		default:
			trace ('Controller::__onModelEvent -- Unkown Model State recieved: ' + event.event);
			break;
	}
}

Obviously this approach works, but it can lead to some quite obvious, yet tricky to find errors. For example, what if I set the value of eventObject.state to ‘Data_Loaded’, or mashed the keyboard and entered ‘data_laoded’? Of course, both these problems can be spotted (the trace() on default will help you debug typos, and a `event.state.toLowerCase()` would help with the first problem, but what about when other people are working with the code – how will they be able to tell which states are available to them, this is especially true if there are multiple listeners to modelEvents, maybe all the state’s aren’t show in each switch() – now we’re starting to see the bigger issue – one of software design. Wouldn’t it be great if we could bring all the Model State messages into one place?

Say hello to the ModelEvent Enum Class:

/**
 * ModelEvent.as
 * John Reeves, http://www.jonnyreeves.co.uk/
 * May 2008
 *
 * Typesafe Event dispatched from Models.
*/
class ModelEvent
{
	private var _name:String;
	private var _level:Number;
 
	/**
	 * Private Constructor for creating ModelEvent instances.  This stops developers
 	 * creating their own ModelEvent's in the code, instead we encourage people
	 * to create Static Model Events below:
	*/
	private function ModelEvent(name:String, level:Number)
	{
		this._name = name;
		this._level = level;
	}
 
	// Static Events Models can Send.
	static public var DATA_LOADED:ModelEvent = new ModelEvent('DATA_LOADED', 1);	
	static public var DATA_ERROR:ModelEvent = new ModelEvent('DATA_ERROR', 2);
 
 
	/**
	 * Returns the ModelEvent object for the given ModelEvent String
	 *
	 *	@param mediaType the MediaType's name
	 *	@return the MediaType object
	*/	
	public static function forName(ModelEvent:String):ModelEvent
	{
		if ((ModelEvent[ModelEvent.toUpperCase()] instanceof ModelEvent)) {
			return ModelEvent[ModelEvent.toUpperCase()];
		}
	}
 
 
	/**
	 * Returns the current ModelEvent's name as a String
	*/
	public function getName():String
	{
		return this._name;
	}
 
 
	/**
	 * Returns the current ModelEvent's Level as a Number
	*/
	public function getLevel():Number
	{
		return this._level;
	}
 
 
	/**
	 * Compares an incoming ModelEvent (x) to the current Object.
	 *
	 *	@param ModelEvent x Incoming ModelEvent Object to perform comparison on.
	 *	@return Boolean true if it matches
	*/
	public function equals(x:ModelEvent):Boolean
	{
		if (x.getName() == this.getName() && x.getLevel() == this.getLevel()) {
			return true;
		}
		return false;
	}	
}

This class is used a little differently to most others. As the constructor is set to private, we don’t expect developers to be creating instances of ModelEvent’s in their own code (so in the case of the Model’s __onLoadComplete() function which was shown above, we wouldn’t other developers using the new keyword to create their own instance of ModelEvent). Instead, we expect developers to access the pre-defined ModelStates via the static public properties defined inside the class, let’s show a quick example and revisit our Model’s onLoadComplete method:

/**
 * Now we will update this method to use the ModelEvent Enum class.
 *
 * 	@param Void
 * 	@return Void
*/
private function __onLoadComplete():Void
{
	// This forms the basis of the event we are going to send.
	var eventObject:Object = new Object();
 
	// Populate our eventObject with its type & the state message to send, 
	// this time instead of setting state as a String, we set it to be on the 
	// ModelEvent's static properties.
	eventObject.type = 'ModelEvent';
	eventObject.event = ModelEvent.DATA_LOADED;
 
	// And send the object via mx.events.EventDispatcher
	this.dispatchEvent(eventObject);
}

That was straight forward – an now we don’t have to worry about typo’s or mixing the case, any mistakes at this point will be caught by the Compiler when you build the swf. Okay, that’s cool, but how do we handle recieving the events in the Controller? Even easier!

/**
 * Callback method, this time update to handle the incoming ModelEvent
 *
 *	@param Object event object dispatched from registered model.
 *	@return Void
*/
private function __onModelEvent(event:Object):Void
{
	// extract the ModelEvent Enum from the event Object
	var event:ModelEvent = event.event;
 
	// Use the ModelEvent::equals method to perform comparison
	if (event.equals(ModelEvent.DATA_LOADED)) {
		trace ('Controller::__onModelEvent -- Data Loaded!');
	}
}

Of course, if you prefer you could switch based on event.getLevel() with the case statements performing comparisons on ModelEvent.DATA_LOADED.getLevel();

Posted in ActionScript 2 | Tagged , , , , | 1 Comment

CNET TV Videos

I’ve recently been slaving away on Version 4 of the CNET Video Player. Today is the official launch of the new player and it would appear everything has gone smoothly. The player features numerous enhancements over the previous version including a full XSPF playlist, XML Configuration, Support for related content at the end of playback and off site embedding (with full metrics).



I will try and give a more detailed write up of some of the component features at a later date, but for now it’s back to work rolling out the rest of the enhancements to the video page!

Posted in ActionScript 2 | Leave a comment

Actionscript – Loading Acute and UTF-8 Characters from XML into Dynamic Text with Flash

Writing flexible applications in Flash is easy, (/me points in the direction of my XML Configuration Class) and there’s few reasons why all your text strings should not be read in at run time to allow for easy internationalization.

However, one pitfall to be aware of is trying to use Acutes and other “non-latin” characters in dynamic text fields (é, etc). I was having a problem when these characters were not only not showing up, but all characters after the acute were being truncated! Here’s my configuration XML:

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
 <textValue>armén</textValue>
 </configuration>

Side Note: Flash Player 9 appears to ignore the XML Encoding value specified in XML files, however it’s good practice to adhere to web standards, so make sure your XML’s encoding is set to UTF-8

The next obvious set is to ensure that you embedding all the required glyphs in your .fla file, you do this by clicking on the Embed… button for your Dynamic Textfield – make sure you embed all the Latin characters you need and then specify the acutes and other UTF-8 characters seperatley in the “include these characters:” textbox:

The Character Embedding Dialouge in Flash CS3

Now this is where the problem comes in – when I try to compile and run this flash movie, instead of getting the text armén in my textfield, I get arm with both the acute (é) and any trailing characters truncated (in some cases (as in the screen shot below), I even get some of the trailing XML… eek!)

The UTF-8 Text String "armén" is breaking the layout

After a bit of head scratching I finally managed to track down the problem: The XML file must be saved as UTF-8 encoding! (d’oh!) However, in tracking down the problem I also discovered another workaround using System.useCodepage. Setting this value to ‘true’ in the first frame of your actionscript will force flash to ignore the character encoding of the XML document.

Hope this saves someone some time.

Posted in ActionScript 2 | Tagged , , , , | 4 Comments

Validating Image Uploads in CakePHP

Following on from my article on CakePHP – Uploaded File Validation in Models, today’s snippet will show you how to use Cake’s validation rules to reject invalid images, or images which do not conform to a specified mime-type. This code relies on the fact that you have LibGD installed on your webserver.

<?php
Class Image extends AppModel
{
	var $name = 'Image';
	var $validate = array
	(
		'uploaded_image' => array
		(
			// Ensure file uploaded OK.
			'valid_upload' => array
			(
				'rule' => array('validateUploadedfile', false),
				'message' => 'An error occured whilst uploading your image, please try again',
			),
 
			// Check image is valid / of allowed mime-type
			'valid_image' => array
			(
				'rule' => array('isValidImageFile'),
				'message' => 'The file you have uploaded is not a valid or is unsupported, please try again',
			),
		),
	);
 
 
        /**
         * Custom validation rule for uploaded files.
         *
         *  @param Array $data CakePHP File info.
         *  @param Boolean $required Is this field required?
         *  @return Boolean
        */
        function validateUploadedFile($data, $required = false) 
        {
                // Remove first level of Array ($data['Image']['size'] becomes $data['size'])
                $upload_info = array_shift($data);
 
                // No file uploaded.
                if ($required && $upload_info['size'] == 0) {
                        return false;
                }
 
                // Check for Basic PHP file errors.
                if ($upload_info['error'] !== 0) {
                        return false;
                }
 
                // Finally, use PHP’s own file validation method.
                return is_uploaded_file($upload_info['tmp_name']);
        }
 
 
    /**
     * Use LibGD to determine if an uploaded file is a valid Image by
     * running it's mime-type against a list of $valid_mime_types
     *
     *	@param Array $data CakePHP File info
     *	@return Boolean
    */
	function isValidImageFile($data)
    {
        // Allow these image mime-types, all others will be rejected
        $valid_mime_types = array('image/jpeg', 'image/png', 'image/gif');
 
        $data = array_shift($data);
        $filename = $data['tmp_name'];
 
        // Catch I/O Errors.
        if (!is_readable($filename)) {
	        debug(__METHOD__." failed to read input file: {$filename}");
	        return false;
        }
 
        // Retrieve the MimeType of Image, if none is returned, it's invalid
        if (!$mime_type = $this->getImageMimeType($filename)) {
	        debug(__METHOD__." Uploaded file does not have a mime-type");
	        return false;
        }
 
        // Check the MimeType against the array of valid ones specified above
        if (!in_array($mime_type, $valid_mime_types)) {
	        debug(__METHOD__." Uploaded image has rejected Mime Type: {$mime_type}");
	        return false;
        }
 
        if (!$this->__getImageHandleFromFile($filename)) {
	        return false;
        }
 
        return true;
    }
 
 
    /** 
     * Use LibGD to return an uploaded Image's MimeType as a String, FALSE
     * on errors or if the file is not an image
     *
     *	@param String $filename Absolute path to file on disc
     *	@return String Image MimeType of $filename, false on failure
    */
    function getImageMimeType($filename)
    {
		// If this error is thrown LibGD is not installed on your server.
        if (!function_exists('getimagesize')) {
	        debug(__METHOD__." LibGD PHP Extension was not found, please refer to http://www.php.net/manual/en/book.image.php");
	        exit();
        }	        
 
        $result = getimagesize($filename);	        
        if (isset($result['mime'])) {
	        return $result['mime'];
        }
        return false;
    }
 
 
    /**
     * Returns a LibGD Image Handle for a file specified by $filename
     *
     *	@param String $filename Absolute path to image on disk
     *	@return LibGD Image Handle on success, FALSE on failure.
    */
    function __getImageHandleFromFile($filename)
    {
        if (!is_readable($filename)) {
	        debug(__METHOD__." failed to read input file: {$filename}");
	        return false;
        }
 
        // Retrieve the MimeType of Image, if none is returned, it's invalid
        if (!$mime_type = $this->getImageMimeType($filename)) {
	        debug(__METHOD__." failed to assertain MimeType of {$filename}");
	        return false;
        }
 
        switch ($mime_type)
        {
	        case 'image/jpeg':
	        	$handle = @imagecreatefromjpeg($filename);
	        	break;
 
	        case 'image/gif':
	        	$handle = @imagecreatefromgif($filename);
	        	break;
 
	        case 'image/png':
	        	$handle = @imagecreatefrompng($filename);
	        	break;
 
	        default:
	        	debug(__METHOD__." Didn't know how to handle MimeType: {$mime_type}");
	        	$handle = false;
	        	break;
        }
 
        return $handle;
    }
}

As always, comments are welcome.

Posted in CakePHP | Tagged , , , | 2 Comments

Holiday Photography

I’m back from a week long holiday in the bay of Elounda, Crete. During my stay I took about 200 pictures using my trusty Samsung i6 my favourite of which were taken on the Island of Spinalonga which served as a leprosy colony until 1957, the following pic was snapped looking back towards Plaka.

A sole tree on the Island of Spinalonga, Crete

The following picture was taken in the town of Aghios Nikolaos, which features a large central lake / dock which is famed for being bottomless and surrounded by restaurants. This particular flower was found all around Crete and looked like a giant dandelion – the “ball” was comprised of hundreds of blue spikes. I took this particular picture in response to a Chris’ own picture, Drunken Bumble Bee to which mine pales in comparison!

A bee on a plant in Aghios Nikolaos

Posted in Personal | Tagged | 1 Comment

CakePHP – Activating User Account via Email

Continuing on from my User Registration with the AuthComponent post I’m going to cover how to activate user account’s via email. Before we get down to the code lets look at a simple use case first.

Activating User Accounts Via Email Use Case
Goal: To confirm that users are registering with a valid email address, force them to activate their account before they can log in.

  1. User registers for an account, all validations passes and $User->save() has been called
  2. At this point we flag that the user’s account is pending activation. An email gets sent to the email address the user registered with. The email contains a unique activation link
  3. The user recieves the activation email and clicks the activation link
  4. The system (your website) handles the incoming link, checks that the activation link is correct (the hash matches) and marks the user’s account as “active” – the user can now log in!
    • Alternative Path: The activation link is rejected by the system (it’s invalid / wasn’t copied correctly) – we present some helpful information to the user.

Time for Some Code!
Okay, let’s start simple – the basic User Table we created in the previous article needs to be expanded to include an “active” flag (boolean) to indicate if the user’s account has been activated yet:

-- Table structure for table `users`
CREATE TABLE IF NOT EXISTS `users` (
  `id` int(11) NOT NULL auto_increment,
  `username` varchar(20) NOT NULL,
  `password` varchar(50) NOT NULL,
  `email` varchar(255) NOT NULL,
  `active` tinyint(1) NOT NULL default '0',
  `created` datetime NOT NULL,
  `modified` datetime NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1;

Okay, let’s go ahead and hook this into our Users Controller’s login() action to stop “un-activated” users from loging in (after all, that is the primary goal of performing this work).

<?php
// Note: not all logic is show!
uses(’sanitize’);
class UsersController extends AppController
{
        var $name = ‘Users’;
        var $components = array(‘Auth’);
        function login() {
                // Check for incoming login request.
                if ($this->data) {
			// Use the AuthComponent's login action
                        if ($this->Auth->login($this->data)) {
				// Retrieve user data
				$results = $this->User->find(array('User.username' => $this->data['User']['username']), array('User.active'), null, false);
				// Check to see if the User's account isn't active
				if ($results['User']['active'] == 0) {
					// Uh Oh!
					$this->Session->setFlash('Your account has not been activated yet!');
					$this->Auth->logout();
					$this->redirect('/users/login');
				}
				// Cool, user is active, redirect post login
				else {
					$this->redirect('/');
				}
                        }
                }
        }
}
?>

With this login check in place, we now need to sort out sending out the email which will actually “activate” the user’s account for them. Before we start with the controller actions, let’s defined some custom logic in the Model. As a quick side note, I work to the principle of skinny controllers, fat models (and so should you). What this means, in a nutshell – is that any logic which relates to a Model (in our case, generating the Confirmation Link) should be done in the Model – so let’s do that now.

<?php
# /app/models/user.php 
# please note that validation logic is not shown
Class User extends AppModel
{
        var $name = ‘User’;
 
	/**
	 * Creates an activation hash for the current user.
	 *
	 *	@param Void
	 *	@return String activation hash.
	*/
	function getActivationHash()
	{
		if (!isset($this->id)) {
			return false;
		}
		return substr(Security::hash(Configure::read('Security.salt') . $this->field('created') . date('Ymd')), 0, 8);
	}
}
?>

So, incase you didn’t gather, we can grab a unique Activation Hash for any given user by calling $User->getActivationHash() from inside the controller. Let’s just break down what we are doing in the getActivationHash funciton and the reason why we’re doing it.

When we send the email to the user, we are going to send them a link which they can click on to activate their account. If we don’t create unique activation links then users would be able to “guess” or craft activation links for other users, for example, if we didn’t use an activation hash our links may look like this: http://mysite.com/user/activate/jreeves/ – Hmm, well I know that my username is jreeves, so I could guess pretty easily that /users/activate/dchang is going to active someone elses’ account… not great.

So, what is getActivationHash doing? Basically, it’s taking the datetime of when the user created their account (this will be unique for each user), adding in the Day-Month-Year value (so that activation links only last for 24 hours) and combining the whole shebang with the Security.salt value from CakePHP’s core.ini and Hashing it (with either MD5 or SHA-1 depending on your Cake’s settings). In case you are wondering, this process is called salting and it makes any unique value (such as a password, or MD5 hash), almost impossible to guess.

Okay, enough talk, let’s hook this into the register action so that this email gets sent out.

<?php
# /controllers/users_controller.php
# please note that not all code is shown...
uses('sanitize');
class UsersController extends AppController {
	var $name = 'Users';
	// Include the Email Component so we can send some out :)
	var $components = array('Email', 'Auth');
 
	// Allow users to access the following action when not logged in	
	function beforeFilter() {
		$this->Auth->allow('register', 'thanks', 'confirm', 'logout');
		$this->Auth->autoRedirect = false;
	}
 
	// Allows a user to sign up for a new account
	function register() {
		if (!empty($this->data)) {
			// See my previous post if this is forgien to you
			$this->data['User']['password'] = $this->Auth->password($this->data['User']['passwrd']);
			$this->User->data = Sanitize::clean($this->data);
			// Successfully created account - send activation email			
			if ($this->User->save()) {
				$this->__sendActivationEmail($this->User->getLastInsertID());
 
				// this view is not show / listed - use your imagination and inform
				// users that an activation email has been sent out to them.
				$this->redirect('/users/thanks');
			}
			// Failed, clear password field
			else {
				$this->data['User']['passwrd'] = null;
			}
		}
	}
 
	/**
	 * Send out an activation email to the user.id specified by $user_id
	 *  @param Int $user_id User to send activation email to
	 *  @return Boolean indicates success
	*/ 
	function __sendActivationEmail($user_id) {
		$user = $this->User->find(array('User.id' => $user_id), array('User.email', 'User.username'), null, false);
		if ($user === false) {
			debug(__METHOD__." failed to retrieve User data for user.id: {$user_id}");
			return false;
		}
 
		// Set data for the "view" of the Email
		$this->set('activate_url', 'http://' . env('SERVER_NAME') . '/users/activate/' . $user['User']['id'] . '/' . $this->User->getActivationHash());
		$this->set('username', $this->data['User']['username']);
 
		$this->Email->to = $user['User']['email'];
		$this->Email->subject = env('SERVER_NAME') . ' - Please confirm your email address';
		$this->Email->from = 'noreply@' . env('SERVER_NAME');
		$this->Email->template = 'user_confirm';
		$this->Email->sendAs = 'text';   // you probably want to use both :)	
		return $this->Email->send();
	}
}
?>

Okay, now we’re cooking – time to create the Email “views” which will be sent out with the emails – in case you are not familiar with the EmailComponent then now would be a good time to refer to the CookBook. So, let’s create the plain text email template which will contain the activation link set above:

<?php
# /app/views/elements/email/text/user_confirm.ctp
?>
Hey there <?= $username ?>, we will have you up and running in no time, but first we just need you to confirm your user account by clicking the link below:
 
<?= $activate_url ?>

Phew! The end is in sight, just one more controller action to hook up (and probably the most important one) – /users/activate – I’m sure you can figure out what this is going to do.

<?php
# /controllers/user_controller.php
# note that only the activate function is shown...

/**
 * Activates a user account from an incoming link
 *
 *  @param Int $user_id User.id to activate
 *  @param String $in_hash Incoming Activation Hash from the email
*/
function activate($user_id = null, $in_hash = null) {
	$this->User->id = $id;
	if ($this->User->exists() && ($in_hash == $this->User->getActivationHash()))
	{
		// Update the active flag in the database
		$this->User->saveField('active', 1);
 
		// Let the user know they can now log in!
		$this->Session->setFlash('Your account has been activated, please log in below');
		$this->redirect('login');
	}
 
	// Activation failed, render '/views/user/activate.ctp' which should tell the user.
}
?>

And there we have it, now when your users register they have to confirm their user accounts via Email – job done!

Posted in CakePHP | Tagged | 23 Comments

CakePHP – Open_Basedir Restriction in Effect

Just deployed a CakePHP on a domain running PLESK and dismayed by the fact that your screen is over-flowing with warning about open_basedir restriction in effect? Fear not, the solution is straight forward (if a little frustrating to track down!)

The problem is caused by this line (line 69, in CakePHP 1.2.0.6311) in /app/webroot/index.php:

ini_set('include_path', CAKE_CORE_INCLUDE_PATH . PATH_SEPARATOR . ROOT . DS . APP_DIR . DS . PATH_SEPARATOR . ini_get('include_path'));

Yep, the simple ini_set command – the clues should start coming in thick and fast that ini_set is causing the problem due to the fact taht open_basedir complains about paths with :’s in their structure. (:’s being the delimiter for automatically included paths). To fix the problem you can either remove the if logic that calls “function_exists(‘ini_set’)”, or, if you prefer to fix things the corret way, modify the line to remove the final stub:

ini_set('include_path', CAKE_CORE_INCLUDE_PATH . PATH_SEPARATOR . ROOT . DS . APP_DIR . DS);

Good luck, hope that saves someone a headache! :)

Posted in CakePHP | Tagged , , , | 4 Comments