From 9b6663f87a7e679ffba691cf516191fc840cf978 Mon Sep 17 00:00:00 2001
From: Bharat Mediratta 
Date: Tue, 24 Nov 2009 19:20:36 -0800
Subject: Update to Kohana r4684 which is now Kohana 2.4 and has substantial
 changes.
---
 system/libraries/drivers/Cache.php                |  36 +-
 system/libraries/drivers/Cache/Apc.php            |  64 ---
 system/libraries/drivers/Cache/Eaccelerator.php   |  66 ---
 system/libraries/drivers/Cache/File.php           | 268 +++++----
 system/libraries/drivers/Cache/Memcache.php       | 200 +++----
 system/libraries/drivers/Cache/Sqlite.php         | 257 ---------
 system/libraries/drivers/Cache/Xcache.php         | 144 +++--
 system/libraries/drivers/Captcha.php              | 227 --------
 system/libraries/drivers/Captcha/Alpha.php        |  92 ----
 system/libraries/drivers/Captcha/Basic.php        |  81 ---
 system/libraries/drivers/Captcha/Black.php        |  72 ---
 system/libraries/drivers/Captcha/Math.php         |  61 ---
 system/libraries/drivers/Captcha/Riddle.php       |  47 --
 system/libraries/drivers/Captcha/Word.php         |  37 --
 system/libraries/drivers/Config.php               | 257 +++++++++
 system/libraries/drivers/Config/Array.php         |  83 +++
 system/libraries/drivers/Database.php             | 636 ----------------------
 system/libraries/drivers/Database/Mssql.php       | 462 ----------------
 system/libraries/drivers/Database/Mysql.php       | 496 -----------------
 system/libraries/drivers/Database/Mysqli.php      | 358 ------------
 system/libraries/drivers/Database/Pdosqlite.php   | 486 -----------------
 system/libraries/drivers/Database/Pgsql.php       | 538 ------------------
 system/libraries/drivers/Image.php                |  10 +-
 system/libraries/drivers/Image/GD.php             |  69 ++-
 system/libraries/drivers/Image/GraphicsMagick.php |  14 +-
 system/libraries/drivers/Image/ImageMagick.php    |  39 +-
 system/libraries/drivers/Log.php                  |  22 +
 system/libraries/drivers/Log/Database.php         |  40 ++
 system/libraries/drivers/Log/File.php             |  44 ++
 system/libraries/drivers/Log/Syslog.php           |  34 ++
 system/libraries/drivers/Session.php              |   6 +-
 system/libraries/drivers/Session/Cache.php        |  10 +-
 system/libraries/drivers/Session/Cookie.php       |  10 +-
 system/libraries/drivers/Session/Database.php     |  40 +-
 34 files changed, 925 insertions(+), 4381 deletions(-)
 delete mode 100644 system/libraries/drivers/Cache/Apc.php
 delete mode 100644 system/libraries/drivers/Cache/Eaccelerator.php
 delete mode 100644 system/libraries/drivers/Cache/Sqlite.php
 delete mode 100644 system/libraries/drivers/Captcha.php
 delete mode 100644 system/libraries/drivers/Captcha/Alpha.php
 delete mode 100644 system/libraries/drivers/Captcha/Basic.php
 delete mode 100644 system/libraries/drivers/Captcha/Black.php
 delete mode 100644 system/libraries/drivers/Captcha/Math.php
 delete mode 100644 system/libraries/drivers/Captcha/Riddle.php
 delete mode 100644 system/libraries/drivers/Captcha/Word.php
 create mode 100644 system/libraries/drivers/Config.php
 create mode 100644 system/libraries/drivers/Config/Array.php
 delete mode 100644 system/libraries/drivers/Database.php
 delete mode 100644 system/libraries/drivers/Database/Mssql.php
 delete mode 100644 system/libraries/drivers/Database/Mysql.php
 delete mode 100644 system/libraries/drivers/Database/Mysqli.php
 delete mode 100644 system/libraries/drivers/Database/Pdosqlite.php
 delete mode 100644 system/libraries/drivers/Database/Pgsql.php
 create mode 100644 system/libraries/drivers/Log.php
 create mode 100644 system/libraries/drivers/Log/Database.php
 create mode 100644 system/libraries/drivers/Log/File.php
 create mode 100644 system/libraries/drivers/Log/Syslog.php
(limited to 'system/libraries/drivers')
diff --git a/system/libraries/drivers/Cache.php b/system/libraries/drivers/Cache.php
index 7c5e3c31..97415096 100644
--- a/system/libraries/drivers/Cache.php
+++ b/system/libraries/drivers/Cache.php
@@ -1,40 +1,42 @@
 config = $config;
+		$this->config['directory'] = str_replace('\\', '/', realpath($this->config['directory'])).'/';
 
-		// Make sure the cache directory is writable
-		if ( ! is_dir($directory) OR ! is_writable($directory))
-			throw new Kohana_Exception('cache.unwritable', $directory);
-
-		// Directory is valid
-		$this->directory = $directory;
+		if ( ! is_dir($this->config['directory']) OR ! is_writable($this->config['directory']))
+			throw new Cache_Exception('The configured cache directory, :directory:, is not writable.', array(':directory:' => $this->config['directory']));
 	}
 
 	/**
 	 * Finds an array of files matching the given id or tag.
 	 *
-	 * @param  string  cache id or tag
+	 * @param  string  cache key or tag
 	 * @param  bool    search for tags
 	 * @return array   of filenames matching the id or tag
 	 */
-	public function exists($id, $tag = FALSE)
+	public function exists($keys, $tag = FALSE)
 	{
-		if ($id === TRUE)
+		if ($keys === TRUE)
 		{
 			// Find all the files
-			return glob($this->directory.'*~*~*');
+			return glob($this->config['directory'].'*~*~*');
 		}
 		elseif ($tag === TRUE)
 		{
 			// Find all the files that have the tag name
-			$paths = glob($this->directory.'*~*'.$id.'*~*');
+			$paths = array();
+
+			foreach ( (array) $keys as $tag)
+			{
+				$paths = array_merge($paths, glob($this->config['directory'].'*~*'.$tag.'*~*'));
+			}
 
 			// Find all tags matching the given tag
 			$files = array();
+
 			foreach ($paths as $path)
 			{
 				// Split the files
@@ -60,12 +59,16 @@ class Cache_File_Driver implements Cache_Driver {
 					continue;
 
 				// Split the tags by plus signs, used to separate tags
-				$tags = explode('+', $tags[1]);
+				$item_tags = explode('+', $tags[1]);
 
-				if (in_array($tag, $tags))
+				// Check each supplied tag, and match aginst the tags on each item.
+				foreach ($keys as $tag)
 				{
-					// Add the file to the array, it has the requested tag
-					$files[] = $path;
+					if (in_array($tag, $item_tags))
+					{
+						// Add the file to the array, it has the requested tag
+						$files[] = $path;
+					}
 				}
 			}
 
@@ -73,98 +76,68 @@ class Cache_File_Driver implements Cache_Driver {
 		}
 		else
 		{
-			// Find the file matching the given id
-			return glob($this->directory.$id.'~*');
+			$paths = array();
+
+			foreach ( (array) $keys as $key)
+			{
+				// Find the file matching the given key
+				$paths = array_merge($paths, glob($this->config['directory'].str_replace(array('/', '\\', ' '), '_', $key).'~*'));
+			}
+
+			return $paths;
 		}
 	}
 
-	/**
-	 * Sets a cache item to the given data, tags, and lifetime.
-	 *
-	 * @param   string   cache id to set
-	 * @param   string   data in the cache
-	 * @param   array    cache tags
-	 * @param   integer  lifetime
-	 * @return  bool
-	 */
-	public function set($id, $data, array $tags = NULL, $lifetime)
+	public function set($items, $tags = NULL, $lifetime = NULL)
 	{
-		// Remove old cache files
-		$this->delete($id);
-
-		// Cache File driver expects unix timestamp
 		if ($lifetime !== 0)
 		{
+			// File driver expects unix timestamp
 			$lifetime += time();
 		}
 
-		if ( ! empty($tags))
+
+		if ( ! is_null($tags) AND ! empty($tags))
 		{
 			// Convert the tags into a string list
-			$tags = implode('+', $tags);
+			$tags = implode('+', (array) $tags);
 		}
 
-		// Write out a serialized cache
-		return (bool) file_put_contents($this->directory.$id.'~'.$tags.'~'.$lifetime, serialize($data));
-	}
+		$success = TRUE;
 
-	/**
-	 * Finds an array of ids for a given tag.
-	 *
-	 * @param  string  tag name
-	 * @return array   of ids that match the tag
-	 */
-	public function find($tag)
-	{
-		// An array will always be returned
-		$result = array();
-
-		if ($paths = $this->exists($tag, TRUE))
+		foreach ($items as $key => $value)
 		{
-			// Length of directory name
-			$offset = strlen($this->directory);
+			if (is_resource($value))
+				throw new Cache_Exception('Caching of resources is impossible, because resources cannot be serialised.');
 
-			// Find all the files with the given tag
-			foreach ($paths as $path)
-			{
-				// Get the id from the filename
-				list($id, $junk) = explode('~', basename($path), 2);
+			// Remove old cache file
+			$this->delete($key);
 
-				if (($data = $this->get($id)) !== FALSE)
-				{
-					// Add the result to the array
-					$result[$id] = $data;
-				}
+			if ( ! (bool) file_put_contents($this->config['directory'].str_replace(array('/', '\\', ' '), '_', $key).'~'.$tags.'~'.$lifetime, serialize($value)))
+			{
+				$success = FALSE;
 			}
 		}
 
-		return $result;
+		return $success;
 	}
 
-	/**
-	 * Fetches a cache item. This will delete the item if it is expired or if
-	 * the hash does not match the stored hash.
-	 *
-	 * @param   string  cache id
-	 * @return  mixed|NULL
-	 */
-	public function get($id)
+	public function get($keys, $single = FALSE)
 	{
-		if ($file = $this->exists($id))
+		$items = array();
+
+		if ($files = $this->exists($keys))
 		{
-			// Use the first file
-			$file = current($file);
+			// Turn off errors while reading the files
+			$ER = error_reporting(0);
 
-			// Validate that the cache has not expired
-			if ($this->expired($file))
-			{
-				// Remove this cache, it has expired
-				$this->delete($id);
-			}
-			else
+			foreach ($files as $file)
 			{
-				// Turn off errors while reading the file
-				$ER = error_reporting(0);
+				// Validate that the item has not expired
+				if ($this->expired($file))
+					continue;
+
+				list($key, $junk) = explode('~', basename($file), 2);
 
 				if (($data = file_get_contents($file)) !== FALSE)
 				{
@@ -173,80 +146,102 @@ class Cache_File_Driver implements Cache_Driver {
 				}
 				else
 				{
-					// Delete the data
-					unset($data);
+					$data = NULL;
 				}
 
-				// Turn errors back on
-				error_reporting($ER);
+				$items[$key] = $data;
 			}
+
+			// Turn errors back on
+			error_reporting($ER);
 		}
 
-		// Return NULL if there is no data
-		return isset($data) ? $data : NULL;
+		// Reutrn a single item if only one key was requested
+		if ($single)
+		{
+			return (count($items) > 0) ? current($items) : NULL;
+		}
+		else
+		{
+			return $items;
+		}
 	}
 
 	/**
-	 * Deletes a cache item by id or tag
-	 *
-	 * @param   string   cache id or tag, or TRUE for "all items"
-	 * @param   boolean  use tags
-	 * @return  boolean
+	 * Get cache items by tag
+	 */
+	public function get_tag($tags)
+	{
+		// An array will always be returned
+		$result = array();
+
+		if ($paths = $this->exists($tags, TRUE))
+		{
+			// Find all the files with the given tag
+			foreach ($paths as $path)
+			{
+				// Get the id from the filename
+				list($key, $junk) = explode('~', basename($path), 2);
+
+				if (($data = $this->get($key)) !== FALSE)
+				{
+					// Add the result to the array
+					$result[$key] = $data;
+				}
+			}
+		}
+
+		return $result;
+	}
+
+	/**
+	 * Delete cache items by keys or tags
 	 */
-	public function delete($id, $tag = FALSE)
+	public function delete($keys, $tag = FALSE)
 	{
-		$files = $this->exists($id, $tag);
+		$success = TRUE;
 
-		if (empty($files))
-			return FALSE;
+		$paths = $this->exists($keys, $tag);
 
 		// Disable all error reporting while deleting
 		$ER = error_reporting(0);
 
-		foreach ($files as $file)
+		foreach ($paths as $path)
 		{
 			// Remove the cache file
-			if ( ! unlink($file))
-				Kohana::log('error', 'Cache: Unable to delete cache file: '.$file);
+			if ( ! unlink($path))
+			{
+				Kohana::log('error', 'Cache: Unable to delete cache file: '.$path);
+				$success = FALSE;
+			}
 		}
 
 		// Turn on error reporting again
 		error_reporting($ER);
 
-		return TRUE;
+		return $success;
 	}
 
 	/**
-	 * Deletes all cache files that are older than the current time.
-	 *
-	 * @return void
+	 * Delete cache items by tag
 	 */
-	public function delete_expired()
+	public function delete_tag($tags)
 	{
-		if ($files = $this->exists(TRUE))
-		{
-			// Disable all error reporting while deleting
-			$ER = error_reporting(0);
-
-			foreach ($files as $file)
-			{
-				if ($this->expired($file))
-				{
-					// The cache file has already expired, delete it
-					if ( ! unlink($file))
-						Kohana::log('error', 'Cache: Unable to delete cache file: '.$file);
-				}
-			}
+		return $this->delete($tags, TRUE);
+	}
 
-			// Turn on error reporting again
-			error_reporting($ER);
-		}
+	/**
+	 * Empty the cache
+	 */
+	public function delete_all()
+	{
+		return $this->delete(TRUE);
 	}
 
 	/**
 	 * Check if a cache file has expired by filename.
 	 *
-	 * @param  string  filename
+	 * @param  string|array   array of filenames
 	 * @return bool
 	 */
 	protected function expired($file)
@@ -257,5 +252,4 @@ class Cache_File_Driver implements Cache_Driver {
 		// Expirations of 0 are "never expire"
 		return ($expires !== 0 AND $expires <= time());
 	}
-
-} // End Cache File Driver
\ No newline at end of file
+} // End Cache Memcache Driver
diff --git a/system/libraries/drivers/Cache/Memcache.php b/system/libraries/drivers/Cache/Memcache.php
index d801de9c..636191d4 100644
--- a/system/libraries/drivers/Cache/Memcache.php
+++ b/system/libraries/drivers/Cache/Memcache.php
@@ -2,190 +2,128 @@
 /**
  * Memcache-based Cache driver.
  *
- * $Id: Memcache.php 4102 2009-03-19 12:55:54Z Shadowhand $
+ * $Id$
  *
  * @package    Cache
  * @author     Kohana Team
- * @copyright  (c) 2007-2008 Kohana Team
- * @license    http://kohanaphp.com/license.html
+ * @copyright  (c) 2007-2009 Kohana Team
+ * @license    http://kohanaphp.com/license
  */
-class Cache_Memcache_Driver implements Cache_Driver {
-
-	const TAGS_KEY = 'memcache_tags_array';
-
-	// Cache backend object and flags
+class Cache_Memcache_Driver extends Cache_Driver {
+	protected $config;
 	protected $backend;
 	protected $flags;
 
-	// Tags array
-	protected static $tags;
-
-	// Have the tags been changed?
-	protected static $tags_changed = FALSE;
-
-	public function __construct()
+	public function __construct($config)
 	{
 		if ( ! extension_loaded('memcache'))
-			throw new Kohana_Exception('cache.extension_not_loaded', 'memcache');
+			throw new Kohana_Exception('The memcache PHP extension must be loaded to use this driver.');
 
+		ini_set('memcache.allow_failover', (isset($config['allow_failover']) AND $config['allow_failover']) ? TRUE : FALSE);
+
+		$this->config = $config;
 		$this->backend = new Memcache;
-		$this->flags = Kohana::config('cache_memcache.compression') ? MEMCACHE_COMPRESSED : FALSE;
 
-		$servers = Kohana::config('cache_memcache.servers');
+		$this->flags = (isset($config['compression']) AND $config['compression']) ? MEMCACHE_COMPRESSED : FALSE;
 
-		foreach ($servers as $server)
+		foreach ($config['servers'] as $server)
 		{
 			// Make sure all required keys are set
-			$server += array('host' => '127.0.0.1', 'port' => 11211, 'persistent' => FALSE);
+			$server += array('host' => '127.0.0.1',
+			                 'port' => 11211,
+			                 'persistent' => FALSE,
+			                 'weight' => 1,
+			                 'timeout' => 1,
+			                 'retry_interval' => 15
+			);
 
 			// Add the server to the pool
-			$this->backend->addServer($server['host'], $server['port'], (bool) $server['persistent'])
-				or Kohana::log('error', 'Cache: Connection failed: '.$server['host']);
-		}
-
-		// Load tags
-		self::$tags = $this->backend->get(self::TAGS_KEY);
-
-		if ( ! is_array(self::$tags))
-		{
-			// Create a new tags array
-			self::$tags = array();
-
-			// Tags have been created
-			self::$tags_changed = TRUE;
+			$this->backend->addServer($server['host'], $server['port'], (bool) $server['persistent'], (int) $server['weight'], (int) $server['timeout'], (int) $server['retry_interval'], TRUE, array($this,'_memcache_failure_callback'));
 		}
 	}
 
-	public function __destruct()
+	public function _memcache_failure_callback($host, $port)
 	{
-		if (self::$tags_changed === TRUE)
-		{
-			// Save the tags
-			$this->backend->set(self::TAGS_KEY, self::$tags, $this->flags, 0);
-
-			// Tags are now unchanged
-			self::$tags_changed = FALSE;
-		}
+		$this->backend->setServerParams($host, $port, 1, -1, FALSE);
+		Kohana_Log::add('error', __('Cache: Memcache server down: :host:::port:',array(':host:' => $host,':port:' => $port)));
 	}
 
-	public function find($tag)
+	public function set($items, $tags = NULL, $lifetime = NULL)
 	{
-		if (isset(self::$tags[$tag]) AND $results = $this->backend->get(self::$tags[$tag]))
-		{
-				// Return all the found caches
-				return $results;
-		}
-		else
+		if ($lifetime !== 0)
 		{
-			// No matching tags
-			return array();
+			// Memcache driver expects unix timestamp
+			$lifetime += time();
 		}
-	}
 
-	public function get($id)
-	{
-		return (($return = $this->backend->get($id)) === FALSE) ? NULL : $return;
-	}
+		if ($tags !== NULL)
+			throw new Cache_Exception('Memcache driver does not support tags');
 
-	public function set($id, $data, array $tags = NULL, $lifetime)
-	{
-		if ( ! empty($tags))
+		foreach ($items as $key => $value)
 		{
-			// Tags will be changed
-			self::$tags_changed = TRUE;
+			if (is_resource($value))
+				throw new Cache_Exception('Caching of resources is impossible, because resources cannot be serialised.');
 
-			foreach ($tags as $tag)
+			if ( ! $this->backend->set($key, $value, $this->flags, $lifetime))
 			{
-				// Add the id to each tag
-				self::$tags[$tag][$id] = $id;
+				return FALSE;
 			}
 		}
 
-		if ($lifetime !== 0)
-		{
-			// Memcache driver expects unix timestamp
-			$lifetime += time();
-		}
-
-		// Set a new value
-		return $this->backend->set($id, $data, $this->flags, $lifetime);
+		return TRUE;
 	}
 
-	public function delete($id, $tag = FALSE)
+	public function get($keys, $single = FALSE)
 	{
-		// Tags will be changed
-		self::$tags_changed = TRUE;
+		$items = $this->backend->get($keys);
 
-		if ($id === TRUE)
+		if ($single)
 		{
-			if ($status = $this->backend->flush())
-			{
-				// Remove all tags, all items have been deleted
-				self::$tags = array();
-
-				// We must sleep after flushing, or overwriting will not work!
-				// @see http://php.net/manual/en/function.memcache-flush.php#81420
-				sleep(1);
-			}
-
-			return $status;
-		}
-		elseif ($tag === TRUE)
-		{
-			if (isset(self::$tags[$id]))
-			{
-				foreach (self::$tags[$id] as $_id)
-				{
-					// Delete each id in the tag
-					$this->backend->delete($_id);
-				}
-
-				// Delete the tag
-				unset(self::$tags[$id]);
-			}
-
-			return TRUE;
+			return ($items === FALSE OR count($items) > 0) ? current($items) : NULL;
 		}
 		else
 		{
-			foreach (self::$tags as $tag => $_ids)
-			{
-				if (isset(self::$tags[$tag][$id]))
-				{
-					// Remove the id from the tags
-					unset(self::$tags[$tag][$id]);
-				}
-			}
-
-			return $this->backend->delete($id);
+			return ($items === FALSE) ? array() : $items;
 		}
 	}
 
-	public function delete_expired()
+	/**
+	 * Get cache items by tag
+	 */
+	public function get_tag($tags)
 	{
-		// Tags will be changed
-		self::$tags_changed = TRUE;
+		throw new Cache_Exception('Memcache driver does not support tags');
+	}
 
-		foreach (self::$tags as $tag => $_ids)
+	/**
+	 * Delete cache item by key
+	 */
+	public function delete($keys)
+	{
+		foreach ($keys as $key)
 		{
-			foreach ($_ids as $id)
+			if ( ! $this->backend->delete($key))
 			{
-				if ( ! $this->backend->get($id))
-				{
-					// This id has disappeared, delete it from the tags
-					unset(self::$tags[$tag][$id]);
-				}
-			}
-
-			if (empty(self::$tags[$tag]))
-			{
-				// The tag no longer has any valid ids
-				unset(self::$tags[$tag]);
+				return FALSE;
 			}
 		}
 
-		// Memcache handles garbage collection internally
 		return TRUE;
 	}
 
+	/**
+	 * Delete cache items by tag
+	 */
+	public function delete_tag($tags)
+	{
+		throw new Cache_Exception('Memcache driver does not support tags');
+	}
+
+	/**
+	 * Empty the cache
+	 */
+	public function delete_all()
+	{
+		return $this->backend->flush();
+	}
 } // End Cache Memcache Driver
diff --git a/system/libraries/drivers/Cache/Sqlite.php b/system/libraries/drivers/Cache/Sqlite.php
deleted file mode 100644
index 9458d851..00000000
--- a/system/libraries/drivers/Cache/Sqlite.php
+++ /dev/null
@@ -1,257 +0,0 @@
-db = new SQLiteDatabase($filename, '0666', $error);
-
-		// Throw an exception if there's an error
-		if ( ! empty($error))
-			throw new Kohana_Exception('cache.driver_error', sqlite_error_string($error));
-
-		$query  = "SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'caches'";
-		$tables = $this->db->query($query, SQLITE_BOTH, $error);
-
-		// Throw an exception if there's an error
-		if ( ! empty($error))
-			throw new Kohana_Exception('cache.driver_error', sqlite_error_string($error));
-
-		if ($tables->numRows() == 0)
-		{
-			Kohana::log('error', 'Cache: Initializing new SQLite cache database');
-
-			// Issue a CREATE TABLE command
-			$this->db->unbufferedQuery(Kohana::config('cache_sqlite.schema'));
-		}
-	}
-
-	/**
-	 * Checks if a cache id is already set.
-	 *
-	 * @param  string   cache id
-	 * @return boolean
-	 */
-	public function exists($id)
-	{
-		// Find the id that matches
-		$query = "SELECT id FROM caches WHERE id = '$id'";
-
-		return ($this->db->query($query)->numRows() > 0);
-	}
-
-	/**
-	 * Sets a cache item to the given data, tags, and lifetime.
-	 *
-	 * @param   string   cache id to set
-	 * @param   string   data in the cache
-	 * @param   array    cache tags
-	 * @param   integer  lifetime
-	 * @return  bool
-	 */
-	public function set($id, $data, array $tags = NULL, $lifetime)
-	{
-		// Serialize and escape the data
-		$data = sqlite_escape_string(serialize($data));
-
-		if ( ! empty($tags))
-		{
-			// Escape the tags, adding brackets so the tag can be explicitly matched
-			$tags = sqlite_escape_string('<'.implode('>,<', $tags).'>');
-		}
-
-		// Cache Sqlite driver expects unix timestamp
-		if ($lifetime !== 0)
-		{
-			$lifetime += time();
-		}
-
-		$query = $this->exists($id)
-			? "UPDATE caches SET tags = '$tags', expiration = '$lifetime', cache = '$data' WHERE id = '$id'"
-			: "INSERT INTO caches VALUES('$id', '$tags', '$lifetime', '$data')";
-
-		// Run the query
-		$this->db->unbufferedQuery($query, SQLITE_BOTH, $error);
-
-		if ( ! empty($error))
-		{
-			self::log_error($error);
-			return FALSE;
-		}
-		else
-		{
-			return TRUE;
-		}
-	}
-
-	/**
-	 * Finds an array of ids for a given tag.
-	 *
-	 * @param  string  tag name
-	 * @return array   of ids that match the tag
-	 */
-	public function find($tag)
-	{
-		$query = "SELECT id,cache FROM caches WHERE tags LIKE '%<{$tag}>%'";
-		$query = $this->db->query($query, SQLITE_BOTH, $error);
-
-		// An array will always be returned
-		$result = array();
-
-		if ( ! empty($error))
-		{
-			self::log_error($error);
-		}
-		elseif ($query->numRows() > 0)
-		{
-			// Disable notices for unserializing
-			$ER = error_reporting(~E_NOTICE);
-
-			while ($row = $query->fetchObject())
-			{
-				// Add each cache to the array
-				$result[$row->id] = unserialize($row->cache);
-			}
-
-			// Turn notices back on
-			error_reporting($ER);
-		}
-
-		return $result;
-	}
-
-	/**
-	 * Fetches a cache item. This will delete the item if it is expired or if
-	 * the hash does not match the stored hash.
-	 *
-	 * @param  string  cache id
-	 * @return mixed|NULL
-	 */
-	public function get($id)
-	{
-		$query = "SELECT id, expiration, cache FROM caches WHERE id = '$id' LIMIT 0, 1";
-		$query = $this->db->query($query, SQLITE_BOTH, $error);
-
-		if ( ! empty($error))
-		{
-			self::log_error($error);
-		}
-		elseif ($cache = $query->fetchObject())
-		{
-			// Make sure the expiration is valid and that the hash matches
-			if ($cache->expiration != 0 AND $cache->expiration <= time())
-			{
-				// Cache is not valid, delete it now
-				$this->delete($cache->id);
-			}
-			else
-			{
-				// Disable notices for unserializing
-				$ER = error_reporting(~E_NOTICE);
-				
-				// Return the valid cache data
-				$data = $cache->cache;
-
-				// Turn notices back on
-				error_reporting($ER);
-			}
-		}
-
-		// No valid cache found
-		return NULL;
-	}
-
-	/**
-	 * Deletes a cache item by id or tag
-	 *
-	 * @param  string  cache id or tag, or TRUE for "all items"
-	 * @param  bool    delete a tag
-	 * @return bool
-	 */
-	public function delete($id, $tag = FALSE)
-	{
-		if ($id === TRUE)
-		{
-			// Delete all caches
-			$where = '1';
-		}
-		elseif ($tag === TRUE)
-		{
-			// Delete by tag
-			$where = "tags LIKE '%<{$id}>%'";
-		}
-		else
-		{
-			// Delete by id
-			$where = "id = '$id'";
-		}
-
-		$this->db->unbufferedQuery('DELETE FROM caches WHERE '.$where, SQLITE_BOTH, $error);
-
-		if ( ! empty($error))
-		{
-			self::log_error($error);
-			return FALSE;
-		}
-		else
-		{
-			return TRUE;
-		}
-	}
-
-	/**
-	 * Deletes all cache files that are older than the current time.
-	 */
-	public function delete_expired()
-	{
-		// Delete all expired caches
-		$query = 'DELETE FROM caches WHERE expiration != 0 AND expiration <= '.time();
-
-		$this->db->unbufferedQuery($query);
-
-		return TRUE;
-	}
-
-} // End Cache SQLite Driver
\ No newline at end of file
diff --git a/system/libraries/drivers/Cache/Xcache.php b/system/libraries/drivers/Cache/Xcache.php
index 6254bbb6..ad11e6d7 100644
--- a/system/libraries/drivers/Cache/Xcache.php
+++ b/system/libraries/drivers/Cache/Xcache.php
@@ -1,85 +1,128 @@
 config = $config;
 	}
 
-	public function set($id, $data, array $tags = NULL, $lifetime)
+	public function set($items, $tags = NULL, $lifetime = NULL)
 	{
-		if ( ! empty($tags))
+		if ($tags !== NULL)
 		{
-			Kohana::log('error', 'Cache: tags are unsupported by the Xcache driver');
+			Kohana_Log::add('debug', __('Cache: XCache driver does not support tags'));
 		}
 
-		return xcache_set($id, $data, $lifetime);
-	}
+		foreach ($items as $key => $value)
+		{
+			if (is_resource($value))
+				throw new Cache_Exception('Caching of resources is impossible, because resources cannot be serialised.');
 
-	public function find($tag)
-	{
-		Kohana::log('error', 'Cache: tags are unsupported by the Xcache driver');
-		return FALSE;
+			if ( ! xcache_set($key, $value, $lifetime))
+			{
+				return FALSE;
+			}
+		}
+
+		return TRUE;
 	}
 
-	public function delete($id, $tag = FALSE)
+	public function get($keys, $single = FALSE)
 	{
-		if ($tag !== FALSE)
-		{
-			Kohana::log('error', 'Cache: tags are unsupported by the Xcache driver');
-			return TRUE;
-		}
-		elseif ($id !== TRUE)
+		$items = array();
+
+		foreach ($keys as $key)
 		{
 			if (xcache_isset($id))
-				return xcache_unset($id);
+			{
+				$items[$key] = xcache_get($id);
+			}
+			else
+			{
+				$items[$key] = NULL;
+			}
+		}
 
-			return FALSE;
+		if ($single)
+		{
+			return ($items === FALSE OR count($items) > 0) ? current($items) : NULL;
 		}
 		else
 		{
-			// Do the login
-			$this->auth();
-			$result = TRUE;
-			for ($i = 0, $max = xcache_count(XC_TYPE_VAR); $i < $max; $i++)
+			return ($items === FALSE) ? array() : $items;
+		}
+	}
+
+	/**
+	 * Get cache items by tag
+	 */
+	public function get_tag($tags)
+	{
+		Kohana_Log::add('debug', __('Cache: XCache driver does not support tags'));
+		return NULL;
+	}
+
+	/**
+	 * Delete cache item by key
+	 */
+	public function delete($keys)
+	{
+		foreach ($keys as $key)
+		{
+			if ( ! xcache_unset($key))
 			{
-				if (xcache_clear_cache(XC_TYPE_VAR, $i) !== NULL)
-				{
-					$result = FALSE;
-					break;
-				}
+				return FALSE;
 			}
-
-			// Undo the login
-			$this->auth(TRUE);
-			return $result;
 		}
 
 		return TRUE;
 	}
 
-	public function delete_expired()
+	/**
+	 * Delete cache items by tag
+	 */
+	public function delete_tag($tags)
 	{
-		return TRUE;
+		Kohana_Log::add('debug', __('Cache: XCache driver does not support tags'));
+		return NULL;
+	}
+
+	/**
+	 * Empty the cache
+	 */
+	public function delete_all()
+	{
+		$this->auth();
+		$result = TRUE;
+
+		for ($i = 0, $max = xcache_count(XC_TYPE_VAR); $i < $max; $i++)
+		{
+			if (xcache_clear_cache(XC_TYPE_VAR, $i) !== NULL)
+			{
+				$result = FALSE;
+				break;
+			}
+		}
+
+		// Undo the login
+		$this->auth(TRUE);
+
+		return $result;
 	}
 
 	private function auth($reverse = FALSE)
@@ -111,9 +154,8 @@ class Cache_Xcache_Driver implements Cache_Driver {
 					$backup[$key] = $value;
 				}
 
-				$_SERVER[$key] = Kohana::config('cache_xcache.'.$key);
+				$_SERVER[$key] = $this->config->{$key};
 			}
 		}
 	}
-
-} // End Cache Xcache Driver
+} // End Cache XCache Driver
diff --git a/system/libraries/drivers/Captcha.php b/system/libraries/drivers/Captcha.php
deleted file mode 100644
index a4343e19..00000000
--- a/system/libraries/drivers/Captcha.php
+++ /dev/null
@@ -1,227 +0,0 @@
-response = $this->generate_challenge();
-
-		// Store the correct Captcha response in a session
-		Event::add('system.post_controller', array($this, 'update_response_session'));
-	}
-
-	/**
-	 * Generate a new Captcha challenge.
-	 *
-	 * @return  string  the challenge answer
-	 */
-	abstract public function generate_challenge();
-
-	/**
-	 * Output the Captcha challenge.
-	 *
-	 * @param   boolean  html output
-	 * @return  mixed    the rendered Captcha (e.g. an image, riddle, etc.)
-	 */
-	abstract public function render($html);
-
-	/**
-	 * Stores the response for the current Captcha challenge in a session so it is available
-	 * on the next page load for Captcha::valid(). This method is called after controller
-	 * execution (in the system.post_controller event) in order not to overwrite itself too soon.
-	 *
-	 * @return  void
-	 */
-	public function update_response_session()
-	{
-		Session::instance()->set('captcha_response', sha1(strtoupper($this->response)));
-	}
-
-	/**
-	 * Validates a Captcha response from a user.
-	 *
-	 * @param   string   captcha response
-	 * @return  boolean
-	 */
-	public function valid($response)
-	{
-		return (sha1(strtoupper($response)) === Session::instance()->get('captcha_response'));
-	}
-
-	/**
-	 * Returns the image type.
-	 *
-	 * @param   string        filename
-	 * @return  string|FALSE  image type ("png", "gif" or "jpeg")
-	 */
-	public function image_type($filename)
-	{
-		switch (strtolower(substr(strrchr($filename, '.'), 1)))
-		{
-			case 'png':
-				return 'png';
-
-			case 'gif':
-				return 'gif';
-
-			case 'jpg':
-			case 'jpeg':
-				// Return "jpeg" and not "jpg" because of the GD2 function names
-				return 'jpeg';
-
-			default:
-				return FALSE;
-		}
-	}
-
-	/**
-	 * Creates an image resource with the dimensions specified in config.
-	 * If a background image is supplied, the image dimensions are used.
-	 *
-	 * @throws  Kohana_Exception  if no GD2 support
-	 * @param   string  path to the background image file
-	 * @return  void
-	 */
-	public function image_create($background = NULL)
-	{
-		// Check for GD2 support
-		if ( ! function_exists('imagegd2'))
-			throw new Kohana_Exception('captcha.requires_GD2');
-
-		// Create a new image (black)
-		$this->image = imagecreatetruecolor(Captcha::$config['width'], Captcha::$config['height']);
-
-		// Use a background image
-		if ( ! empty($background))
-		{
-			// Create the image using the right function for the filetype
-			$function = 'imagecreatefrom'.$this->image_type($background);
-			$this->background_image = $function($background);
-
-			// Resize the image if needed
-			if (imagesx($this->background_image) !== Captcha::$config['width']
-			    OR imagesy($this->background_image) !== Captcha::$config['height'])
-			{
-				imagecopyresampled
-				(
-					$this->image, $this->background_image, 0, 0, 0, 0,
-					Captcha::$config['width'], Captcha::$config['height'],
-					imagesx($this->background_image), imagesy($this->background_image)
-				);
-			}
-
-			// Free up resources
-			imagedestroy($this->background_image);
-		}
-	}
-
-	/**
-	 * Fills the background with a gradient.
-	 *
-	 * @param   resource  gd image color identifier for start color
-	 * @param   resource  gd image color identifier for end color
-	 * @param   string    direction: 'horizontal' or 'vertical', 'random' by default
-	 * @return  void
-	 */
-	public function image_gradient($color1, $color2, $direction = NULL)
-	{
-		$directions = array('horizontal', 'vertical');
-
-		// Pick a random direction if needed
-		if ( ! in_array($direction, $directions))
-		{
-			$direction = $directions[array_rand($directions)];
-
-			// Switch colors
-			if (mt_rand(0, 1) === 1)
-			{
-				$temp = $color1;
-				$color1 = $color2;
-				$color2 = $temp;
-			}
-		}
-
-		// Extract RGB values
-		$color1 = imagecolorsforindex($this->image, $color1);
-		$color2 = imagecolorsforindex($this->image, $color2);
-
-		// Preparations for the gradient loop
-		$steps = ($direction === 'horizontal') ? Captcha::$config['width'] : Captcha::$config['height'];
-
-		$r1 = ($color1['red'] - $color2['red']) / $steps;
-		$g1 = ($color1['green'] - $color2['green']) / $steps;
-		$b1 = ($color1['blue'] - $color2['blue']) / $steps;
-
-		if ($direction === 'horizontal')
-		{
-			$x1 =& $i;
-			$y1 = 0;
-			$x2 =& $i;
-			$y2 = Captcha::$config['height'];
-		}
-		else
-		{
-			$x1 = 0;
-			$y1 =& $i;
-			$x2 = Captcha::$config['width'];
-			$y2 =& $i;
-		}
-
-		// Execute the gradient loop
-		for ($i = 0; $i <= $steps; $i++)
-		{
-			$r2 = $color1['red'] - floor($i * $r1);
-			$g2 = $color1['green'] - floor($i * $g1);
-			$b2 = $color1['blue'] - floor($i * $b1);
-			$color = imagecolorallocate($this->image, $r2, $g2, $b2);
-
-			imageline($this->image, $x1, $y1, $x2, $y2, $color);
-		}
-	}
-
-	/**
-	 * Returns the img html element or outputs the image to the browser.
-	 *
-	 * @param   boolean  html output
-	 * @return  mixed    html string or void
-	 */
-	public function image_render($html)
-	{
-		// Output html element
-		if ($html)
-			return '
';
-
-		// Send the correct HTTP header
-		header('Content-Type: image/'.$this->image_type);
-
-		// Pick the correct output function
-		$function = 'image'.$this->image_type;
-		$function($this->image);
-
-		// Free up resources
-		imagedestroy($this->image);
-	}
-
-} // End Captcha Driver
\ No newline at end of file
diff --git a/system/libraries/drivers/Captcha/Alpha.php b/system/libraries/drivers/Captcha/Alpha.php
deleted file mode 100644
index 27795804..00000000
--- a/system/libraries/drivers/Captcha/Alpha.php
+++ /dev/null
@@ -1,92 +0,0 @@
-image
-		$this->image_create(Captcha::$config['background']);
-
-		// Add a random gradient
-		if (empty(Captcha::$config['background']))
-		{
-			$color1 = imagecolorallocate($this->image, mt_rand(0, 100), mt_rand(0, 100), mt_rand(0, 100));
-			$color2 = imagecolorallocate($this->image, mt_rand(0, 100), mt_rand(0, 100), mt_rand(0, 100));
-			$this->image_gradient($color1, $color2);
-		}
-
-		// Add a few random circles
-		for ($i = 0, $count = mt_rand(10, Captcha::$config['complexity'] * 3); $i < $count; $i++)
-		{
-			$color = imagecolorallocatealpha($this->image, mt_rand(0, 255), mt_rand(0, 255), mt_rand(0, 255), mt_rand(80, 120));
-			$size = mt_rand(5, Captcha::$config['height'] / 3);
-			imagefilledellipse($this->image, mt_rand(0, Captcha::$config['width']), mt_rand(0, Captcha::$config['height']), $size, $size, $color);
-		}
-
-		// Calculate character font-size and spacing
-		$default_size = min(Captcha::$config['width'], Captcha::$config['height'] * 2) / strlen($this->response);
-		$spacing = (int) (Captcha::$config['width'] * 0.9 / strlen($this->response));
-
-		// Background alphabetic character attributes
-		$color_limit = mt_rand(96, 160);
-		$chars = 'ABEFGJKLPQRTVY';
-
-		// Draw each Captcha character with varying attributes
-		for ($i = 0, $strlen = strlen($this->response); $i < $strlen; $i++)
-		{
-			// Use different fonts if available
-			$font = Captcha::$config['fontpath'].Captcha::$config['fonts'][array_rand(Captcha::$config['fonts'])];
-
-			$angle = mt_rand(-40, 20);
-			// Scale the character size on image height
-			$size = $default_size / 10 * mt_rand(8, 12);
-			$box = imageftbbox($size, $angle, $font, $this->response[$i]);
-
-			// Calculate character starting coordinates
-			$x = $spacing / 4 + $i * $spacing;
-			$y = Captcha::$config['height'] / 2 + ($box[2] - $box[5]) / 4;
-
-			// Draw captcha text character
-			// Allocate random color, size and rotation attributes to text
-			$color = imagecolorallocate($this->image, mt_rand(150, 255), mt_rand(200, 255), mt_rand(0, 255));
-
-			// Write text character to image
-			imagefttext($this->image, $size, $angle, $x, $y, $color, $font, $this->response[$i]);
-
-			// Draw "ghost" alphabetic character
-			$text_color = imagecolorallocatealpha($this->image, mt_rand($color_limit + 8, 255), mt_rand($color_limit + 8, 255), mt_rand($color_limit + 8, 255), mt_rand(70, 120));
-			$char = $chars[mt_rand(0, 14)];
-			imagettftext($this->image, $size * 2, mt_rand(-45, 45), ($x - (mt_rand(5, 10))), ($y + (mt_rand(5, 10))), $text_color, $font, $char);
-		}
-
-		// Output
-		return $this->image_render($html);
-	}
-
-} // End Captcha Alpha Driver Class
\ No newline at end of file
diff --git a/system/libraries/drivers/Captcha/Basic.php b/system/libraries/drivers/Captcha/Basic.php
deleted file mode 100644
index d212e72c..00000000
--- a/system/libraries/drivers/Captcha/Basic.php
+++ /dev/null
@@ -1,81 +0,0 @@
-image
-		$this->image_create(Captcha::$config['background']);
-
-		// Add a random gradient
-		if (empty(Captcha::$config['background']))
-		{
-			$color1 = imagecolorallocate($this->image, mt_rand(200, 255), mt_rand(200, 255), mt_rand(150, 255));
-			$color2 = imagecolorallocate($this->image, mt_rand(200, 255), mt_rand(200, 255), mt_rand(150, 255));
-			$this->image_gradient($color1, $color2);
-		}
-
-		// Add a few random lines
-		for ($i = 0, $count = mt_rand(5, Captcha::$config['complexity'] * 4); $i < $count; $i++)
-		{
-			$color = imagecolorallocatealpha($this->image, mt_rand(0, 255), mt_rand(0, 255), mt_rand(100, 255), mt_rand(50, 120));
-			imageline($this->image, mt_rand(0, Captcha::$config['width']), 0, mt_rand(0, Captcha::$config['width']), Captcha::$config['height'], $color);
-		}
-
-		// Calculate character font-size and spacing
-		$default_size = min(Captcha::$config['width'], Captcha::$config['height'] * 2) / (strlen($this->response) + 1);
-		$spacing = (int) (Captcha::$config['width'] * 0.9 / strlen($this->response));
-
-		// Draw each Captcha character with varying attributes
-		for ($i = 0, $strlen = strlen($this->response); $i < $strlen; $i++)
-		{
-			// Use different fonts if available
-			$font = Captcha::$config['fontpath'].Captcha::$config['fonts'][array_rand(Captcha::$config['fonts'])];
-
-			// Allocate random color, size and rotation attributes to text
-			$color = imagecolorallocate($this->image, mt_rand(0, 150), mt_rand(0, 150), mt_rand(0, 150));
-			$angle = mt_rand(-40, 20);
-
-			// Scale the character size on image height
-			$size = $default_size / 10 * mt_rand(8, 12);
-			$box = imageftbbox($size, $angle, $font, $this->response[$i]);
-
-			// Calculate character starting coordinates
-			$x = $spacing / 4 + $i * $spacing;
-			$y = Captcha::$config['height'] / 2 + ($box[2] - $box[5]) / 4;
-
-			// Write text character to image
-			imagefttext($this->image, $size, $angle, $x, $y, $color, $font, $this->response[$i]);
-		}
-
-		// Output
-		return $this->image_render($html);
-	}
-
-} // End Captcha Basic Driver Class
\ No newline at end of file
diff --git a/system/libraries/drivers/Captcha/Black.php b/system/libraries/drivers/Captcha/Black.php
deleted file mode 100644
index 6a2e2226..00000000
--- a/system/libraries/drivers/Captcha/Black.php
+++ /dev/null
@@ -1,72 +0,0 @@
-image_create(Captcha::$config['background']);
-
-		// Add random white/gray arcs, amount depends on complexity setting
-		$count = (Captcha::$config['width'] + Captcha::$config['height']) / 2;
-		$count = $count / 5 * min(10, Captcha::$config['complexity']);
-		for ($i = 0; $i < $count; $i++)
-		{
-			imagesetthickness($this->image, mt_rand(1, 2));
-			$color = imagecolorallocatealpha($this->image, 255, 255, 255, mt_rand(0, 120));
-			imagearc($this->image, mt_rand(-Captcha::$config['width'], Captcha::$config['width']), mt_rand(-Captcha::$config['height'], Captcha::$config['height']), mt_rand(-Captcha::$config['width'], Captcha::$config['width']), mt_rand(-Captcha::$config['height'], Captcha::$config['height']), mt_rand(0, 360), mt_rand(0, 360), $color);
-		}
-
-		// Use different fonts if available
-		$font = Captcha::$config['fontpath'].Captcha::$config['fonts'][array_rand(Captcha::$config['fonts'])];
-
-		// Draw the character's white shadows
-		$size = (int) min(Captcha::$config['height'] / 2, Captcha::$config['width'] * 0.8 / strlen($this->response));
-		$angle = mt_rand(-15 + strlen($this->response), 15 - strlen($this->response));
-		$x = mt_rand(1, Captcha::$config['width'] * 0.9 - $size * strlen($this->response));
-		$y = ((Captcha::$config['height'] - $size) / 2) + $size;
-		$color = imagecolorallocate($this->image, 255, 255, 255);
-		imagefttext($this->image, $size, $angle, $x + 1, $y + 1, $color, $font, $this->response);
-
-		// Add more shadows for lower complexities
-		(Captcha::$config['complexity'] < 10) and imagefttext($this->image, $size, $angle, $x - 1, $y - 1, $color, $font , $this->response);
-		(Captcha::$config['complexity'] < 8)  and imagefttext($this->image, $size, $angle, $x - 2, $y + 2, $color, $font , $this->response);
-		(Captcha::$config['complexity'] < 6)  and imagefttext($this->image, $size, $angle, $x + 2, $y - 2, $color, $font , $this->response);
-		(Captcha::$config['complexity'] < 4)  and imagefttext($this->image, $size, $angle, $x + 3, $y + 3, $color, $font , $this->response);
-		(Captcha::$config['complexity'] < 2)  and imagefttext($this->image, $size, $angle, $x - 3, $y - 3, $color, $font , $this->response);
-
-		// Finally draw the foreground characters
-		$color = imagecolorallocate($this->image, 0, 0, 0);
-		imagefttext($this->image, $size, $angle, $x, $y, $color, $font, $this->response);
-
-		// Output
-		return $this->image_render($html);
-	}
-
-} // End Captcha Black Driver Class
\ No newline at end of file
diff --git a/system/libraries/drivers/Captcha/Math.php b/system/libraries/drivers/Captcha/Math.php
deleted file mode 100644
index 4ac20248..00000000
--- a/system/libraries/drivers/Captcha/Math.php
+++ /dev/null
@@ -1,61 +0,0 @@
-math_exercice = implode(' + ', $numbers).' = ';
-
-		// Return the answer
-		return array_sum($numbers);
-	}
-
-	/**
-	 * Outputs the Captcha riddle.
-	 *
-	 * @param   boolean  html output
-	 * @return  mixed
-	 */
-	public function render($html)
-	{
-		return $this->math_exercice;
-	}
-
-} // End Captcha Math Driver Class
\ No newline at end of file
diff --git a/system/libraries/drivers/Captcha/Riddle.php b/system/libraries/drivers/Captcha/Riddle.php
deleted file mode 100644
index 765eeaad..00000000
--- a/system/libraries/drivers/Captcha/Riddle.php
+++ /dev/null
@@ -1,47 +0,0 @@
-riddle = $riddle[0];
-
-		// Return the answer
-		return $riddle[1];
-	}
-
-	/**
-	 * Outputs the Captcha riddle.
-	 *
-	 * @param   boolean  html output
-	 * @return  mixed
-	 */
-	public function render($html)
-	{
-		return $this->riddle;
-	}
-
-} // End Captcha Riddle Driver Class
\ No newline at end of file
diff --git a/system/libraries/drivers/Captcha/Word.php b/system/libraries/drivers/Captcha/Word.php
deleted file mode 100644
index 856bd9b4..00000000
--- a/system/libraries/drivers/Captcha/Word.php
+++ /dev/null
@@ -1,37 +0,0 @@
-cache_lifetime = $cache_setting;
+			// Restore the cached configuration
+			$this->config = $this->load_cache();
+
+			if (count($this->config) > 0)
+				$this->loaded = TRUE;
+
+			// Add the save cache method to system.shutshut event
+			Event::add('system.shutdown', array($this, 'save_cache'));
+		}
+
+	}
+
+	/**
+	 * Gets a value from config. If required is TRUE
+	 * then get will throw an exception if value cannot
+	 * be loaded.
+	 *
+	 * @param   string       key  the setting to get
+	 * @param   bool         slash  remove trailing slashes
+	 * @param   bool         required  is setting required?
+	 * @return  mixed
+	 * @access  public
+	 */
+	public function get($key, $slash = FALSE, $required = FALSE)
+	{
+		// Get the group name from the key
+		$group = explode('.', $key, 2);
+		$group = $group[0];
+
+		// Check for existing value and load it dynamically if required
+		if ( ! isset($this->config[$group]))
+			$this->config[$group] = $this->load($group, $required);
+
+		// Get the value of the key string
+		$value = Kohana::key_string($this->config, $key);
+
+		if ($slash === TRUE AND is_string($value) AND $value !== '')
+		{
+			// Force the value to end with "/"
+			$value = rtrim($value, '/').'/';
+		}
+
+		if (($required === TRUE) AND ($value === null))
+			throw new Kohana_Config_Exception('Value not found in config driver');
+
+		$this->loaded = TRUE;
+		return $value;
+	}
+
+	/**
+	 * Sets a new value to the configuration
+	 *
+	 * @param   string       key
+	 * @param   mixed        value
+	 * @return  bool
+	 * @access  public
+	 */
+	public function set($key, $value)
+	{
+		// Do this to make sure that the config array is already loaded
+		$this->get($key);
+
+		if (substr($key, 0, 7) === 'routes.')
+		{
+			// Routes cannot contain sub keys due to possible dots in regex
+			$keys = explode('.', $key, 2);
+		}
+		else
+		{
+			// Convert dot-noted key string to an array
+			$keys = explode('.', $key);
+		}
+
+		// Used for recursion
+		$conf =& $this->config;
+		$last = count($keys) - 1;
+
+		foreach ($keys as $i => $k)
+		{
+			if ($i === $last)
+			{
+				$conf[$k] = $value;
+			}
+			else
+			{
+				$conf =& $conf[$k];
+			}
+		}
+
+		if (substr($key,0,12) === 'core.modules')
+		{
+			// Reprocess the include paths
+			Kohana::include_paths(TRUE);
+		}
+
+		// Set config to changed
+		return $this->changed = TRUE;
+	}
+
+	/**
+	 * Clear the configuration
+	 *
+	 * @param   string       group
+	 * @return  bool
+	 * @access  public
+	 */
+	public function clear($group)
+	{
+		// Remove the group from config
+		unset($this->config[$group]);
+
+		// Set config to changed
+		return $this->changed = TRUE;
+	}
+
+	/**
+	 * Checks whether the setting exists in
+	 * config
+	 *
+	 * @param   string $key
+	 * @return  bool
+	 * @access  public
+	 */
+	public function setting_exists($key)
+	{
+		return $this->get($key) === NULL;
+	}
+
+	/**
+	 * Loads a configuration group based on the setting
+	 *
+	 * @param   string       group
+	 * @param   bool         required
+	 * @return  array
+	 * @access  public
+	 * @abstract
+	 */
+	abstract public function load($group, $required = FALSE);
+
+	/**
+	 * Loads the cached version of this configuration driver
+	 *
+	 * @return  array
+	 * @access  public
+	 */
+	public function load_cache()
+	{
+		// Load the cache for this configuration
+		$cached_config = Kohana::cache($this->cache_name, $this->cache_lifetime);
+
+		// If the configuration wasn't loaded from the cache
+		if ($cached_config === NULL)
+			$cached_config = array();
+
+		// Return the cached config
+		return $cached_config;
+	}
+
+	/**
+	 * Saves a cached version of this configuration driver
+	 *
+	 * @return  bool
+	 * @access  public
+	 */
+	public function save_cache()
+	{
+		// If this configuration has changed
+		if ($this->get('core.internal_cache') !== FALSE AND $this->changed)
+		{
+			$data = $this->config;
+
+			// Save the cache
+			return Kohana::cache_save($this->cache_name, $data, $this->cache_lifetime);
+		}
+
+		return TRUE;
+	}
+} // End Kohana_Config_Driver
\ No newline at end of file
diff --git a/system/libraries/drivers/Config/Array.php b/system/libraries/drivers/Config/Array.php
new file mode 100644
index 00000000..b2ca19ba
--- /dev/null
+++ b/system/libraries/drivers/Config/Array.php
@@ -0,0 +1,83 @@
+escape_table($table).' WHERE '.implode(' ', $where);
-	}
-
-	/**
-	 * Builds an UPDATE query.
-	 *
-	 * @param   string  table name
-	 * @param   array   key => value pairs
-	 * @param   array   where clause
-	 * @return  string
-	 */
-	public function update($table, $values, $where)
-	{
-		foreach ($values as $key => $val)
-		{
-			$valstr[] = $this->escape_column($key).' = '.$val;
-		}
-		return 'UPDATE '.$this->escape_table($table).' SET '.implode(', ', $valstr).' WHERE '.implode(' ',$where);
-	}
-
-	/**
-	 * Set the charset using 'SET NAMES '.
-	 *
-	 * @param  string  character set to use
-	 */
-	public function set_charset($charset)
-	{
-		throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__);
-	}
-
-	/**
-	 * Wrap the tablename in backticks, has support for: table.field syntax.
-	 *
-	 * @param   string  table name
-	 * @return  string
-	 */
-	abstract public function escape_table($table);
-
-	/**
-	 * Escape a column/field name, has support for special commands.
-	 *
-	 * @param   string  column name
-	 * @return  string
-	 */
-	abstract public function escape_column($column);
-
-	/**
-	 * Builds a WHERE portion of a query.
-	 *
-	 * @param   mixed    key
-	 * @param   string   value
-	 * @param   string   type
-	 * @param   int      number of where clauses
-	 * @param   boolean  escape the value
-	 * @return  string
-	 */
-	public function where($key, $value, $type, $num_wheres, $quote)
-	{
-		$prefix = ($num_wheres == 0) ? '' : $type;
-
-		if ($quote === -1)
-		{
-			$value = '';
-		}
-		else
-		{
-			if ($value === NULL)
-			{
-				if ( ! $this->has_operator($key))
-				{
-					$key .= ' IS';
-				}
-
-				$value = ' NULL';
-			}
-			elseif (is_bool($value))
-			{
-				if ( ! $this->has_operator($key))
-				{
-					$key .= ' =';
-				}
-
-				$value = ($value == TRUE) ? ' TRUE' : ' FALSE';
-			}
-			else
-			{
-				if ( ! $this->has_operator($key) AND ! empty($key))
-				{
-					$key = $this->escape_column($key).' =';
-				}
-				else
-				{
-					preg_match('/^(.+?)([<>!=]+|\bIS(?:\s+NULL))\s*$/i', $key, $matches);
-					if (isset($matches[1]) AND isset($matches[2]))
-					{
-						$key = $this->escape_column(trim($matches[1])).' '.trim($matches[2]);
-					}
-				}
-
-				$value = ' '.(($quote == TRUE) ? $this->escape($value) : $value);
-			}
-		}
-
-		return $prefix.$key.$value;
-	}
-
-	/**
-	 * Builds a LIKE portion of a query.
-	 *
-	 * @param   mixed    field name
-	 * @param   string   value to match with field
-	 * @param   boolean  add wildcards before and after the match
-	 * @param   string   clause type (AND or OR)
-	 * @param   int      number of likes
-	 * @return  string
-	 */
-	public function like($field, $match, $auto, $type, $num_likes)
-	{
-		$prefix = ($num_likes == 0) ? '' : $type;
-
-		$match = $this->escape_str($match);
-
-		if ($auto === TRUE)
-		{
-			// Add the start and end quotes
-			$match = '%'.str_replace('%', '\\%', $match).'%';
-		}
-
-		return $prefix.' '.$this->escape_column($field).' LIKE \''.$match . '\'';
-	}
-
-	/**
-	 * Builds a NOT LIKE portion of a query.
-	 *
-	 * @param   mixed   field name
-	 * @param   string  value to match with field
-	 * @param   string  clause type (AND or OR)
-	 * @param   int     number of likes
-	 * @return  string
-	 */
-	public function notlike($field, $match, $auto, $type, $num_likes)
-	{
-		$prefix = ($num_likes == 0) ? '' : $type;
-
-		$match = $this->escape_str($match);
-
-		if ($auto === TRUE)
-		{
-			// Add the start and end quotes
-			$match = '%'.$match.'%';
-		}
-
-		return $prefix.' '.$this->escape_column($field).' NOT LIKE \''.$match.'\'';
-	}
-
-	/**
-	 * Builds a REGEX portion of a query.
-	 *
-	 * @param   string   field name
-	 * @param   string   value to match with field
-	 * @param   string   clause type (AND or OR)
-	 * @param   integer  number of regexes
-	 * @return  string
-	 */
-	public function regex($field, $match, $type, $num_regexs)
-	{
-		throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__);
-	}
-
-	/**
-	 * Builds a NOT REGEX portion of a query.
-	 *
-	 * @param   string   field name
-	 * @param   string   value to match with field
-	 * @param   string   clause type (AND or OR)
-	 * @param   integer  number of regexes
-	 * @return  string
-	 */
-	public function notregex($field, $match, $type, $num_regexs)
-	{
-		throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__);
-	}
-
-	/**
-	 * Builds an INSERT query.
-	 *
-	 * @param   string  table name
-	 * @param   array   keys
-	 * @param   array   values
-	 * @return  string
-	 */
-	public function insert($table, $keys, $values)
-	{
-		// Escape the column names
-		foreach ($keys as $key => $value)
-		{
-			$keys[$key] = $this->escape_column($value);
-		}
-		return 'INSERT INTO '.$this->escape_table($table).' ('.implode(', ', $keys).') VALUES ('.implode(', ', $values).')';
-	}
-
-	/**
-	 * Builds a MERGE portion of a query.
-	 *
-	 * @param   string  table name
-	 * @param   array   keys
-	 * @param   array   values
-	 * @return  string
-	 */
-	public function merge($table, $keys, $values)
-	{
-		throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__);
-	}
-
-	/**
-	 * Builds a LIMIT portion of a query.
-	 *
-	 * @param   integer  limit
-	 * @param   integer  offset
-	 * @return  string
-	 */
-	abstract public function limit($limit, $offset = 0);
-
-	/**
-	 * Creates a prepared statement.
-	 *
-	 * @param   string  SQL query
-	 * @return  Database_Stmt
-	 */
-	public function stmt_prepare($sql = '')
-	{
-		throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__);
-	}
-
-	/**
-	 *  Compiles the SELECT statement.
-	 *  Generates a query string based on which functions were used.
-	 *  Should not be called directly, the get() function calls it.
-	 *
-	 * @param   array   select query values
-	 * @return  string
-	 */
-	abstract public function compile_select($database);
-
-	/**
-	 * Determines if the string has an arithmetic operator in it.
-	 *
-	 * @param   string   string to check
-	 * @return  boolean
-	 */
-	public function has_operator($str)
-	{
-		return (bool) preg_match('/[<>!=]|\sIS(?:\s+NOT\s+)?\b|BETWEEN/i', trim($str));
-	}
-
-	/**
-	 * Escapes any input value.
-	 *
-	 * @param   mixed   value to escape
-	 * @return  string
-	 */
-	public function escape($value)
-	{
-		if ( ! $this->db_config['escape'])
-			return $value;
-
-		switch (gettype($value))
-		{
-			case 'string':
-				$value = '\''.$this->escape_str($value).'\'';
-			break;
-			case 'boolean':
-				$value = ($value == TRUE) ? 'TRUE' : 'FALSE';
-			break;
-			case 'double':
-				// Convert to non-locale aware float to prevent possible commas
-				$value = sprintf('%F', $value);
-			break;
-			default:
-				$value = ($value === NULL) ? 'NULL' : $value;
-			break;
-		}
-
-		return (string) $value;
-	}
-
-	/**
-	 * Escapes a string for a query.
-	 *
-	 * @param   mixed   value to escape
-	 * @return  string
-	 */
-	abstract public function escape_str($str);
-
-	/**
-	 * Lists all tables in the database.
-	 *
-	 * @return  array
-	 */
-	abstract public function list_tables();
-
-	/**
-	 * Lists all fields in a table.
-	 *
-	 * @param   string  table name
-	 * @return  array
-	 */
-	abstract function list_fields($table);
-
-	/**
-	 * Returns the last database error.
-	 *
-	 * @return  string
-	 */
-	abstract public function show_error();
-
-	/**
-	 * Returns field data about a table.
-	 *
-	 * @param   string  table name
-	 * @return  array
-	 */
-	abstract public function field_data($table);
-
-	/**
-	 * Fetches SQL type information about a field, in a generic format.
-	 *
-	 * @param   string  field datatype
-	 * @return  array
-	 */
-	protected function sql_type($str)
-	{
-		static $sql_types;
-
-		if ($sql_types === NULL)
-		{
-			// Load SQL data types
-			$sql_types = Kohana::config('sql_types');
-		}
-
-		$str = strtolower(trim($str));
-
-		if (($open  = strpos($str, '(')) !== FALSE)
-		{
-			// Find closing bracket
-			$close = strpos($str, ')', $open) - 1;
-
-			// Find the type without the size
-			$type = substr($str, 0, $open);
-		}
-		else
-		{
-			// No length
-			$type = $str;
-		}
-
-		empty($sql_types[$type]) and exit
-		(
-			'Unknown field type: '.$type.'. '.
-			'Please report this: http://trac.kohanaphp.com/newticket'
-		);
-
-		// Fetch the field definition
-		$field = $sql_types[$type];
-
-		switch ($field['type'])
-		{
-			case 'string':
-			case 'float':
-				if (isset($close))
-				{
-					// Add the length to the field info
-					$field['length'] = substr($str, $open + 1, $close - $open);
-				}
-			break;
-			case 'int':
-				// Add unsigned value
-				$field['unsigned'] = (strpos($str, 'unsigned') !== FALSE);
-			break;
-		}
-
-		return $field;
-	}
-
-	/**
-	 * Clears the internal query cache.
-	 *
-	 * @param  string  SQL query
-	 */
-	public function clear_cache($sql = NULL)
-	{
-		if (empty($sql))
-		{
-			$this->query_cache = array();
-		}
-		else
-		{
-			unset($this->query_cache[$this->query_hash($sql)]);
-		}
-
-		Kohana::log('debug', 'Database cache cleared: '.get_class($this));
-	}
-
-	/**
-	 * Creates a hash for an SQL query string. Replaces newlines with spaces,
-	 * trims, and hashes.
-	 *
-	 * @param   string  SQL query
-	 * @return  string
-	 */
-	protected function query_hash($sql)
-	{
-		return sha1(str_replace("\n", ' ', trim($sql)));
-	}
-
-} // End Database Driver Interface
-
-/**
- * Database_Result
- *
- */
-abstract class Database_Result implements ArrayAccess, Iterator, Countable {
-
-	// Result resource, insert id, and SQL
-	protected $result;
-	protected $insert_id;
-	protected $sql;
-
-	// Current and total rows
-	protected $current_row = 0;
-	protected $total_rows  = 0;
-
-	// Fetch function and return type
-	protected $fetch_type;
-	protected $return_type;
-
-	/**
-	 * Returns the SQL used to fetch the result.
-	 *
-	 * @return  string
-	 */
-	public function sql()
-	{
-		return $this->sql;
-	}
-
-	/**
-	 * Returns the insert id from the result.
-	 *
-	 * @return  mixed
-	 */
-	public function insert_id()
-	{
-		return $this->insert_id;
-	}
-
-	/**
-	 * Prepares the query result.
-	 *
-	 * @param   boolean   return rows as objects
-	 * @param   mixed     type
-	 * @return  Database_Result
-	 */
-	abstract function result($object = TRUE, $type = FALSE);
-
-	/**
-	 * Builds an array of query results.
-	 *
-	 * @param   boolean   return rows as objects
-	 * @param   mixed     type
-	 * @return  array
-	 */
-	abstract function result_array($object = NULL, $type = FALSE);
-
-	/**
-	 * Gets the fields of an already run query.
-	 *
-	 * @return  array
-	 */
-	abstract public function list_fields();
-
-	/**
-	 * Seek to an offset in the results.
-	 *
-	 * @return  boolean
-	 */
-	abstract public function seek($offset);
-
-	/**
-	 * Countable: count
-	 */
-	public function count()
-	{
-		return $this->total_rows;
-	}
-
-	/**
-	 * ArrayAccess: offsetExists
-	 */
-	public function offsetExists($offset)
-	{
-		if ($this->total_rows > 0)
-		{
-			$min = 0;
-			$max = $this->total_rows - 1;
-
-			return ! ($offset < $min OR $offset > $max);
-		}
-
-		return FALSE;
-	}
-
-	/**
-	 * ArrayAccess: offsetGet
-	 */
-	public function offsetGet($offset)
-	{
-		if ( ! $this->seek($offset))
-			return FALSE;
-
-		// Return the row by calling the defined fetching callback
-		return call_user_func($this->fetch_type, $this->result, $this->return_type);
-	}
-
-	/**
-	 * ArrayAccess: offsetSet
-	 *
-	 * @throws  Kohana_Database_Exception
-	 */
-	final public function offsetSet($offset, $value)
-	{
-		throw new Kohana_Database_Exception('database.result_read_only');
-	}
-
-	/**
-	 * ArrayAccess: offsetUnset
-	 *
-	 * @throws  Kohana_Database_Exception
-	 */
-	final public function offsetUnset($offset)
-	{
-		throw new Kohana_Database_Exception('database.result_read_only');
-	}
-
-	/**
-	 * Iterator: current
-	 */
-	public function current()
-	{
-		return $this->offsetGet($this->current_row);
-	}
-
-	/**
-	 * Iterator: key
-	 */
-	public function key()
-	{
-		return $this->current_row;
-	}
-
-	/**
-	 * Iterator: next
-	 */
-	public function next()
-	{
-		++$this->current_row;
-		return $this;
-	}
-
-	/**
-	 * Iterator: prev
-	 */
-	public function prev()
-	{
-		--$this->current_row;
-		return $this;
-	}
-
-	/**
-	 * Iterator: rewind
-	 */
-	public function rewind()
-	{
-		$this->current_row = 0;
-		return $this;
-	}
-
-	/**
-	 * Iterator: valid
-	 */
-	public function valid()
-	{
-		return $this->offsetExists($this->current_row);
-	}
-
-} // End Database Result Interface
diff --git a/system/libraries/drivers/Database/Mssql.php b/system/libraries/drivers/Database/Mssql.php
deleted file mode 100644
index 8b5ed50b..00000000
--- a/system/libraries/drivers/Database/Mssql.php
+++ /dev/null
@@ -1,462 +0,0 @@
-db_config = $config;
-
-		Kohana::log('debug', 'MSSQL Database Driver Initialized');
-	}
-
-	/**
-	 * Closes the database connection.
-	 */
-	public function __destruct()
-	{
-		is_resource($this->link) and mssql_close($this->link);
-	}
-
-	/**
-	 * Make the connection
-	 *
-	 * @return return connection
-	 */
-	public function connect()
-	{
-		// Check if link already exists
-		if (is_resource($this->link))
-			return $this->link;
-
-		// Import the connect variables
-		extract($this->db_config['connection']);
-
-		// Persistent connections enabled?
-		$connect = ($this->db_config['persistent'] == TRUE) ? 'mssql_pconnect' : 'mssql_connect';
-
-		// Build the connection info
-		$host = isset($host) ? $host : $socket;
-
-		// Windows uses a comma instead of a colon
-		$port = (isset($port) AND is_string($port)) ? (KOHANA_IS_WIN ? ',' : ':').$port : '';
-
-		// Make the connection and select the database
-		if (($this->link = $connect($host.$port, $user, $pass, TRUE)) AND mssql_select_db($database, $this->link))
-		{
-			/* This is being removed so I can use it, will need to come up with a more elegant workaround in the future...
-			 *
-			if ($charset = $this->db_config['character_set'])
-			{
-				$this->set_charset($charset);
-			}
-			*/
-
-			// Clear password after successful connect
-			$this->db_config['connection']['pass'] = NULL;
-
-			return $this->link;
-		}
-
-		return FALSE;
-	}
-
-	public function query($sql)
-	{
-		// Only cache if it's turned on, and only cache if it's not a write statement
-		if ($this->db_config['cache'] AND ! preg_match('#\b(?:INSERT|UPDATE|REPLACE|SET)\b#i', $sql))
-		{
-			$hash = $this->query_hash($sql);
-
-			if ( ! isset($this->query_cache[$hash]))
-			{
-				// Set the cached object
-				$this->query_cache[$hash] = new Mssql_Result(mssql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql);
-			}
-			else
-			{
-				// Rewind cached result
-				$this->query_cache[$hash]->rewind();
-			}
-
-			// Return the cached query
-			return $this->query_cache[$hash];
-		}
-
-		return new Mssql_Result(mssql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql);
-	}
-
-	public function escape_table($table)
-	{
-		if (stripos($table, ' AS ') !== FALSE)
-		{
-			// Force 'AS' to uppercase
-			$table = str_ireplace(' AS ', ' AS ', $table);
-
-			// Runs escape_table on both sides of an AS statement
-			$table = array_map(array($this, __FUNCTION__), explode(' AS ', $table));
-
-			// Re-create the AS statement
-			return implode(' AS ', $table);
-		}
-		return '['.str_replace('.', '[.]', $table).']';
-	}
-
-	public function escape_column($column)
-	{
-		if (!$this->db_config['escape'])
-			return $column;
-
-		if ($column == '*')
-			return $column;
-
-		// This matches any functions we support to SELECT.
-		if ( preg_match('/(avg|count|sum|max|min)\(\s*(.*)\s*\)(\s*as\s*(.+)?)?/i', $column, $matches))
-		{
-			if ( count($matches) == 3)
-			{
-				return $matches[1].'('.$this->escape_column($matches[2]).')';
-			}
-			else if ( count($matches) == 5)
-			{
-				return $matches[1].'('.$this->escape_column($matches[2]).') AS '.$this->escape_column($matches[2]);
-			}
-		}
-
-		// This matches any modifiers we support to SELECT.
-		if ( ! preg_match('/\b(?:rand|all|distinct(?:row)?|high_priority|sql_(?:small_result|b(?:ig_result|uffer_result)|no_cache|ca(?:che|lc_found_rows)))\s/i', $column))
-		{
-			if (stripos($column, ' AS ') !== FALSE)
-			{
-				// Force 'AS' to uppercase
-				$column = str_ireplace(' AS ', ' AS ', $column);
-
-				// Runs escape_column on both sides of an AS statement
-				$column = array_map(array($this, __FUNCTION__), explode(' AS ', $column));
-
-				// Re-create the AS statement
-				return implode(' AS ', $column);
-			}
-
-			return preg_replace('/[^.*]+/', '[$0]', $column);
-		}
-
-		$parts = explode(' ', $column);
-		$column = '';
-
-		for ($i = 0, $c = count($parts); $i < $c; $i++)
-		{
-			// The column is always last
-			if ($i == ($c - 1))
-			{
-				$column .= preg_replace('/[^.*]+/', '[$0]', $parts[$i]);
-			}
-			else // otherwise, it's a modifier
-			{
-				$column .= $parts[$i].' ';
-			}
-		}
-		return $column;
-	}
-
-	/**
-	 * Limit in SQL Server 2000 only uses the keyword
-	 * 'TOP'; 2007 may have an offset keyword, but
-	 * I am unsure - for pagination style limit,offset
-	 * functionality, a fancy query needs to be built.
-	 *
-	 * @param unknown_type $limit
-	 * @return unknown
-	 */
-	public function limit($limit, $offset=null)
-	{
-		return 'TOP '.$limit;
-	}
-
-	public function compile_select($database)
-	{
-		$sql = ($database['distinct'] == TRUE) ? 'SELECT DISTINCT ' : 'SELECT ';
-		$sql .= (count($database['select']) > 0) ? implode(', ', $database['select']) : '*';
-
-		if (count($database['from']) > 0)
-		{
-			// Escape the tables
-			$froms = array();
-			foreach ($database['from'] as $from)
-				$froms[] = $this->escape_column($from);
-			$sql .= "\nFROM ";
-			$sql .= implode(', ', $froms);
-		}
-
-		if (count($database['join']) > 0)
-		{
-			foreach($database['join'] AS $join)
-			{
-				$sql .= "\n".$join['type'].'JOIN '.implode(', ', $join['tables']).' ON '.$join['conditions'];
-			}
-		}
-
-		if (count($database['where']) > 0)
-		{
-			$sql .= "\nWHERE ";
-		}
-
-		$sql .= implode("\n", $database['where']);
-
-		if (count($database['groupby']) > 0)
-		{
-			$sql .= "\nGROUP BY ";
-			$sql .= implode(', ', $database['groupby']);
-		}
-
-		if (count($database['having']) > 0)
-		{
-			$sql .= "\nHAVING ";
-			$sql .= implode("\n", $database['having']);
-		}
-
-		if (count($database['orderby']) > 0)
-		{
-			$sql .= "\nORDER BY ";
-			$sql .= implode(', ', $database['orderby']);
-		}
-
-		if (is_numeric($database['limit']))
-		{
-			$sql .= "\n";
-			$sql .= $this->limit($database['limit']);
-		}
-
-		return $sql;
-	}
-
-	public function escape_str($str)
-	{
-		if (!$this->db_config['escape'])
-			return $str;
-
-		is_resource($this->link) or $this->connect();
-		//mssql_real_escape_string($str, $this->link); <-- this function doesn't exist
-
-		$characters = array('/\x00/', '/\x1a/', '/\n/', '/\r/', '/\\\/', '/\'/');
-		$replace    = array('\\\x00', '\\x1a', '\\n', '\\r', '\\\\', "''");
-		return preg_replace($characters, $replace, $str);
-	}
-
-	public function list_tables()
-	{
-		$sql    = 'SHOW TABLES FROM ['.$this->db_config['connection']['database'].']';
-		$result = $this->query($sql)->result(FALSE, MSSQL_ASSOC);
-
-		$retval = array();
-		foreach ($result as $row)
-		{
-			$retval[] = current($row);
-		}
-
-		return $retval;
-	}
-
-	public function show_error()
-	{
-		return mssql_get_last_message($this->link);
-	}
-
-	public function list_fields($table)
-	{
-		$result = array();
-
-		foreach ($this->field_data($table) as $row)
-		{
-			// Make an associative array
-			$result[$row->Field] = $this->sql_type($row->Type);
-		}
-
-		return $result;
-	}
-
-	public function field_data($table)
-	{
-		$query = $this->query("SELECT COLUMN_NAME AS Field, DATA_TYPE as Type  FROM INFORMATION_SCHEMA.Columns WHERE TABLE_NAME = '".$this->escape_table($table)."'", $this->link);
-
-		return $query->result_array(TRUE);
-	}
-}
-
-/**
- * MSSQL Result
- */
-class Mssql_Result extends Database_Result {
-
-	// Fetch function and return type
-	protected $fetch_type  = 'mssql_fetch_object';
-	protected $return_type = MSSQL_ASSOC;
-
-	/**
-	 * Sets up the result variables.
-	 *
-	 * @param  resource  query result
-	 * @param  resource  database link
-	 * @param  boolean   return objects or arrays
-	 * @param  string    SQL query that was run
-	 */
-	public function __construct($result, $link, $object = TRUE, $sql)
-	{
-		$this->result = $result;
-
-		// If the query is a resource, it was a SELECT, SHOW, DESCRIBE, EXPLAIN query
-		if (is_resource($result))
-		{
-			$this->current_row = 0;
-			$this->total_rows  = mssql_num_rows($this->result);
-			$this->fetch_type = ($object === TRUE) ? 'mssql_fetch_object' : 'mssql_fetch_array';
-		}
-		elseif (is_bool($result))
-		{
-			if ($result == FALSE)
-			{
-				// SQL error
-				throw new Kohana_Database_Exception('database.error', mssql_get_last_message($link).' - '.$sql);
-			}
-			else
-			{
-				// Its an DELETE, INSERT, REPLACE, or UPDATE querys
-				$last_id          = mssql_query('SELECT @@IDENTITY AS last_id', $link);
-				$result           = mssql_fetch_assoc($last_id);
-				$this->insert_id  = $result['last_id'];
-				$this->total_rows = mssql_rows_affected($link);
-			}
-		}
-
-		// Set result type
-		$this->result($object);
-
-		// Store the SQL
-		$this->sql = $sql;
-	}
-
-	/**
-	 * Destruct, the cleanup crew!
-	 */
-	public function __destruct()
-	{
-		if (is_resource($this->result))
-		{
-			mssql_free_result($this->result);
-		}
-	}
-
-	public function result($object = TRUE, $type = MSSQL_ASSOC)
-	{
-		$this->fetch_type = ((bool) $object) ? 'mssql_fetch_object' : 'mssql_fetch_array';
-
-		// This check has to be outside the previous statement, because we do not
-		// know the state of fetch_type when $object = NULL
-		// NOTE - The class set by $type must be defined before fetching the result,
-		// autoloading is disabled to save a lot of stupid overhead.
-		if ($this->fetch_type == 'mssql_fetch_object')
-		{
-			$this->return_type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
-		}
-		else
-		{
-			$this->return_type = $type;
-		}
-
-		return $this;
-	}
-
-	public function as_array($object = NULL, $type = MSSQL_ASSOC)
-	{
-		return $this->result_array($object, $type);
-	}
-
-	public function result_array($object = NULL, $type = MSSQL_ASSOC)
-	{
-		$rows = array();
-
-		if (is_string($object))
-		{
-			$fetch = $object;
-		}
-		elseif (is_bool($object))
-		{
-			if ($object === TRUE)
-			{
-				$fetch = 'mssql_fetch_object';
-
-				// NOTE - The class set by $type must be defined before fetching the result,
-				// autoloading is disabled to save a lot of stupid overhead.
-				$type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
-			}
-			else
-			{
-				$fetch = 'mssql_fetch_array';
-			}
-		}
-		else
-		{
-			// Use the default config values
-			$fetch = $this->fetch_type;
-
-			if ($fetch == 'mssql_fetch_object')
-			{
-				$type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
-			}
-		}
-
-		if (mssql_num_rows($this->result))
-		{
-			// Reset the pointer location to make sure things work properly
-			mssql_data_seek($this->result, 0);
-
-			while ($row = $fetch($this->result, $type))
-			{
-				$rows[] = $row;
-			}
-		}
-
-		return isset($rows) ? $rows : array();
-	}
-
-	public function list_fields()
-	{
-		$field_names = array();
-		while ($field = mssql_fetch_field($this->result))
-		{
-			$field_names[] = $field->name;
-		}
-
-		return $field_names;
-	}
-
-	public function seek($offset)
-	{
-		if ( ! $this->offsetExists($offset))
-			return FALSE;
-
-		return mssql_data_seek($this->result, $offset);
-	}
-
-} // End mssql_Result Class
diff --git a/system/libraries/drivers/Database/Mysql.php b/system/libraries/drivers/Database/Mysql.php
deleted file mode 100644
index d5222f50..00000000
--- a/system/libraries/drivers/Database/Mysql.php
+++ /dev/null
@@ -1,496 +0,0 @@
-db_config = $config;
-
-		Kohana::log('debug', 'MySQL Database Driver Initialized');
-	}
-
-	/**
-	 * Closes the database connection.
-	 */
-	public function __destruct()
-	{
-		is_resource($this->link) and mysql_close($this->link);
-	}
-
-	public function connect()
-	{
-		// Check if link already exists
-		if (is_resource($this->link))
-			return $this->link;
-
-		// Import the connect variables
-		extract($this->db_config['connection']);
-
-		// Persistent connections enabled?
-		$connect = ($this->db_config['persistent'] == TRUE) ? 'mysql_pconnect' : 'mysql_connect';
-
-		// Build the connection info
-		$host = isset($host) ? $host : $socket;
-		$port = isset($port) ? ':'.$port : '';
-
-		// Make the connection and select the database
-		if (($this->link = $connect($host.$port, $user, $pass, TRUE)) AND mysql_select_db($database, $this->link))
-		{
-			if ($charset = $this->db_config['character_set'])
-			{
-				$this->set_charset($charset);
-			}
-
-			// Clear password after successful connect
-			$this->db_config['connection']['pass'] = NULL;
-
-			return $this->link;
-		}
-
-		return FALSE;
-	}
-
-	public function query($sql)
-	{
-		// Only cache if it's turned on, and only cache if it's not a write statement
-		if ($this->db_config['cache'] AND ! preg_match('#\b(?:INSERT|UPDATE|REPLACE|SET|DELETE|TRUNCATE)\b#i', $sql))
-		{
-			$hash = $this->query_hash($sql);
-
-			if ( ! isset($this->query_cache[$hash]))
-			{
-				// Set the cached object
-				$this->query_cache[$hash] = new Mysql_Result(mysql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql);
-			}
-			else
-			{
-				// Rewind cached result
-				$this->query_cache[$hash]->rewind();
-			}
-
-			// Return the cached query
-			return $this->query_cache[$hash];
-		}
-
-		return new Mysql_Result(mysql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql);
-	}
-
-	public function set_charset($charset)
-	{
-		$this->query('SET NAMES '.$this->escape_str($charset));
-	}
-
-	public function escape_table($table)
-	{
-		if (!$this->db_config['escape'])
-			return $table;
-
-		if (stripos($table, ' AS ') !== FALSE)
-		{
-			// Force 'AS' to uppercase
-			$table = str_ireplace(' AS ', ' AS ', $table);
-
-			// Runs escape_table on both sides of an AS statement
-			$table = array_map(array($this, __FUNCTION__), explode(' AS ', $table));
-
-			// Re-create the AS statement
-			return implode(' AS ', $table);
-		}
-		return '`'.str_replace('.', '`.`', $table).'`';
-	}
-
-	public function escape_column($column)
-	{
-		if (!$this->db_config['escape'])
-			return $column;
-
-		if ($column == '*')
-			return $column;
-
-		// This matches any functions we support to SELECT.
-		if ( preg_match('/(avg|count|sum|max|min)\(\s*(.*)\s*\)(\s*as\s*(.+)?)?/i', $column, $matches))
-		{
-			if ( count($matches) == 3)
-			{
-				return $matches[1].'('.$this->escape_column($matches[2]).')';
-			}
-			else if ( count($matches) == 5)
-			{
-				return $matches[1].'('.$this->escape_column($matches[2]).') AS '.$this->escape_column($matches[2]);
-			}
-		}
-		
-		// This matches any modifiers we support to SELECT.
-		if ( ! preg_match('/\b(?:rand|all|distinct(?:row)?|high_priority|sql_(?:small_result|b(?:ig_result|uffer_result)|no_cache|ca(?:che|lc_found_rows)))\s/i', $column))
-		{
-			if (stripos($column, ' AS ') !== FALSE)
-			{
-				// Force 'AS' to uppercase
-				$column = str_ireplace(' AS ', ' AS ', $column);
-
-				// Runs escape_column on both sides of an AS statement
-				$column = array_map(array($this, __FUNCTION__), explode(' AS ', $column));
-
-				// Re-create the AS statement
-				return implode(' AS ', $column);
-			}
-
-			return preg_replace('/[^.*]+/', '`$0`', $column);
-		}
-
-		$parts = explode(' ', $column);
-		$column = '';
-
-		for ($i = 0, $c = count($parts); $i < $c; $i++)
-		{
-			// The column is always last
-			if ($i == ($c - 1))
-			{
-				$column .= preg_replace('/[^.*]+/', '`$0`', $parts[$i]);
-			}
-			else // otherwise, it's a modifier
-			{
-				$column .= $parts[$i].' ';
-			}
-		}
-		return $column;
-	}
-
-	public function regex($field, $match, $type, $num_regexs)
-	{
-		$prefix = ($num_regexs == 0) ? '' : $type;
-
-		return $prefix.' '.$this->escape_column($field).' REGEXP \''.$this->escape_str($match).'\'';
-	}
-
-	public function notregex($field, $match, $type, $num_regexs)
-	{
-		$prefix = $num_regexs == 0 ? '' : $type;
-
-		return $prefix.' '.$this->escape_column($field).' NOT REGEXP \''.$this->escape_str($match) . '\'';
-	}
-
-	public function merge($table, $keys, $values)
-	{
-		// Escape the column names
-		foreach ($keys as $key => $value)
-		{
-			$keys[$key] = $this->escape_column($value);
-		}
-		return 'REPLACE INTO '.$this->escape_table($table).' ('.implode(', ', $keys).') VALUES ('.implode(', ', $values).')';
-	}
-
-	public function limit($limit, $offset = 0)
-	{
-		return 'LIMIT '.$offset.', '.$limit;
-	}
-
-	public function compile_select($database)
-	{
-		$sql = ($database['distinct'] == TRUE) ? 'SELECT DISTINCT ' : 'SELECT ';
-		$sql .= (count($database['select']) > 0) ? implode(', ', $database['select']) : '*';
-
-		if (count($database['from']) > 0)
-		{
-			// Escape the tables
-			$froms = array();
-			foreach ($database['from'] as $from)
-			{
-				$froms[] = $this->escape_column($from);
-			}
-			$sql .= "\nFROM (";
-			$sql .= implode(', ', $froms).")";
-		}
-
-		if (count($database['join']) > 0)
-		{
-			foreach($database['join'] AS $join)
-			{
-				$sql .= "\n".$join['type'].'JOIN '.implode(', ', $join['tables']).' ON '.$join['conditions'];
-			}
-		}
-
-		if (count($database['where']) > 0)
-		{
-			$sql .= "\nWHERE ";
-		}
-
-		$sql .= implode("\n", $database['where']);
-
-		if (count($database['groupby']) > 0)
-		{
-			$sql .= "\nGROUP BY ";
-			$sql .= implode(', ', $database['groupby']);
-		}
-
-		if (count($database['having']) > 0)
-		{
-			$sql .= "\nHAVING ";
-			$sql .= implode("\n", $database['having']);
-		}
-
-		if (count($database['orderby']) > 0)
-		{
-			$sql .= "\nORDER BY ";
-			$sql .= implode(', ', $database['orderby']);
-		}
-
-		if (is_numeric($database['limit']))
-		{
-			$sql .= "\n";
-			$sql .= $this->limit($database['limit'], $database['offset']);
-		}
-
-		return $sql;
-	}
-
-	public function escape_str($str)
-	{
-		if (!$this->db_config['escape'])
-			return $str;
-
-		is_resource($this->link) or $this->connect();
-
-		return mysql_real_escape_string($str, $this->link);
-	}
-
-	public function list_tables()
-	{
-		$tables = array();
-
-		if ($query = $this->query('SHOW TABLES FROM '.$this->escape_table($this->db_config['connection']['database'])))
-		{
-			foreach ($query->result(FALSE) as $row)
-			{
-				$tables[] = current($row);
-			}
-		}
-
-		return $tables;
-	}
-
-	public function show_error()
-	{
-		return mysql_error($this->link);
-	}
-
-	public function list_fields($table)
-	{
-		$result = NULL;
-
-		foreach ($this->field_data($table) as $row)
-		{
-			// Make an associative array
-			$result[$row->Field] = $this->sql_type($row->Type);
-
-			if ($row->Key === 'PRI' AND $row->Extra === 'auto_increment')
-			{
-				// For sequenced (AUTO_INCREMENT) tables
-				$result[$row->Field]['sequenced'] = TRUE;
-			}
-
-			if ($row->Null === 'YES')
-			{
-				// Set NULL status
-				$result[$row->Field]['null'] = TRUE;
-			}
-		}
-
-		if (!isset($result))
-			throw new Kohana_Database_Exception('database.table_not_found', $table);
-
-		return $result;
-	}
-
-	public function field_data($table)
-	{
-		$result = $this->query('SHOW COLUMNS FROM '.$this->escape_table($table));
-
-		return $result->result_array(TRUE);
-	}
-
-} // End Database_Mysql_Driver Class
-
-/**
- * MySQL Result
- */
-class Mysql_Result extends Database_Result {
-
-	// Fetch function and return type
-	protected $fetch_type  = 'mysql_fetch_object';
-	protected $return_type = MYSQL_ASSOC;
-
-	/**
-	 * Sets up the result variables.
-	 *
-	 * @param  resource  query result
-	 * @param  resource  database link
-	 * @param  boolean   return objects or arrays
-	 * @param  string    SQL query that was run
-	 */
-	public function __construct($result, $link, $object = TRUE, $sql)
-	{
-		$this->result = $result;
-
-		// If the query is a resource, it was a SELECT, SHOW, DESCRIBE, EXPLAIN query
-		if (is_resource($result))
-		{
-			$this->current_row = 0;
-			$this->total_rows  = mysql_num_rows($this->result);
-			$this->fetch_type = ($object === TRUE) ? 'mysql_fetch_object' : 'mysql_fetch_array';
-		}
-		elseif (is_bool($result))
-		{
-			if ($result == FALSE)
-			{
-				// SQL error
-				throw new Kohana_Database_Exception('database.error', mysql_error($link).' - '.$sql);
-			}
-			else
-			{
-				// Its an DELETE, INSERT, REPLACE, or UPDATE query
-				$this->insert_id  = mysql_insert_id($link);
-				$this->total_rows = mysql_affected_rows($link);
-			}
-		}
-
-		// Set result type
-		$this->result($object);
-
-		// Store the SQL
-		$this->sql = $sql;
-	}
-
-	/**
-	 * Destruct, the cleanup crew!
-	 */
-	public function __destruct()
-	{
-		if (is_resource($this->result))
-		{
-			mysql_free_result($this->result);
-		}
-	}
-
-	public function result($object = TRUE, $type = MYSQL_ASSOC)
-	{
-		$this->fetch_type = ((bool) $object) ? 'mysql_fetch_object' : 'mysql_fetch_array';
-
-		// This check has to be outside the previous statement, because we do not
-		// know the state of fetch_type when $object = NULL
-		// NOTE - The class set by $type must be defined before fetching the result,
-		// autoloading is disabled to save a lot of stupid overhead.
-		if ($this->fetch_type == 'mysql_fetch_object' AND $object === TRUE)
-		{
-			$this->return_type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
-		}
-		else
-		{
-			$this->return_type = $type;
-		}
-
-		return $this;
-	}
-
-	public function as_array($object = NULL, $type = MYSQL_ASSOC)
-	{
-		return $this->result_array($object, $type);
-	}
-
-	public function result_array($object = NULL, $type = MYSQL_ASSOC)
-	{
-		$rows = array();
-
-		if (is_string($object))
-		{
-			$fetch = $object;
-		}
-		elseif (is_bool($object))
-		{
-			if ($object === TRUE)
-			{
-				$fetch = 'mysql_fetch_object';
-
-				$type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
-			}
-			else
-			{
-				$fetch = 'mysql_fetch_array';
-			}
-		}
-		else
-		{
-			// Use the default config values
-			$fetch = $this->fetch_type;
-
-			if ($fetch == 'mysql_fetch_object')
-			{
-				$type = (is_string($this->return_type) AND Kohana::auto_load($this->return_type)) ? $this->return_type : 'stdClass';
-			}
-		}
-
-		if (mysql_num_rows($this->result))
-		{
-			// Reset the pointer location to make sure things work properly
-			mysql_data_seek($this->result, 0);
-
-			while ($row = $fetch($this->result, $type))
-			{
-				$rows[] = $row;
-			}
-		}
-
-		return isset($rows) ? $rows : array();
-	}
-
-	public function list_fields()
-	{
-		$field_names = array();
-		while ($field = mysql_fetch_field($this->result))
-		{
-			$field_names[] = $field->name;
-		}
-
-		return $field_names;
-	}
-
-	public function seek($offset)
-	{
-		if ($this->offsetExists($offset) AND mysql_data_seek($this->result, $offset))
-		{
-			// Set the current row to the offset
-			$this->current_row = $offset;
-
-			return TRUE;
-		}
-		else
-		{
-			return FALSE;
-		}
-	}
-
-} // End Mysql_Result Class
diff --git a/system/libraries/drivers/Database/Mysqli.php b/system/libraries/drivers/Database/Mysqli.php
deleted file mode 100644
index 0dd9f05c..00000000
--- a/system/libraries/drivers/Database/Mysqli.php
+++ /dev/null
@@ -1,358 +0,0 @@
-db_config = $config;
-
-		Kohana::log('debug', 'MySQLi Database Driver Initialized');
-	}
-
-	/**
-	 * Closes the database connection.
-	 */
-	public function __destruct()
-	{
-		is_object($this->link) and $this->link->close();
-	}
-
-	public function connect()
-	{
-		// Check if link already exists
-		if (is_object($this->link))
-			return $this->link;
-
-		// Import the connect variables
-		extract($this->db_config['connection']);
-
-		// Build the connection info
-		$host = isset($host) ? $host : $socket;
-
-		// Make the connection and select the database
-		if ($this->link = new mysqli($host, $user, $pass, $database, $port))
-		{
-			if ($charset = $this->db_config['character_set'])
-			{
-				$this->set_charset($charset);
-			}
-
-			// Clear password after successful connect
-			$this->db_config['connection']['pass'] = NULL;
-
-			return $this->link;
-		}
-
-		return FALSE;
-	}
-
-	public function query($sql)
-	{
-		// Only cache if it's turned on, and only cache if it's not a write statement
-		if ($this->db_config['cache'] AND ! preg_match('#\b(?:INSERT|UPDATE|REPLACE|SET|DELETE|TRUNCATE)\b#i', $sql))
-		{
-			$hash = $this->query_hash($sql);
-
-			if ( ! isset($this->query_cache[$hash]))
-			{
-				// Set the cached object
-				$this->query_cache[$hash] = new Kohana_Mysqli_Result($this->link, $this->db_config['object'], $sql);
-			}
-			else
-			{
-				// Rewind cached result
-				$this->query_cache[$hash]->rewind();
-			}
-
-			// Return the cached query
-			return $this->query_cache[$hash];
-		}
-
-		return new Kohana_Mysqli_Result($this->link, $this->db_config['object'], $sql);
-	}
-
-	public function set_charset($charset)
-	{
-		if ($this->link->set_charset($charset) === FALSE)
-			throw new Kohana_Database_Exception('database.error', $this->show_error());
-	}
-
-	public function escape_str($str)
-	{
-		if (!$this->db_config['escape'])
-			return $str;
-
-		is_object($this->link) or $this->connect();
-
-		return $this->link->real_escape_string($str);
-	}
-
-	public function show_error()
-	{
-		return $this->link->error;
-	}
-
-} // End Database_Mysqli_Driver Class
-
-/**
- * MySQLi Result
- */
-class Kohana_Mysqli_Result extends Database_Result {
-
-	// Database connection
-	protected $link;
-
-	// Data fetching types
-	protected $fetch_type  = 'mysqli_fetch_object';
-	protected $return_type = MYSQLI_ASSOC;
-
-	/**
-	 * Sets up the result variables.
-	 *
-	 * @param  object    database link
-	 * @param  boolean   return objects or arrays
-	 * @param  string    SQL query that was run
-	 */
-	public function __construct($link, $object = TRUE, $sql)
-	{
-		$this->link = $link;
-
-		if ( ! $this->link->multi_query($sql))
-		{
-			// SQL error
-			throw new Kohana_Database_Exception('database.error', $this->link->error.' - '.$sql);
-		}
-		else
-		{
-			$this->result = $this->link->store_result();
-
-			// If the query is an object, it was a SELECT, SHOW, DESCRIBE, EXPLAIN query
-			if (is_object($this->result))
-			{
-				$this->current_row = 0;
-				$this->total_rows  = $this->result->num_rows;
-				$this->fetch_type = ($object === TRUE) ? 'fetch_object' : 'fetch_array';
-			}
-			elseif ($this->link->error)
-			{
-				// SQL error
-				throw new Kohana_Database_Exception('database.error', $this->link->error.' - '.$sql);
-			}
-			else
-			{
-				// Its an DELETE, INSERT, REPLACE, or UPDATE query
-				$this->insert_id  = $this->link->insert_id;
-				$this->total_rows = $this->link->affected_rows;
-			}
-		}
-
-		// Set result type
-		$this->result($object);
-
-		// Store the SQL
-		$this->sql = $sql;
-	}
-
-	/**
-	 * Magic __destruct function, frees the result.
-	 */
-	public function __destruct()
-	{
-		if (is_object($this->result))
-		{
-			$this->result->free_result();
-
-			// this is kinda useless, but needs to be done to avoid the "Commands out of sync; you
-			// can't run this command now" error. Basically, we get all results after the first one
-			// (the one we actually need) and free them.
-			if (is_resource($this->link) AND $this->link->more_results())
-			{
-				do
-				{
-					if ($result = $this->link->store_result())
-					{
-						$result->free_result();
-					}
-				} while ($this->link->next_result());
-			}
-		}
-	}
-
-	public function result($object = TRUE, $type = MYSQLI_ASSOC)
-	{
-		$this->fetch_type = ((bool) $object) ? 'fetch_object' : 'fetch_array';
-
-		// This check has to be outside the previous statement, because we do not
-		// know the state of fetch_type when $object = NULL
-		// NOTE - The class set by $type must be defined before fetching the result,
-		// autoloading is disabled to save a lot of stupid overhead.
-		if ($this->fetch_type == 'fetch_object')
-		{
-			$this->return_type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
-		}
-		else
-		{
-			$this->return_type = $type;
-		}
-
-		return $this;
-	}
-
-	public function as_array($object = NULL, $type = MYSQLI_ASSOC)
-	{
-		return $this->result_array($object, $type);
-	}
-
-	public function result_array($object = NULL, $type = MYSQLI_ASSOC)
-	{
-		$rows = array();
-
-		if (is_string($object))
-		{
-			$fetch = $object;
-		}
-		elseif (is_bool($object))
-		{
-			if ($object === TRUE)
-			{
-				$fetch = 'fetch_object';
-
-				// NOTE - The class set by $type must be defined before fetching the result,
-				// autoloading is disabled to save a lot of stupid overhead.
-				$type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
-			}
-			else
-			{
-				$fetch = 'fetch_array';
-			}
-		}
-		else
-		{
-			// Use the default config values
-			$fetch = $this->fetch_type;
-
-			if ($fetch == 'fetch_object')
-			{
-				$type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
-			}
-		}
-
-		if ($this->result->num_rows)
-		{
-			// Reset the pointer location to make sure things work properly
-			$this->result->data_seek(0);
-
-			while ($row = $this->result->$fetch($type))
-			{
-				$rows[] = $row;
-			}
-		}
-
-		return isset($rows) ? $rows : array();
-	}
-
-	public function list_fields()
-	{
-		$field_names = array();
-		while ($field = $this->result->fetch_field())
-		{
-			$field_names[] = $field->name;
-		}
-
-		return $field_names;
-	}
-
-	public function seek($offset)
-	{
-		if ($this->offsetExists($offset) AND $this->result->data_seek($offset))
-		{
-			// Set the current row to the offset
-			$this->current_row = $offset;
-
-			return TRUE;
-		}
-
-		return FALSE;
-	}
-
-	public function offsetGet($offset)
-	{
-		if ( ! $this->seek($offset))
-			return FALSE;
-
-		// Return the row
-		$fetch = $this->fetch_type;
-		return $this->result->$fetch($this->return_type);
-	}
-
-} // End Mysqli_Result Class
-
-/**
- * MySQLi Prepared Statement (experimental)
- */
-class Kohana_Mysqli_Statement {
-
-	protected $link = NULL;
-	protected $stmt;
-	protected $var_names = array();
-	protected $var_values = array();
-
-	public function __construct($sql, $link)
-	{
-		$this->link = $link;
-
-		$this->stmt = $this->link->prepare($sql);
-
-		return $this;
-	}
-
-	public function __destruct()
-	{
-		$this->stmt->close();
-	}
-
-	// Sets the bind parameters
-	public function bind_params($param_types, $params)
-	{
-		$this->var_names = array_keys($params);
-		$this->var_values = array_values($params);
-		call_user_func_array(array($this->stmt, 'bind_param'), array_merge($param_types, $var_names));
-
-		return $this;
-	}
-
-	public function bind_result($params)
-	{
-		call_user_func_array(array($this->stmt, 'bind_result'), $params);
-	}
-
-	// Runs the statement
-	public function execute()
-	{
-		foreach ($this->var_names as $key => $name)
-		{
-			$$name = $this->var_values[$key];
-		}
-		$this->stmt->execute();
-		return $this->stmt;
-	}
-}
diff --git a/system/libraries/drivers/Database/Pdosqlite.php b/system/libraries/drivers/Database/Pdosqlite.php
deleted file mode 100644
index c2d1bb21..00000000
--- a/system/libraries/drivers/Database/Pdosqlite.php
+++ /dev/null
@@ -1,486 +0,0 @@
-
- */
-
-class Database_Pdosqlite_Driver extends Database_Driver {
-
-	// Database connection link
-	protected $link;
-	protected $db_config;
-
-	/*
-	 * Constructor: __construct
-	 *  Sets up the config for the class.
-	 *
-	 * Parameters:
-	 *  config - database configuration
-	 *
-	 */
-	public function __construct($config)
-	{
-		$this->db_config = $config;
-
-		Kohana::log('debug', 'PDO:Sqlite Database Driver Initialized');
-	}
-
-	public function connect()
-	{
-		// Import the connect variables
-		extract($this->db_config['connection']);
-
-		try
-		{
-			$this->link = new PDO('sqlite:'.$socket.$database, $user, $pass,
-				array(PDO::ATTR_PERSISTENT => $this->db_config['persistent']));
-
-			$this->link->setAttribute(PDO::ATTR_CASE, PDO::CASE_NATURAL);
-			//$this->link->query('PRAGMA count_changes=1;');
-
-			if ($charset = $this->db_config['character_set'])
-			{
-				$this->set_charset($charset);
-			}
-		}
-		catch (PDOException $e)
-		{
-			throw new Kohana_Database_Exception('database.error', $e->getMessage());
-		}
-
-		// Clear password after successful connect
-		$this->db_config['connection']['pass'] = NULL;
-
-		return $this->link;
-	}
-
-	public function query($sql)
-	{
-        try
-		{
-			$sth = $this->link->prepare($sql);
-		}
-		catch (PDOException $e)
-		{
-			throw new Kohana_Database_Exception('database.error', $e->getMessage());
-		}
-		return new Pdosqlite_Result($sth, $this->link, $this->db_config['object'], $sql);
-	}
-
-	public function set_charset($charset)
-	{
-		$this->link->query('PRAGMA encoding = '.$this->escape_str($charset));
-	}
-
-	public function escape_table($table)
-	{
-		if ( ! $this->db_config['escape'])
-			return $table;
-
-		return '`'.str_replace('.', '`.`', $table).'`';
-	}
-
-	public function escape_column($column)
-	{
-		if ( ! $this->db_config['escape'])
-			return $column;
-
-		if ($column == '*')
-			return $column;
-
-		// This matches any functions we support to SELECT.
-		if ( preg_match('/(avg|count|sum|max|min)\(\s*(.*)\s*\)(\s*as\s*(.+)?)?/i', $column, $matches))
-		{
-			if ( count($matches) == 3)
-			{
-				return $matches[1].'('.$this->escape_column($matches[2]).')';
-			}
-			else if ( count($matches) == 5)
-			{
-				return $matches[1].'('.$this->escape_column($matches[2]).') AS '.$this->escape_column($matches[2]);
-			}
-		}
-
-		// This matches any modifiers we support to SELECT.
-		if ( ! preg_match('/\b(?:rand|all|distinct(?:row)?|high_priority|sql_(?:small_result|b(?:ig_result|uffer_result)|no_cache|ca(?:che|lc_found_rows)))\s/i', $column))
-		{
-			if (stripos($column, ' AS ') !== FALSE)
-			{
-				// Force 'AS' to uppercase
-				$column = str_ireplace(' AS ', ' AS ', $column);
-
-				// Runs escape_column on both sides of an AS statement
-				$column = array_map(array($this, __FUNCTION__), explode(' AS ', $column));
-
-				// Re-create the AS statement
-				return implode(' AS ', $column);
-			}
-
-			return preg_replace('/[^.*]+/', '`$0`', $column);
-		}
-
-		$parts = explode(' ', $column);
-		$column = '';
-
-		for ($i = 0, $c = count($parts); $i < $c; $i++)
-		{
-			// The column is always last
-			if ($i == ($c - 1))
-			{
-				$column .= preg_replace('/[^.*]+/', '`$0`', $parts[$i]);
-			}
-			else // otherwise, it's a modifier
-			{
-				$column .= $parts[$i].' ';
-			}
-		}
-		return $column;
-	}
-
-	public function limit($limit, $offset = 0)
-	{
-		return 'LIMIT '.$offset.', '.$limit;
-	}
-
-	public function compile_select($database)
-	{
-		$sql = ($database['distinct'] == TRUE) ? 'SELECT DISTINCT ' : 'SELECT ';
-		$sql .= (count($database['select']) > 0) ? implode(', ', $database['select']) : '*';
-
-		if (count($database['from']) > 0)
-		{
-			$sql .= "\nFROM ";
-			$sql .= implode(', ', $database['from']);
-		}
-
-		if (count($database['join']) > 0)
-		{
-			foreach($database['join'] AS $join)
-			{
-				$sql .= "\n".$join['type'].'JOIN '.implode(', ', $join['tables']).' ON '.$join['conditions'];
-			}
-		}
-
-		if (count($database['where']) > 0)
-		{
-			$sql .= "\nWHERE ";
-		}
-
-		$sql .= implode("\n", $database['where']);
-
-		if (count($database['groupby']) > 0)
-		{
-			$sql .= "\nGROUP BY ";
-			$sql .= implode(', ', $database['groupby']);
-		}
-
-		if (count($database['having']) > 0)
-		{
-			$sql .= "\nHAVING ";
-			$sql .= implode("\n", $database['having']);
-		}
-
-		if (count($database['orderby']) > 0)
-		{
-			$sql .= "\nORDER BY ";
-			$sql .= implode(', ', $database['orderby']);
-		}
-
-		if (is_numeric($database['limit']))
-		{
-			$sql .= "\n";
-			$sql .= $this->limit($database['limit'], $database['offset']);
-		}
-
-		return $sql;
-	}
-
-	public function escape_str($str)
-	{
-		if ( ! $this->db_config['escape'])
-			return $str;
-
-		if (function_exists('sqlite_escape_string'))
-		{
-			$res = sqlite_escape_string($str);
-		}
-		else
-		{
-			$res = str_replace("'", "''", $str);
-		}
-		return $res;
-	}
-
-	public function list_tables()
-	{
-		$sql = "SELECT `name` FROM `sqlite_master` WHERE `type`='table' ORDER BY `name`;";
-		try
-		{
-			$result = $this->query($sql)->result(FALSE, PDO::FETCH_ASSOC);
-			$tables = array();
-			foreach ($result as $row)
-			{
-				$tables[] = current($row);
-			}
-		}
-		catch (PDOException $e)
-		{
-			throw new Kohana_Database_Exception('database.error', $e->getMessage());
-		}
-		return $tables;
-	}
-
-	public function show_error()
-	{
-		$err = $this->link->errorInfo();
-		return isset($err[2]) ? $err[2] : 'Unknown error!';
-	}
-
-	public function list_fields($table, $query = FALSE)
-	{
-		static $tables;
-		if (is_object($query))
-		{
-			if (empty($tables[$table]))
-			{
-				$tables[$table] = array();
-
-				foreach ($query->result() as $row)
-				{
-					$tables[$table][] = $row->name;
-				}
-			}
-
-			return $tables[$table];
-		}
-		else
-		{
-			$result = $this->link->query( 'PRAGMA table_info('.$this->escape_table($table).')' );
-
-			foreach ($result as $row)
-			{
-				$tables[$table][$row['name']] = $this->sql_type($row['type']);
-			}
-
-			return $tables[$table];
-		}
-	}
-
-	public function field_data($table)
-	{
-		Kohana::log('error', 'This method is under developing');
-	}
-	/**
-	 * Version number query string
-	 *
-	 * @access	public
-	 * @return	string
-	 */
-	function version()
-	{
-		return $this->link->getAttribute(constant("PDO::ATTR_SERVER_VERSION"));
-	}
-
-} // End Database_PdoSqlite_Driver Class
-
-/*
- * PDO-sqlite Result
- */
-class Pdosqlite_Result extends Database_Result {
-
-	// Data fetching types
-	protected $fetch_type  = PDO::FETCH_OBJ;
-	protected $return_type = PDO::FETCH_ASSOC;
-
-	/**
-	 * Sets up the result variables.
-	 *
-	 * @param  resource  query result
-	 * @param  resource  database link
-	 * @param  boolean   return objects or arrays
-	 * @param  string    SQL query that was run
-	 */
-	public function __construct($result, $link, $object = TRUE, $sql)
-	{
-		if (is_object($result) OR $result = $link->prepare($sql))
-		{
-			// run the query. Return true if success, false otherwise
-			if( ! $result->execute())
-			{
-				// Throw Kohana Exception with error message. See PDOStatement errorInfo() method
-				$arr_infos = $result->errorInfo();
-				throw new Kohana_Database_Exception('database.error', $arr_infos[2]);
-			}
-
-			if (preg_match('/^SELECT|PRAGMA|EXPLAIN/i', $sql))
-			{
-				$this->result = $result;
-				$this->current_row = 0;
-
-				$this->total_rows = $this->sqlite_row_count();
-
-				$this->fetch_type = ($object === TRUE) ? PDO::FETCH_OBJ : PDO::FETCH_ASSOC;
-			}
-			elseif (preg_match('/^DELETE|INSERT|UPDATE/i', $sql))
-			{
-				$this->insert_id  = $link->lastInsertId();
-
-				$this->total_rows = $result->rowCount();
-			}
-		}
-		else
-		{
-			// SQL error
-			throw new Kohana_Database_Exception('database.error', $link->errorInfo().' - '.$sql);
-		}
-
-		// Set result type
-		$this->result($object);
-
-		// Store the SQL
-		$this->sql = $sql;
-	}
-
-	private function sqlite_row_count()
-	{
-		$count = 0;
-		while ($this->result->fetch())
-		{
-			$count++;
-		}
-
-		// The query must be re-fetched now.
-		$this->result->execute();
-
-		return $count;
-	}
-
-	/*
-	 * Destructor: __destruct
-	 *  Magic __destruct function, frees the result.
-	 */
-	public function __destruct()
-	{
-		if (is_object($this->result))
-		{
-			$this->result->closeCursor();
-			$this->result = NULL;
-		}
-	}
-
-	public function result($object = TRUE, $type = PDO::FETCH_BOTH)
-	{
-		$this->fetch_type = (bool) $object ? PDO::FETCH_OBJ : PDO::FETCH_BOTH;
-
-		if ($this->fetch_type == PDO::FETCH_OBJ)
-		{
-			$this->return_type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
-		}
-		else
-		{
-			$this->return_type = $type;
-		}
-
-		return $this;
-	}
-
-	public function as_array($object = NULL, $type = PDO::FETCH_ASSOC)
-	{
-		return $this->result_array($object, $type);
-	}
-
-	public function result_array($object = NULL, $type = PDO::FETCH_ASSOC)
-	{
-		$rows = array();
-
-		if (is_string($object))
-		{
-			$fetch = $object;
-		}
-		elseif (is_bool($object))
-		{
-			if ($object === TRUE)
-			{
-				$fetch = PDO::FETCH_OBJ;
-
-				// NOTE - The class set by $type must be defined before fetching the result,
-				// autoloading is disabled to save a lot of stupid overhead.
-				$type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
-			}
-			else
-			{
-				$fetch = PDO::FETCH_OBJ;
-			}
-		}
-		else
-		{
-			// Use the default config values
-			$fetch = $this->fetch_type;
-
-			if ($fetch == PDO::FETCH_OBJ)
-			{
-				$type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
-			}
-		}
-		try
-		{
-			while ($row = $this->result->fetch($fetch))
-			{
-				$rows[] = $row;
-			}
-		}
-		catch(PDOException $e)
-		{
-			throw new Kohana_Database_Exception('database.error', $e->getMessage());
-			return FALSE;
-		}
-		return $rows;
-	}
-
-	public function list_fields()
-	{
-		$field_names = array();
-		for ($i = 0, $max = $this->result->columnCount(); $i < $max; $i++)
-		{
-			$info = $this->result->getColumnMeta($i);
-			$field_names[] = $info['name'];
-		}
-		return $field_names;
-	}
-
-	public function seek($offset)
-	{
-		// To request a scrollable cursor for your PDOStatement object, you must
-		// set the PDO::ATTR_CURSOR attribute to PDO::CURSOR_SCROLL when you
-		// prepare the statement.
-		Kohana::log('error', get_class($this).' does not support scrollable cursors, '.__FUNCTION__.' call ignored');
-
-		return FALSE;
-	}
-
-	public function offsetGet($offset)
-	{
-		try
-		{
-			return $this->result->fetch($this->fetch_type, PDO::FETCH_ORI_ABS, $offset);
-		}
-		catch(PDOException $e)
-		{
-			throw new Kohana_Database_Exception('database.error', $e->getMessage());
-		}
-	}
-
-	public function rewind()
-	{
-		// Same problem that seek() has, see above.
-		return $this->seek(0);
-	}
-
-} // End PdoSqlite_Result Class
\ No newline at end of file
diff --git a/system/libraries/drivers/Database/Pgsql.php b/system/libraries/drivers/Database/Pgsql.php
deleted file mode 100644
index c53c8439..00000000
--- a/system/libraries/drivers/Database/Pgsql.php
+++ /dev/null
@@ -1,538 +0,0 @@
-db_config = $config;
-
-		Kohana::log('debug', 'PgSQL Database Driver Initialized');
-	}
-
-	public function connect()
-	{
-		// Check if link already exists
-		if (is_resource($this->link))
-			return $this->link;
-
-		// Import the connect variables
-		extract($this->db_config['connection']);
-
-		// Persistent connections enabled?
-		$connect = ($this->db_config['persistent'] == TRUE) ? 'pg_pconnect' : 'pg_connect';
-
-		// Build the connection info
-		$port = isset($port) ? 'port=\''.$port.'\'' : '';
-		$host = isset($host) ? 'host=\''.$host.'\' '.$port : ''; // if no host, connect with the socket
-
-		$connection_string = $host.' dbname=\''.$database.'\' user=\''.$user.'\' password=\''.$pass.'\'';
-		// Make the connection and select the database
-		if ($this->link = $connect($connection_string))
-		{
-			if ($charset = $this->db_config['character_set'])
-			{
-				echo $this->set_charset($charset);
-			}
-
-			// Clear password after successful connect
-			$this->db_config['connection']['pass'] = NULL;
-
-			return $this->link;
-		}
-
-		return FALSE;
-	}
-
-	public function query($sql)
-	{
-		// Only cache if it's turned on, and only cache if it's not a write statement
-		if ($this->db_config['cache'] AND ! preg_match('#\b(?:INSERT|UPDATE|SET)\b#i', $sql))
-		{
-			$hash = $this->query_hash($sql);
-
-			if ( ! isset($this->query_cache[$hash]))
-			{
-				// Set the cached object
-				$this->query_cache[$hash] = new Pgsql_Result(pg_query($this->link, $sql), $this->link, $this->db_config['object'], $sql);
-			}
-			else
-			{
-				// Rewind cached result
-				$this->query_cache[$hash]->rewind();
-			}
-
-			return $this->query_cache[$hash];
-		}
-
-		// Suppress warning triggered when a database error occurs (e.g., a constraint violation)
-		return new Pgsql_Result(@pg_query($this->link, $sql), $this->link, $this->db_config['object'], $sql);
-	}
-
-	public function set_charset($charset)
-	{
-		$this->query('SET client_encoding TO '.pg_escape_string($this->link, $charset));
-	}
-
-	public function escape_table($table)
-	{
-		if (!$this->db_config['escape'])
-			return $table;
-
-		return '"'.str_replace('.', '"."', $table).'"';
-	}
-
-	public function escape_column($column)
-	{
-		if (!$this->db_config['escape'])
-			return $column;
-
-		if ($column == '*')
-			return $column;
-
-		// This matches any functions we support to SELECT.
-		if ( preg_match('/(avg|count|sum|max|min)\(\s*(.*)\s*\)(\s*as\s*(.+)?)?/i', $column, $matches))
-		{
-			if ( count($matches) == 3)
-			{
-				return $matches[1].'('.$this->escape_column($matches[2]).')';
-			}
-			else if ( count($matches) == 5)
-			{
-				return $matches[1].'('.$this->escape_column($matches[2]).') AS '.$this->escape_column($matches[2]);
-			}
-		}
-
-		// This matches any modifiers we support to SELECT.
-		if ( ! preg_match('/\b(?:all|distinct)\s/i', $column))
-		{
-			if (stripos($column, ' AS ') !== FALSE)
-			{
-				// Force 'AS' to uppercase
-				$column = str_ireplace(' AS ', ' AS ', $column);
-
-				// Runs escape_column on both sides of an AS statement
-				$column = array_map(array($this, __FUNCTION__), explode(' AS ', $column));
-
-				// Re-create the AS statement
-				return implode(' AS ', $column);
-			}
-
-			return preg_replace('/[^.*]+/', '"$0"', $column);
-		}
-
-		$parts = explode(' ', $column);
-		$column = '';
-
-		for ($i = 0, $c = count($parts); $i < $c; $i++)
-		{
-			// The column is always last
-			if ($i == ($c - 1))
-			{
-				$column .= preg_replace('/[^.*]+/', '"$0"', $parts[$i]);
-			}
-			else // otherwise, it's a modifier
-			{
-				$column .= $parts[$i].' ';
-			}
-		}
-		return $column;
-	}
-
-	public function regex($field, $match, $type, $num_regexs)
-	{
-		$prefix = ($num_regexs == 0) ? '' : $type;
-
-		return $prefix.' '.$this->escape_column($field).' ~* \''.$this->escape_str($match).'\'';
-	}
-
-	public function notregex($field, $match, $type, $num_regexs)
-	{
-		$prefix = $num_regexs == 0 ? '' : $type;
-
-		return $prefix.' '.$this->escape_column($field).' !~* \''.$this->escape_str($match) . '\'';
-	}
-
-	public function limit($limit, $offset = 0)
-	{
-		return 'LIMIT '.$limit.' OFFSET '.$offset;
-	}
-
-	public function compile_select($database)
-	{
-		$sql = ($database['distinct'] == TRUE) ? 'SELECT DISTINCT ' : 'SELECT ';
-		$sql .= (count($database['select']) > 0) ? implode(', ', $database['select']) : '*';
-
-		if (count($database['from']) > 0)
-		{
-			$sql .= "\nFROM ";
-			$sql .= implode(', ', $database['from']);
-		}
-
-		if (count($database['join']) > 0)
-		{
-			foreach($database['join'] AS $join)
-			{
-				$sql .= "\n".$join['type'].'JOIN '.implode(', ', $join['tables']).' ON '.$join['conditions'];
-			}
-		}
-
-		if (count($database['where']) > 0)
-		{
-			$sql .= "\nWHERE ";
-		}
-
-		$sql .= implode("\n", $database['where']);
-
-		if (count($database['groupby']) > 0)
-		{
-			$sql .= "\nGROUP BY ";
-			$sql .= implode(', ', $database['groupby']);
-		}
-
-		if (count($database['having']) > 0)
-		{
-			$sql .= "\nHAVING ";
-			$sql .= implode("\n", $database['having']);
-		}
-
-		if (count($database['orderby']) > 0)
-		{
-			$sql .= "\nORDER BY ";
-			$sql .= implode(', ', $database['orderby']);
-		}
-
-		if (is_numeric($database['limit']))
-		{
-			$sql .= "\n";
-			$sql .= $this->limit($database['limit'], $database['offset']);
-		}
-
-		return $sql;
-	}
-
-	public function escape_str($str)
-	{
-		if (!$this->db_config['escape'])
-			return $str;
-
-		is_resource($this->link) or $this->connect();
-
-		return pg_escape_string($this->link, $str);
-	}
-
-	public function list_tables()
-	{
-		$sql    = 'SELECT table_schema || \'.\' || table_name FROM information_schema.tables WHERE table_schema NOT IN (\'pg_catalog\', \'information_schema\')';
-		$result = $this->query($sql)->result(FALSE, PGSQL_ASSOC);
-
-		$retval = array();
-		foreach ($result as $row)
-		{
-			$retval[] = current($row);
-		}
-
-		return $retval;
-	}
-
-	public function show_error()
-	{
-		return pg_last_error($this->link);
-	}
-
-	public function list_fields($table)
-	{
-		$result = NULL;
-
-		foreach ($this->field_data($table) as $row)
-		{
-			// Make an associative array
-			$result[$row->column_name] = $this->sql_type($row->data_type);
-
-			if (!strncmp($row->column_default, 'nextval(', 8))
-			{
-				$result[$row->column_name]['sequenced'] = TRUE;
-			}
-
-			if ($row->is_nullable === 'YES')
-			{
-				$result[$row->column_name]['null'] = TRUE;
-			}
-		}
-
-		if (!isset($result))
-			throw new Kohana_Database_Exception('database.table_not_found', $table);
-
-		return $result;
-	}
-
-	public function field_data($table)
-	{
-		// http://www.postgresql.org/docs/8.3/static/infoschema-columns.html
-		$result = $this->query('
-			SELECT column_name, column_default, is_nullable, data_type, udt_name,
-				character_maximum_length, numeric_precision, numeric_precision_radix, numeric_scale
-			FROM information_schema.columns
-			WHERE table_name = \''. $this->escape_str($table) .'\'
-			ORDER BY ordinal_position
-		');
-
-		return $result->result_array(TRUE);
-	}
-
-} // End Database_Pgsql_Driver Class
-
-/**
- * PostgreSQL Result
- */
-class Pgsql_Result extends Database_Result {
-
-	// Data fetching types
-	protected $fetch_type  = 'pgsql_fetch_object';
-	protected $return_type = PGSQL_ASSOC;
-
-	/**
-	 * Sets up the result variables.
-	 *
-	 * @param  resource  query result
-	 * @param  resource  database link
-	 * @param  boolean   return objects or arrays
-	 * @param  string    SQL query that was run
-	 */
-	public function __construct($result, $link, $object = TRUE, $sql)
-	{
-		$this->link = $link;
-		$this->result = $result;
-
-		// If the query is a resource, it was a SELECT, SHOW, DESCRIBE, EXPLAIN query
-		if (is_resource($result))
-		{
-			// Its an DELETE, INSERT, REPLACE, or UPDATE query
-			if (preg_match('/^(?:delete|insert|replace|update)\b/iD', trim($sql), $matches))
-			{
-				$this->insert_id  = (strtolower($matches[0]) == 'insert') ? $this->insert_id() : FALSE;
-				$this->total_rows = pg_affected_rows($this->result);
-			}
-			else
-			{
-				$this->current_row = 0;
-				$this->total_rows  = pg_num_rows($this->result);
-				$this->fetch_type = ($object === TRUE) ? 'pg_fetch_object' : 'pg_fetch_array';
-			}
-		}
-		else
-		{
-			throw new Kohana_Database_Exception('database.error', pg_last_error().' - '.$sql);
-		}
-
-		// Set result type
-		$this->result($object);
-
-		// Store the SQL
-		$this->sql = $sql;
-	}
-
-	/**
-	 * Magic __destruct function, frees the result.
-	 */
-	public function __destruct()
-	{
-		if (is_resource($this->result))
-		{
-			pg_free_result($this->result);
-		}
-	}
-
-	public function result($object = TRUE, $type = PGSQL_ASSOC)
-	{
-		$this->fetch_type = ((bool) $object) ? 'pg_fetch_object' : 'pg_fetch_array';
-
-		// This check has to be outside the previous statement, because we do not
-		// know the state of fetch_type when $object = NULL
-		// NOTE - The class set by $type must be defined before fetching the result,
-		// autoloading is disabled to save a lot of stupid overhead.
-		if ($this->fetch_type == 'pg_fetch_object')
-		{
-			$this->return_type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
-		}
-		else
-		{
-			$this->return_type = $type;
-		}
-
-		return $this;
-	}
-
-	public function as_array($object = NULL, $type = PGSQL_ASSOC)
-	{
-		return $this->result_array($object, $type);
-	}
-
-	public function result_array($object = NULL, $type = PGSQL_ASSOC)
-	{
-		$rows = array();
-
-		if (is_string($object))
-		{
-			$fetch = $object;
-		}
-		elseif (is_bool($object))
-		{
-			if ($object === TRUE)
-			{
-				$fetch = 'pg_fetch_object';
-
-				// NOTE - The class set by $type must be defined before fetching the result,
-				// autoloading is disabled to save a lot of stupid overhead.
-				$type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
-			}
-			else
-			{
-				$fetch = 'pg_fetch_array';
-			}
-		}
-		else
-		{
-			// Use the default config values
-			$fetch = $this->fetch_type;
-
-			if ($fetch == 'pg_fetch_object')
-			{
-				$type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
-			}
-		}
-
-		if ($this->total_rows)
-		{
-			pg_result_seek($this->result, 0);
-
-			while ($row = $fetch($this->result, NULL, $type))
-			{
-				$rows[] = $row;
-			}
-		}
-
-		return $rows;
-	}
-
-	public function insert_id()
-	{
-		if ($this->insert_id === NULL)
-		{
-			$query = 'SELECT LASTVAL() AS insert_id';
-
-			// Disable error reporting for this, just to silence errors on
-			// tables that have no serial column.
-			$ER = error_reporting(0);
-
-			$result = pg_query($this->link, $query);
-			$insert_id = pg_fetch_array($result, NULL, PGSQL_ASSOC);
-
-			$this->insert_id = $insert_id['insert_id'];
-
-			// Reset error reporting
-			error_reporting($ER);
-		}
-
-		return $this->insert_id;
-	}
-
-	public function seek($offset)
-	{
-		if ($this->offsetExists($offset) AND pg_result_seek($this->result, $offset))
-		{
-			// Set the current row to the offset
-			$this->current_row = $offset;
-
-			return TRUE;
-		}
-
-		return FALSE;
-	}
-
-	public function list_fields()
-	{
-		$field_names = array();
-
-		$fields = pg_num_fields($this->result);
-		for ($i = 0; $i < $fields; ++$i)
-		{
-			$field_names[] = pg_field_name($this->result, $i);
-		}
-
-		return $field_names;
-	}
-
-	/**
-	 * ArrayAccess: offsetGet
-	 */
-	public function offsetGet($offset)
-	{
-		if ( ! $this->seek($offset))
-			return FALSE;
-
-		// Return the row by calling the defined fetching callback
-		$fetch = $this->fetch_type;
-		return $fetch($this->result, NULL, $this->return_type);
-	}
-
-} // End Pgsql_Result Class
-
-/**
- * PostgreSQL Prepared Statement (experimental)
- */
-class Kohana_Pgsql_Statement {
-
-	protected $link = NULL;
-	protected $stmt;
-
-	public function __construct($sql, $link)
-	{
-		$this->link = $link;
-
-		$this->stmt = $this->link->prepare($sql);
-
-		return $this;
-	}
-
-	public function __destruct()
-	{
-		$this->stmt->close();
-	}
-
-	// Sets the bind parameters
-	public function bind_params()
-	{
-		$argv = func_get_args();
-		return $this;
-	}
-
-	// sets the statement values to the bound parameters
-	public function set_vals()
-	{
-		return $this;
-	}
-
-	// Runs the statement
-	public function execute()
-	{
-		return $this;
-	}
-}
diff --git a/system/libraries/drivers/Image.php b/system/libraries/drivers/Image.php
index f89ba953..39936c30 100644
--- a/system/libraries/drivers/Image.php
+++ b/system/libraries/drivers/Image.php
@@ -2,12 +2,12 @@
 /**
  * Image API driver.
  *
- * $Id: Image.php 3769 2008-12-15 00:48:56Z zombor $
+ * $Id: Image.php 4679 2009-11-10 01:45:52Z isaiah $
  *
  * @package    Image
  * @author     Kohana Team
- * @copyright  (c) 2007-2008 Kohana Team
- * @license    http://kohanaphp.com/license.html
+ * @copyright  (c) 2007-2009 Kohana Team
+ * @license    http://kohanaphp.com/license
  */
 abstract class Image_Driver {
 
@@ -102,9 +102,11 @@ abstract class Image_Driver {
 	 * @param   array    actions to execute
 	 * @param   string   destination directory path
 	 * @param   string   destination filename
+	 * @param   boolean  render the image
+	 * @param   string   background color
 	 * @return  boolean
 	 */
-	abstract public function process($image, $actions, $dir, $file);
+	abstract public function process($image, $actions, $dir, $file, $render = FALSE, $background = NULL);
 
 	/**
 	 * Flip an image. Valid directions are horizontal and vertical.
diff --git a/system/libraries/drivers/Image/GD.php b/system/libraries/drivers/Image/GD.php
index be2af4e2..6ffffe8a 100644
--- a/system/libraries/drivers/Image/GD.php
+++ b/system/libraries/drivers/Image/GD.php
@@ -2,12 +2,12 @@
 /**
  * GD Image Driver.
  *
- * $Id: GD.php 3769 2008-12-15 00:48:56Z zombor $
+ * $Id: GD.php 4679 2009-11-10 01:45:52Z isaiah $
  *
  * @package    Image
  * @author     Kohana Team
- * @copyright  (c) 2007-2008 Kohana Team
- * @license    http://kohanaphp.com/license.html
+ * @copyright  (c) 2007-2009 Kohana Team
+ * @license    http://kohanaphp.com/license
  */
 class Image_GD_Driver extends Image_Driver {
 
@@ -20,17 +20,17 @@ class Image_GD_Driver extends Image_Driver {
 	{
 		// Make sure that GD2 is available
 		if ( ! function_exists('gd_info'))
-			throw new Kohana_Exception('image.gd.requires_v2');
+			throw new Kohana_Exception('The Image library requires GD2. Please see http://php.net/gd_info for more information.');
 
 		// Get the GD information
 		$info = gd_info();
 
 		// Make sure that the GD2 is installed
 		if (strpos($info['GD Version'], '2.') === FALSE)
-			throw new Kohana_Exception('image.gd.requires_v2');
+			throw new Kohana_Exception('The Image library requires GD2. Please see http://php.net/gd_info for more information.');
 	}
 
-	public function process($image, $actions, $dir, $file, $render = FALSE)
+	public function process($image, $actions, $dir, $file, $render = FALSE, $background = NULL)
 	{
 		// Set the "create" function
 		switch ($image['type'])
@@ -63,11 +63,11 @@ class Image_GD_Driver extends Image_Driver {
 
 		// Make sure the image type is supported for import
 		if (empty($create) OR ! function_exists($create))
-			throw new Kohana_Exception('image.type_not_allowed', $image['file']);
+			throw new Kohana_Exception('The specified image, :type:, is not an allowed image type.', array(':type:' => $image['file']));
 
 		// Make sure the image type is supported for saving
 		if (empty($save) OR ! function_exists($save))
-			throw new Kohana_Exception('image.type_not_allowed', $dir.$file);
+			throw new Kohana_Exception('The specified image, :type:, is not an allowed image type.', array(':type:' => $dir.$file));
 
 		// Load the image
 		$this->image = $image;
@@ -211,11 +211,11 @@ class Image_GD_Driver extends Image_Driver {
 			// Recalculate the percentage to a pixel size
 			$properties['height'] = round($height * (substr($properties['height'], 0, -1) / 100));
 		}
-		
+
 		// Recalculate the width and height, if they are missing
 		empty($properties['width'])  and $properties['width']  = round($width * $properties['height'] / $height);
 		empty($properties['height']) and $properties['height'] = round($height * $properties['width'] / $width);
-		
+
 		if ($properties['master'] === Image::AUTO)
 		{
 			// Change an automatic master dim to the correct type
@@ -314,7 +314,7 @@ class Image_GD_Driver extends Image_Driver {
 	{
 		// Make sure that the sharpening function is available
 		if ( ! function_exists('imageconvolution'))
-			throw new Kohana_Exception('image.unsupported_method', __FUNCTION__);
+			throw new Kohana_Exception('Your configured driver does not support the :method: image transformation.', array(':method:' => __FUNCTION__));
 
 		// Amount should be in the range of 18-10
 		$amount = round(abs(-18 + ($amount * 0.08)), 2);
@@ -336,23 +336,52 @@ class Image_GD_Driver extends Image_Driver {
 		switch($properties['mime'])
 		{
 			case "image/jpeg":
-			$overlay_img = imagecreatefromjpeg($properties['overlay_file']);
+				$overlay_img = imagecreatefromjpeg($properties['overlay_file']);
 			break;
 
 			case "image/gif":
-			$overlay_img = imagecreatefromgif($properties['overlay_file']);
+				$overlay_img = imagecreatefromgif($properties['overlay_file']);
 			break;
 
 			case "image/png":
-			$overlay_img = imagecreatefrompng($properties['overlay_file']);
+				$overlay_img = imagecreatefrompng($properties['overlay_file']);
 			break;
 		}
 
-		imagecopymerge($this->tmp_image, $overlay_img, $properties['x'], $properties['y'], 0, 0, imagesx($overlay_img), imagesy($overlay_img), $properties['transparency']);
+		$this->imagecopymerge_alpha($this->tmp_image, $overlay_img, $properties['x'], $properties['y'], 0, 0, imagesx($overlay_img), imagesy($overlay_img), $properties['transparency']);
+
 		imagedestroy($overlay_img);
+
 		return TRUE;
 	}
 
+	/**
+	 * A replacement for php's imagecopymerge() function that supports the alpha channel
+	 * See php bug #23815:  http://bugs.php.net/bug.php?id=23815
+	 *
+	 * @param  resource		$dst_im		Destination image link resource
+	 * @param  resource		$src_im		Source image link resource
+	 * @param  integer		$dst_x		x-coordinate of destination point
+	 * @param  integer		$dst_y		y-coordinate of destination point
+	 * @param  integer		$src_x		x-coordinate of source point
+	 * @param  integer		$src_y		y-coordinate of source point
+	 * @param  integer		$src_w		Source width
+	 * @param  integer		$src_h		Source height
+	 * @param  integer		$pct		Transparency percent (0 to 100)
+	 */
+	protected function imagecopymerge_alpha($dst_im, $src_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $pct)
+	{
+		// Create a new blank image the site of our source image
+		$cut = imagecreatetruecolor($src_w, $src_h);
+
+		// Copy the blank image into the destination image where the source goes
+		imagecopy($cut, $dst_im, 0, 0, $dst_x, $dst_y, $src_w, $src_h);
+
+		// Place the source image in the destination image
+		imagecopy($cut, $src_im, 0, 0, $src_x, $src_y, $src_w, $src_h);
+		imagecopymerge($dst_im, $cut, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $pct);
+	}
+
 	protected function properties()
 	{
 		return array(imagesx($this->tmp_image), imagesy($this->tmp_image));
@@ -368,6 +397,16 @@ class Image_GD_Driver extends Image_Driver {
 	 */
 	protected function imagecreatetransparent($width, $height)
 	{
+		if ($width < 1)
+		{
+			$width = 1;
+		}
+
+		if ($height < 1)
+		{
+			$height = 1;
+		}
+
 		if (self::$blank_png === NULL)
 		{
 			// Decode the blank PNG if it has not been done already
diff --git a/system/libraries/drivers/Image/GraphicsMagick.php b/system/libraries/drivers/Image/GraphicsMagick.php
index a8bc4d9b..89b40b41 100644
--- a/system/libraries/drivers/Image/GraphicsMagick.php
+++ b/system/libraries/drivers/Image/GraphicsMagick.php
@@ -4,8 +4,8 @@
  *
  * @package    Image
  * @author     Kohana Team
- * @copyright  (c) 2007-2008 Kohana Team
- * @license    http://kohanaphp.com/license.html
+ * @copyright  (c) 2007-2009 Kohana Team
+ * @license    http://kohanaphp.com/license
  */
 class Image_GraphicsMagick_Driver extends Image_Driver {
 
@@ -31,7 +31,7 @@ class Image_GraphicsMagick_Driver extends Image_Driver {
 		{
 			// Attempt to locate GM by using "which" (only works for *nix!)
 			if ( ! is_file($path = exec('which gm')))
-				throw new Kohana_Exception('image.graphicsmagick.not_found');
+				throw new Kohana_Exception('The GraphicsMagick directory specified does not contain a required program.');
 
 			$config['directory'] = dirname($path);
 		}
@@ -41,7 +41,7 @@ class Image_GraphicsMagick_Driver extends Image_Driver {
 
 		// Check to make sure the provided path is correct
 		if ( ! is_file(realpath($config['directory']).'/gm'.$this->ext))
-			throw new Kohana_Exception('image.graphicsmagick.not_found', 'gm'.$this->ext);
+			throw new Kohana_Exception('The GraphicsMagick directory specified does not contain a required program, :gm:.', array(':gm:' => 'gm'.$this->ext));
 
 
 		// Set the installation directory
@@ -52,8 +52,12 @@ class Image_GraphicsMagick_Driver extends Image_Driver {
 	 * Creates a temporary image and executes the given actions. By creating a
 	 * temporary copy of the image before manipulating it, this process is atomic.
 	 */
-	public function process($image, $actions, $dir, $file, $render = FALSE)
+	public function process($image, $actions, $dir, $file, $render = FALSE, $background = NULL)
 	{
+		// Need to implement $background support
+		if ($background !== NULL)
+			throw new Kohana_Exception('The GraphicsMagick driver does not support setting a background color');
+
 		// We only need the filename
 		$image = $image['file'];
 
diff --git a/system/libraries/drivers/Image/ImageMagick.php b/system/libraries/drivers/Image/ImageMagick.php
index 4b381fd6..31862f75 100644
--- a/system/libraries/drivers/Image/ImageMagick.php
+++ b/system/libraries/drivers/Image/ImageMagick.php
@@ -2,12 +2,12 @@
 /**
  * ImageMagick Image Driver.
  *
- * $Id: ImageMagick.php 3769 2008-12-15 00:48:56Z zombor $
+ * $Id: ImageMagick.php 4679 2009-11-10 01:45:52Z isaiah $
  *
  * @package    Image
  * @author     Kohana Team
- * @copyright  (c) 2007-2008 Kohana Team
- * @license    http://kohanaphp.com/license.html
+ * @copyright  (c) 2007-2009 Kohana Team
+ * @license    http://kohanaphp.com/license
  */
 class Image_ImageMagick_Driver extends Image_Driver {
 
@@ -33,7 +33,7 @@ class Image_ImageMagick_Driver extends Image_Driver {
 		{
 			// Attempt to locate IM by using "which" (only works for *nix!)
 			if ( ! is_file($path = exec('which convert')))
-				throw new Kohana_Exception('image.imagemagick.not_found');
+				throw new Kohana_Exception('The ImageMagick directory specified does not contain a required program.');
 
 			$config['directory'] = dirname($path);
 		}
@@ -43,7 +43,7 @@ class Image_ImageMagick_Driver extends Image_Driver {
 
 		// Check to make sure the provided path is correct
 		if ( ! is_file(realpath($config['directory']).'/convert'.$this->ext))
-			throw new Kohana_Exception('image.imagemagick.not_found', 'convert'.$this->ext);
+			throw new Kohana_Exception('The ImageMagick directory specified does not contain a required program, :im:', array(':im:' => 'convert'.$this->ext));
 
 		// Set the installation directory
 		$this->dir = str_replace('\\', '/', realpath($config['directory'])).'/';
@@ -53,7 +53,7 @@ class Image_ImageMagick_Driver extends Image_Driver {
 	 * Creates a temporary image and executes the given actions. By creating a
 	 * temporary copy of the image before manipulating it, this process is atomic.
 	 */
-	public function process($image, $actions, $dir, $file, $render = FALSE)
+	public function process($image, $actions, $dir, $file, $render = FALSE, $background = NULL)
 	{
 		// We only need the filename
 		$image = $image['file'];
@@ -70,23 +70,34 @@ class Image_ImageMagick_Driver extends Image_Driver {
 		// Use 95 for the default quality
 		empty($quality) and $quality = 95;
 
+		if (is_string($background))
+		{
+			// Set the background color
+			$this->background = escapeshellarg($background);
+		}
+		else
+		{
+			// Use a transparent background
+			$this->background = 'transparent';
+		}
+
 		// All calls to these will need to be escaped, so do it now
 		$this->cmd_image = escapeshellarg($this->tmp_image);
-		$this->new_image = ($render)? $this->cmd_image : escapeshellarg($dir.$file);
+		$this->new_image = $render ? $this->cmd_image : escapeshellarg($dir.$file);
 
 		if ($status = $this->execute($actions))
 		{
 			// Use convert to change the image into its final version. This is
 			// done to allow the file type to change correctly, and to handle
 			// the quality conversion in the most effective way possible.
-			if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -quality '.$quality.'% '.$this->cmd_image.' '.$this->new_image))
+			if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -background '.$this->background.' -flatten -quality '.$quality.'% '.$this->cmd_image.' '.$this->new_image))
 			{
 				$this->errors[] = $error;
 			}
 			else
 			{
 				// Output the image directly to the browser
-				if ($render !== FALSE)
+				if ($render === TRUE)
 				{
 					$contents = file_get_contents($this->tmp_image);
 					switch (substr($file, strrpos($file, '.') + 1))
@@ -122,7 +133,7 @@ class Image_ImageMagick_Driver extends Image_Driver {
 		// Set the IM geometry based on the properties
 		$geometry = escapeshellarg($prop['width'].'x'.$prop['height'].'+'.$prop['left'].'+'.$prop['top']);
 
-		if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -crop '.$geometry.' '.$this->cmd_image.' '.$this->cmd_image))
+		if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -background '.$this->background.' -flatten -crop '.$geometry.' '.$this->cmd_image.' '.$this->cmd_image))
 		{
 			$this->errors[] = $error;
 			return FALSE;
@@ -136,7 +147,7 @@ class Image_ImageMagick_Driver extends Image_Driver {
 		// Convert the direction into a IM command
 		$dir = ($dir === Image::HORIZONTAL) ? '-flop' : '-flip';
 
-		if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' '.$dir.' '.$this->cmd_image.' '.$this->cmd_image))
+		if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -background '.$this->background.' -flatten '.$dir.' '.$this->cmd_image.' '.$this->cmd_image))
 		{
 			$this->errors[] = $error;
 			return FALSE;
@@ -164,7 +175,7 @@ class Image_ImageMagick_Driver extends Image_Driver {
 		}
 
 		// Use "convert" to change the width and height
-		if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -resize '.$dim.' '.$this->cmd_image.' '.$this->cmd_image))
+		if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -background '.$this->background.' -flatten -resize '.$dim.' '.$this->cmd_image.' '.$this->cmd_image))
 		{
 			$this->errors[] = $error;
 			return FALSE;
@@ -175,7 +186,7 @@ class Image_ImageMagick_Driver extends Image_Driver {
 
 	public function rotate($amt)
 	{
-		if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -rotate '.escapeshellarg($amt).' -background transparent '.$this->cmd_image.' '.$this->cmd_image))
+		if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -background '.$this->background.' -flatten -rotate '.escapeshellarg($amt).' -background transparent '.$this->cmd_image.' '.$this->cmd_image))
 		{
 			$this->errors[] = $error;
 			return FALSE;
@@ -195,7 +206,7 @@ class Image_ImageMagick_Driver extends Image_Driver {
 		// Convert the amount to an IM command
 		$sharpen = escapeshellarg($radius.'x'.$sigma.'+'.$amount.'+0');
 
-		if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -unsharp '.$sharpen.' '.$this->cmd_image.' '.$this->cmd_image))
+		if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -background '.$this->background.' -flatten -unsharp '.$sharpen.' '.$this->cmd_image.' '.$this->cmd_image))
 		{
 			$this->errors[] = $error;
 			return FALSE;
diff --git a/system/libraries/drivers/Log.php b/system/libraries/drivers/Log.php
new file mode 100644
index 00000000..cd6dba7d
--- /dev/null
+++ b/system/libraries/drivers/Log.php
@@ -0,0 +1,22 @@
+config = $config;
+	}
+
+	abstract public function save(array $messages);
+}
\ No newline at end of file
diff --git a/system/libraries/drivers/Log/Database.php b/system/libraries/drivers/Log/Database.php
new file mode 100644
index 00000000..19db9747
--- /dev/null
+++ b/system/libraries/drivers/Log/Database.php
@@ -0,0 +1,40 @@
+config['group'])
+						->insert($this->config['table'])
+						->columns(array('date', 'level', 'message'));
+
+		$run_insert = FALSE;
+
+		foreach ($messages AS $message)
+		{
+			if ($this->config['log_levels'][$message['type']] <= $this->config['log_threshold'])
+			{
+				// Add new message to database
+				$insert->values($message);
+
+				// There is data to insert
+				$run_insert = TRUE;
+			}
+		}
+
+		// Update the database
+		if ($run_insert)
+		{
+			$insert->execute();
+		}
+	}
+}
\ No newline at end of file
diff --git a/system/libraries/drivers/Log/File.php b/system/libraries/drivers/Log/File.php
new file mode 100644
index 00000000..6ad565b4
--- /dev/null
+++ b/system/libraries/drivers/Log/File.php
@@ -0,0 +1,44 @@
+config['log_directory'].'/'.date('Y-m-d').'.log'.EXT;
+
+		if ( ! is_file($filename))
+		{
+			// Write the SYSPATH checking header
+			file_put_contents($filename,
+				''.PHP_EOL.PHP_EOL);
+
+			// Prevent external writes
+			chmod($filename, $this->config['posix_permissions']);
+		}
+
+		foreach ($messages AS $message)
+		{
+			if ($this->config['log_levels'][$message['type']] <= $this->config['log_threshold'])
+			{
+				// Add a new message line
+				$messages_to_write[] = date($this->config['date_format'], $message['date']).' --- '.$message['type'].': '.$message['message'];
+			}
+		}
+
+		if ( ! empty($messages_to_write))
+		{
+			// Write messages to log file
+			file_put_contents($filename, implode(PHP_EOL, $messages_to_write).PHP_EOL, FILE_APPEND);
+		}
+	}
+}
\ No newline at end of file
diff --git a/system/libraries/drivers/Log/Syslog.php b/system/libraries/drivers/Log/Syslog.php
new file mode 100644
index 00000000..5da5d255
--- /dev/null
+++ b/system/libraries/drivers/Log/Syslog.php
@@ -0,0 +1,34 @@
+ LOG_ERR,
+	                                 'alert' => LOG_WARNING,
+	                                 'info'  => LOG_INFO,
+	                                 'debug' => LOG_DEBUG);
+
+	public function save(array $messages)
+	{
+		// Open the connection to syslog
+		openlog($this->config['ident'], LOG_CONS, LOG_USER);
+
+		do
+		{
+			// Load the next message
+			list ($date, $type, $text) = array_shift($messages);
+
+			syslog($this->syslog_levels[$type], $text);
+		}
+		while ( ! empty($messages));
+
+		// Close connection to syslog
+		closelog();
+	}
+}
\ No newline at end of file
diff --git a/system/libraries/drivers/Session.php b/system/libraries/drivers/Session.php
index fb58c8d3..759ccd84 100644
--- a/system/libraries/drivers/Session.php
+++ b/system/libraries/drivers/Session.php
@@ -2,12 +2,12 @@
 /**
  * Session driver interface
  *
- * $Id: Session.php 3769 2008-12-15 00:48:56Z zombor $
+ * $Id: Session.php 4679 2009-11-10 01:45:52Z isaiah $
  *
  * @package    Core
  * @author     Kohana Team
- * @copyright  (c) 2007-2008 Kohana Team
- * @license    http://kohanaphp.com/license.html
+ * @copyright  (c) 2007-2009 Kohana Team
+ * @license    http://kohanaphp.com/license
  */
 interface Session_Driver {
 
diff --git a/system/libraries/drivers/Session/Cache.php b/system/libraries/drivers/Session/Cache.php
index 45e49495..c0d4d0a4 100644
--- a/system/libraries/drivers/Session/Cache.php
+++ b/system/libraries/drivers/Session/Cache.php
@@ -10,12 +10,12 @@
  * Lifetime does not need to be set as it is
  * overridden by the session expiration setting.
  *
- * $Id: Cache.php 4431 2009-07-01 03:41:41Z kiall $
+ * $Id: Cache.php 4679 2009-11-10 01:45:52Z isaiah $
  *
  * @package    Core
  * @author     Kohana Team
- * @copyright  (c) 2007-2008 Kohana Team
- * @license    http://kohanaphp.com/license.html
+ * @copyright  (c) 2007-2009 Kohana Team
+ * @license    http://kohanaphp.com/license
  */
 class Session_Cache_Driver implements Session_Driver {
 
@@ -30,7 +30,7 @@ class Session_Cache_Driver implements Session_Driver {
 			$this->encrypt = new Encrypt;
 		}
 
-		Kohana::log('debug', 'Session Cache Driver Initialized');
+		Kohana_Log::add('debug', 'Session Cache Driver Initialized');
 	}
 
 	public function open($path, $name)
@@ -48,7 +48,7 @@ class Session_Cache_Driver implements Session_Driver {
 
 			// Test the config group name
 			if (($config = Kohana::config('cache.'.$config)) === NULL)
-				throw new Kohana_Exception('cache.undefined_group', $name);
+				throw new Kohana_Exception('The :group: group is not defined in your configuration.', array(':group:' => $name));
 		}
 
 		$config['lifetime'] = (Kohana::config('session.expiration') == 0) ? 86400 : Kohana::config('session.expiration');
diff --git a/system/libraries/drivers/Session/Cookie.php b/system/libraries/drivers/Session/Cookie.php
index 4cf18fc2..ef575cab 100644
--- a/system/libraries/drivers/Session/Cookie.php
+++ b/system/libraries/drivers/Session/Cookie.php
@@ -2,12 +2,12 @@
 /**
  * Session cookie driver.
  *
- * $Id: Cookie.php 4431 2009-07-01 03:41:41Z kiall $
+ * $Id: Cookie.php 4679 2009-11-10 01:45:52Z isaiah $
  *
  * @package    Core
  * @author     Kohana Team
- * @copyright  (c) 2007-2008 Kohana Team
- * @license    http://kohanaphp.com/license.html
+ * @copyright  (c) 2007-2009 Kohana Team
+ * @license    http://kohanaphp.com/license
  */
 class Session_Cookie_Driver implements Session_Driver {
 
@@ -23,7 +23,7 @@ class Session_Cookie_Driver implements Session_Driver {
 			$this->encrypt = Encrypt::instance();
 		}
 
-		Kohana::log('debug', 'Session Cookie Driver Initialized');
+		Kohana_Log::add('debug', 'Session Cookie Driver Initialized');
 	}
 
 	public function open($path, $name)
@@ -55,7 +55,7 @@ class Session_Cookie_Driver implements Session_Driver {
 
 		if (strlen($data) > 4048)
 		{
-			Kohana::log('error', 'Session ('.$id.') data exceeds the 4KB limit, ignoring write.');
+			Kohana_Log::add('error', 'Session ('.$id.') data exceeds the 4KB limit, ignoring write.');
 			return FALSE;
 		}
 
diff --git a/system/libraries/drivers/Session/Database.php b/system/libraries/drivers/Session/Database.php
index 490875a1..cbe76001 100644
--- a/system/libraries/drivers/Session/Database.php
+++ b/system/libraries/drivers/Session/Database.php
@@ -2,12 +2,12 @@
 /**
  * Session database driver.
  *
- * $Id: Database.php 4431 2009-07-01 03:41:41Z kiall $
+ * $Id: Database.php 4679 2009-11-10 01:45:52Z isaiah $
  *
  * @package    Core
  * @author     Kohana Team
- * @copyright  (c) 2007-2008 Kohana Team
- * @license    http://kohanaphp.com/license.html
+ * @copyright  (c) 2007-2009 Kohana Team
+ * @license    http://kohanaphp.com/license
  */
 class Session_Database_Driver implements Session_Driver {
 
@@ -58,10 +58,7 @@ class Session_Database_Driver implements Session_Driver {
 			}
 		}
 
-		// Load database
-		$this->db = Database::instance($this->db);
-
-		Kohana::log('debug', 'Session Database Driver Initialized');
+		Kohana_Log::add('debug', 'Session Database Driver Initialized');
 	}
 
 	public function open($path, $name)
@@ -77,7 +74,11 @@ class Session_Database_Driver implements Session_Driver {
 	public function read($id)
 	{
 		// Load the session
-		$query = $this->db->from($this->table)->where('session_id', $id)->limit(1)->get()->result(TRUE);
+		$query = db::select('data')
+			->from($this->table)
+			->where('session_id', '=', $id)
+			->limit(1)
+			->execute($this->db);
 
 		if ($query->count() === 0)
 		{
@@ -111,7 +112,8 @@ class Session_Database_Driver implements Session_Driver {
 		if ($this->session_id === NULL)
 		{
 			// Insert a new session
-			$query = $this->db->insert($this->table, $data);
+			$query = db::insert($this->table, $data)
+				->execute($this->db);
 		}
 		elseif ($id === $this->session_id)
 		{
@@ -119,12 +121,18 @@ class Session_Database_Driver implements Session_Driver {
 			unset($data['session_id']);
 
 			// Update the existing session
-			$query = $this->db->update($this->table, $data, array('session_id' => $id));
+			$query = db::update($this->table)
+				->set($data)
+				->where('session_id', '=', $id)
+				->execute($this->db);
 		}
 		else
 		{
 			// Update the session and id
-			$query = $this->db->update($this->table, $data, array('session_id' => $this->session_id));
+			$query = db::update($this->table)
+				->set($data)
+				->where('session_id', '=', $this->session_id)
+				->execute($this->db);
 
 			// Set the new session id
 			$this->session_id = $id;
@@ -136,7 +144,9 @@ class Session_Database_Driver implements Session_Driver {
 	public function destroy($id)
 	{
 		// Delete the requested session
-		$this->db->delete($this->table, array('session_id' => $id));
+		db::delete($this->table)
+			->where('session_id', '=', $id)
+			->execute($this->db);
 
 		// Session id is no longer valid
 		$this->session_id = NULL;
@@ -156,9 +166,11 @@ class Session_Database_Driver implements Session_Driver {
 	public function gc($maxlifetime)
 	{
 		// Delete all expired sessions
-		$query = $this->db->delete($this->table, array('last_activity <' => time() - $maxlifetime));
+		$query = db::delete($this->table)
+			->where('last_activity', '<', time() - $maxlifetime)
+			->execute($this->db);
 
-		Kohana::log('debug', 'Session garbage collected: '.$query->count().' row(s) deleted.');
+		Kohana_Log::add('debug', 'Session garbage collected: '.$query->count().' row(s) deleted.');
 
 		return TRUE;
 	}
-- 
cgit v1.2.3
From 9285c8c66c530196399eb05bb5561c3fa5538335 Mon Sep 17 00:00:00 2001
From: Bharat Mediratta 
Date: Mon, 21 Dec 2009 20:05:27 -0800
Subject: Updated Kohana to r4724
---
 system/config/cache.php                     |    3 +
 system/config/database.php                  |    3 +-
 system/core/Kohana.php                      |   39 +-
 system/core/Kohana_Exception.php            |    4 +-
 system/helpers/form.php                     |   28 +-
 system/helpers/inflector.php                |    6 +-
 system/helpers/request.php                  |    4 +-
 system/helpers/security.php                 |   13 +-
 system/helpers/text.php                     |   46 +-
 system/helpers/url.php                      |   21 +-
 system/helpers/utf8.php                     |    2 +-
 system/libraries/Cache.php                  |   87 +-
 system/libraries/Controller.php             |    8 +-
 system/libraries/Database.php               |   23 +-
 system/libraries/Database_Builder.php       |  103 +-
 system/libraries/Database_Mysql.php         |    9 +-
 system/libraries/Database_Mysqli.php        |   32 +-
 system/libraries/Input.php                  |    8 +-
 system/libraries/Kohana_PHP_Exception.php   |    4 +-
 system/libraries/ORM.php                    |   56 +-
 system/libraries/Profiler.php               |    4 +-
 system/libraries/Router.php                 |   10 +-
 system/libraries/Validation.php             |  155 +-
 system/libraries/drivers/Cache/File.php     |    4 +-
 system/libraries/drivers/Cache/Memcache.php |    7 +-
 system/libraries/drivers/Cache/Xcache.php   |    2 +-
 system/messages/core.php                    |    9 +-
 system/messages/validation/default.php      |   17 +
 system/vendor/Markdown.php                  | 2909 ---------------------------
 29 files changed, 458 insertions(+), 3158 deletions(-)
 create mode 100644 system/messages/validation/default.php
 delete mode 100644 system/vendor/Markdown.php
(limited to 'system/libraries/drivers')
diff --git a/system/config/cache.php b/system/config/cache.php
index 68682c0a..76af4f6a 100644
--- a/system/config/cache.php
+++ b/system/config/cache.php
@@ -19,10 +19,13 @@
  *             thirty minutes. Specific lifetime can also be set when creating a new cache.
  *             Setting this to 0 will never automatically delete caches.
  *
+ *  prefix   - Adds a prefix to all keys and tags. This can have a severe performance impact.
+ *
  */
 $config['default'] = array
 (
 	'driver'   => 'file',
 	'params'   => array('directory' => APPPATH.'cache', 'gc_probability' => 1000),
 	'lifetime' => 1800,
+	'prefix'   => NULL
 );
diff --git a/system/config/database.php b/system/config/database.php
index 2e53fa2b..36e4614c 100644
--- a/system/config/database.php
+++ b/system/config/database.php
@@ -35,7 +35,8 @@ $config['default'] = array
 		'host'     => 'localhost',
 		'port'     => FALSE,
 		'socket'   => FALSE,
-		'database' => 'kohana'
+		'database' => 'kohana',
+		'params'   => NULL,
 	),
 	'character_set' => 'utf8',
 	'table_prefix'  => '',
diff --git a/system/core/Kohana.php b/system/core/Kohana.php
index 5258d635..740adb80 100644
--- a/system/core/Kohana.php
+++ b/system/core/Kohana.php
@@ -2,7 +2,7 @@
 /**
  * Provides Kohana-specific helper functions. This is where the magic happens!
  *
- * $Id: Kohana.php 4679 2009-11-10 01:45:52Z isaiah $
+ * $Id: Kohana.php 4724 2009-12-21 16:28:54Z isaiah $
  *
  * @package    Core
  * @author     Kohana Team
@@ -45,6 +45,9 @@ abstract class Kohana_Core {
 	protected static $internal_cache_key;
 	protected static $internal_cache_encrypt;
 
+	// Server API that PHP is using. Allows testing of different APIs.
+	public static $server_api = PHP_SAPI;
+
 	/**
 	 * Sets up the PHP environment. Adds error/exception handling, output
 	 * buffering, and adds an auto-loading method for loading classes.
@@ -162,6 +165,35 @@ abstract class Kohana_Core {
 		// Set and validate the timezone
 		date_default_timezone_set(Kohana::config('locale.timezone'));
 
+		// register_globals is enabled
+		if (ini_get('register_globals'))
+		{
+			if (isset($_REQUEST['GLOBALS']))
+			{
+				// Prevent GLOBALS override attacks
+				exit('Global variable overload attack.');
+			}
+
+			// Destroy the REQUEST global
+			$_REQUEST = array();
+
+			// These globals are standard and should not be removed
+			$preserve = array('GLOBALS', '_REQUEST', '_GET', '_POST', '_FILES', '_COOKIE', '_SERVER', '_ENV', '_SESSION');
+
+			// This loop has the same effect as disabling register_globals
+			foreach (array_diff(array_keys($GLOBALS), $preserve) as $key)
+			{
+				global $$key;
+				$$key = NULL;
+
+				// Unset the global variable
+				unset($GLOBALS[$key], $$key);
+			}
+
+			// Warn the developer about register globals
+			Kohana_Log::add('debug', 'Disable register_globals! It is evil and deprecated: http://php.net/register_globals');
+		}
+
 		// Enable Kohana routing
 		Event::add('system.routing', array('Router', 'find_uri'));
 		Event::add('system.routing', array('Router', 'setup'));
@@ -602,7 +634,7 @@ abstract class Kohana_Core {
 				header('Content-Encoding: '.$compress);
 
 				// Sending Content-Length in CGI can result in unexpected behavior
-				if (stripos(PHP_SAPI, 'cgi') === FALSE)
+				if (stripos(Kohana::$server_api, 'cgi') === FALSE)
 				{
 					header('Content-Length: '.strlen($output));
 				}
@@ -876,9 +908,6 @@ abstract class Kohana_Core {
 		$group = explode('.', $key, 2);
 		$group = $group[0];
 
-		// Get locale name
-		$locale = Kohana::config('locale.language.0');
-
 		if ( ! isset(Kohana::$internal_cache['messages'][$group]))
 		{
 			// Messages for this group
diff --git a/system/core/Kohana_Exception.php b/system/core/Kohana_Exception.php
index cdb06a62..2eb28f75 100644
--- a/system/core/Kohana_Exception.php
+++ b/system/core/Kohana_Exception.php
@@ -2,7 +2,7 @@
 /**
  * Kohana Exceptions
  *
- * $Id: Kohana_Exception.php 4679 2009-11-10 01:45:52Z isaiah $
+ * $Id: Kohana_Exception.php 4692 2009-12-04 15:59:44Z cbandy $
  *
  * @package    Core
  * @author     Kohana Team
@@ -197,7 +197,7 @@ class Kohana_Exception_Core extends Exception {
 			echo Kohana_Exception::text($e), "\n";
 		}
 
-		if (PHP_SAPI === 'cli')
+		if (Kohana::$server_api === 'cli')
 		{
 			// Exit with an error status
 			exit(1);
diff --git a/system/helpers/form.php b/system/helpers/form.php
index 901edc91..4225b1b6 100644
--- a/system/helpers/form.php
+++ b/system/helpers/form.php
@@ -2,7 +2,7 @@
 /**
  * Form helper class.
  *
- * $Id: form.php 4679 2009-11-10 01:45:52Z isaiah $
+ * $Id: form.php 4699 2009-12-08 18:45:14Z isaiah $
  *
  * @package    Core
  * @author     Kohana Team
@@ -421,32 +421,6 @@ class form_Core {
 		if (empty($attr))
 			return '';
 
-		if (isset($attr['name']) AND empty($attr['id']) AND strpos($attr['name'], '[') === FALSE)
-		{
-			if ($type === NULL AND ! empty($attr['type']))
-			{
-				// Set the type by the attributes
-				$type = $attr['type'];
-			}
-
-			switch ($type)
-			{
-				case 'text':
-				case 'textarea':
-				case 'password':
-				case 'select':
-				case 'checkbox':
-				case 'file':
-				case 'image':
-				case 'button':
-				case 'submit':
-				case 'hidden':
-					// Only specific types of inputs use name to id matching
-					$attr['id'] = $attr['name'];
-				break;
-			}
-		}
-
 		$order = array
 		(
 			'action',
diff --git a/system/helpers/inflector.php b/system/helpers/inflector.php
index 9bd281db..5a2910c0 100644
--- a/system/helpers/inflector.php
+++ b/system/helpers/inflector.php
@@ -2,7 +2,7 @@
 /**
  * Inflector helper class.
  *
- * $Id: inflector.php 4679 2009-11-10 01:45:52Z isaiah $
+ * $Id: inflector.php 4722 2009-12-19 17:47:34Z isaiah $
  *
  * @package    Core
  * @author     Kohana Team
@@ -241,9 +241,9 @@ class inflector_Core {
 	}
 
 	/**
-	 * Makes an underscored or dashed phrase human-reable.
+	 * Makes an underscored or dashed phrase human-readable.
 	 *
-	 * @param   string  phrase to make human-reable
+	 * @param   string  phrase to make human-readable
 	 * @return  string
 	 */
 	public static function humanize($str)
diff --git a/system/helpers/request.php b/system/helpers/request.php
index 4770d64b..31afee4e 100644
--- a/system/helpers/request.php
+++ b/system/helpers/request.php
@@ -2,7 +2,7 @@
 /**
  * Request helper class.
  *
- * $Id: request.php 4679 2009-11-10 01:45:52Z isaiah $
+ * $Id: request.php 4692 2009-12-04 15:59:44Z cbandy $
  *
  * @package    Core
  * @author     Kohana Team
@@ -61,7 +61,7 @@ class request_Core {
 	 */
 	public static function protocol()
 	{
-		if (PHP_SAPI === 'cli')
+		if (Kohana::$server_api === 'cli')
 		{
 			return NULL;
 		}
diff --git a/system/helpers/security.php b/system/helpers/security.php
index 9eb82a58..33e5118e 100644
--- a/system/helpers/security.php
+++ b/system/helpers/security.php
@@ -2,7 +2,7 @@
 /**
  * Security helper class.
  *
- * $Id: security.php 4679 2009-11-10 01:45:52Z isaiah $
+ * $Id: security.php 4698 2009-12-08 18:39:33Z isaiah $
  *
  * @package    Core
  * @author     Kohana Team
@@ -34,15 +34,4 @@ class security_Core {
 		return preg_replace('#
\s]*)["\']?[^>]*)?>#is', '$1', $str);
 	}
 
-	/**
-	 * Remove PHP tags from a string.
-	 *
-	 * @param   string  string to sanitize
-	 * @return  string
-	 */
-	public static function encode_php_tags($str)
-	{
-		return str_replace(array('', '?>'), array('<?', '?>'), $str);
-	}
-
 } // End security
\ No newline at end of file
diff --git a/system/helpers/text.php b/system/helpers/text.php
index 66bcd243..ed7f9cbf 100644
--- a/system/helpers/text.php
+++ b/system/helpers/text.php
@@ -2,7 +2,7 @@
 /**
  * Text helper class.
  *
- * $Id: text.php 4679 2009-11-10 01:45:52Z isaiah $
+ * $Id: text.php 4689 2009-12-02 01:39:24Z isaiah $
  *
  * @package    Core
  * @author     Kohana Team
@@ -298,27 +298,37 @@ class text_Core {
 	 */
 	public static function auto_link_urls($text)
 	{
-		// Finds all http/https/ftp/ftps links that are not part of an existing html anchor
-		if (preg_match_all('~\b(?)(?:ht|f)tps?://\S+(?:/|\b)~i', $text, $matches))
-		{
-			foreach ($matches[0] as $match)
-			{
-				// Replace each link with an anchor
-				$text = str_replace($match, html::anchor($match), $text);
-			}
-		}
 
-		// Find all naked www.links.com (without http://)
-		if (preg_match_all('~\b(? $name));
+				throw new Cache_Exception('The :group: group is not defined in your configuration.', array(':group:' => $name));
 		}
 
 		if (is_array($config))
@@ -74,7 +74,7 @@ class Cache_Core {
 
 		// Load the driver
 		if ( ! Kohana::auto_load($driver))
-			throw new Kohana_Exception('The :driver: driver for the :class: library could not be found',
+			throw new Cache_Exception('The :driver: driver for the :class: library could not be found',
 									   array(':driver:' => $this->config['driver'], ':class:' => get_class($this)));
 
 		// Initialize the driver
@@ -82,7 +82,7 @@ class Cache_Core {
 
 		// Validate the driver
 		if ( ! ($this->driver instanceof Cache_Driver))
-			throw new Kohana_Exception('The :driver: driver for the :library: library must implement the :interface: interface',
+			throw new Cache_Exception('The :driver: driver for the :library: library must implement the :interface: interface',
 									   array(':driver:' => $this->config['driver'], ':library:' => get_class($this), ':interface:' => 'Cache_Driver'));
 
 		Kohana_Log::add('debug', 'Cache Library initialized');
@@ -103,6 +103,16 @@ class Cache_Core {
 			$key = array($key => $value);
 		}
 
+		if ($this->config['prefix'] !== NULL)
+		{
+			$key = $this->add_prefix($key);
+			
+			if ($tags !== NULL)
+			{
+				$tags = $this->add_prefix($tags, FALSE);
+			}
+		}
+
 		return $this->driver->set($key, $tags, $lifetime);
 	}
 
@@ -119,6 +129,17 @@ class Cache_Core {
 			$single = TRUE;
 		}
 
+		if ($this->config['prefix'] !== NULL)
+		{
+			$keys = $this->add_prefix($keys, FALSE);
+			
+			if ( ! $single)
+			{
+			    return $this->strip_prefix($this->driver->get($keys, $single));
+			}
+
+		}
+		
 		return $this->driver->get($keys, $single);
 	}
 
@@ -132,7 +153,15 @@ class Cache_Core {
 			$tags = array($tags);
 		}
 
-		return $this->driver->get_tag($tags);
+		if ($this->config['prefix'] !== NULL)
+		{
+		    $tags = $this->add_prefix($tags, FALSE);
+		    return $this->strip_prefix($this->driver->get_tag($tags));
+		}
+		else
+		{
+		    return $this->driver->get_tag($tags);
+		}
 	}
 
 	/**
@@ -145,6 +174,11 @@ class Cache_Core {
 			$keys = array($keys);
 		}
 
+		if ($this->config['prefix'] !== NULL)
+		{
+			$keys = $this->add_prefix($keys, FALSE);
+		}
+
 		return $this->driver->delete($keys);
 	}
 
@@ -158,6 +192,11 @@ class Cache_Core {
 			$tags = array($tags);
 		}
 
+		if ($this->config['prefix'] !== NULL)
+		{
+			$tags = $this->add_prefix($tags, FALSE);
+		}
+
 		return $this->driver->delete_tag($tags);
 	}
 
@@ -168,4 +207,44 @@ class Cache_Core {
 	{
 		return $this->driver->delete_all();
 	}
+
+	/**
+	 * Add a prefix to keys or tags
+	 */
+	protected function add_prefix($array, $to_key = TRUE)
+	{
+		$out = array();
+
+		foreach($array as $key => $value)
+		{
+			if ($to_key)
+			{
+				$out[$this->config['prefix'].$key] = $value;
+			}
+			else
+			{
+				$out[$key] = $this->config['prefix'].$value;
+			}
+		}
+
+		return $out;
+	}
+
+	/**
+	 * Strip a prefix to keys or tags
+	 */
+	protected function strip_prefix($array)
+	{
+		$out = array();
+
+		$start = strlen($this->config['prefix']);
+
+		foreach($array as $key => $value)
+		{
+			$out[substr($key, $start)] = $value;
+		}
+
+		return $out;
+	}
+
 } // End Cache Library
\ No newline at end of file
diff --git a/system/libraries/Controller.php b/system/libraries/Controller.php
index da84bfc1..830c06e5 100644
--- a/system/libraries/Controller.php
+++ b/system/libraries/Controller.php
@@ -3,7 +3,7 @@
  * Kohana Controller class. The controller class must be extended to work
  * properly, so this class is defined as abstract.
  *
- * $Id: Controller.php 4679 2009-11-10 01:45:52Z isaiah $
+ * $Id: Controller.php 4721 2009-12-17 23:02:07Z isaiah $
  *
  * @package    Core
  * @author     Kohana Team
@@ -27,12 +27,6 @@ abstract class Controller_Core {
 			// Set the instance to the first controller loaded
 			Kohana::$instance = $this;
 		}
-
-		// URI should always be available
-		$this->uri = URI::instance();
-
-		// Input should always be available
-		$this->input = Input::instance();
 	}
 
 	/**
diff --git a/system/libraries/Database.php b/system/libraries/Database.php
index d3716c59..38a38fbf 100644
--- a/system/libraries/Database.php
+++ b/system/libraries/Database.php
@@ -1,9 +1,9 @@
 cache instanceof Cache)
+		if ($this->cache instanceof Cache AND ($type == NULL OR $type == Database::CROSS_REQUEST))
 		{
 			// Using cross-request Cache library
 			if ($sql === TRUE)
@@ -370,7 +373,7 @@ abstract class Database_Core {
 				$this->cache->delete_all();
 			}
 		}
-		elseif (is_array($this->cache))
+		elseif (is_array($this->cache) AND ($type == NULL OR $type == Database::PER_REQUEST))
 		{
 			// Using per-request memory cache
 			if ($sql === TRUE)
diff --git a/system/libraries/Database_Builder.php b/system/libraries/Database_Builder.php
index 5a5a058c..4e6951e7 100644
--- a/system/libraries/Database_Builder.php
+++ b/system/libraries/Database_Builder.php
@@ -29,6 +29,7 @@ class Database_Builder_Core {
 	protected $columns  = array();
 	protected $values   = array();
 	protected $type;
+	protected $distinct = FALSE;
 
 	// TTL for caching (using Cache library)
 	protected $ttl      = FALSE;
@@ -77,7 +78,8 @@ class Database_Builder_Core {
 		if ($this->type === Database::SELECT)
 		{
 			// SELECT columns FROM table
-			$sql = 'SELECT '.$this->compile_select();
+			$sql = $this->distinct ? 'SELECT DISTINCT ' : 'SELECT ';
+			$sql .= $this->compile_select();
 
 			if ( ! empty($this->from))
 			{
@@ -146,7 +148,13 @@ class Database_Builder_Core {
 
 		foreach ($this->select as $alias => $name)
 		{
-			if (is_string($alias))
+			if ($name instanceof Database_Builder)
+			{
+				// Using a subquery
+				$name->db = $this->db;
+				$vals[] = '('.(string) $name.') AS '.$this->db->quote_column($alias);
+			}
+			elseif (is_string($alias))
 			{
 				$vals[] = $this->db->quote_column($name, $alias);
 			}
@@ -374,28 +382,48 @@ class Database_Builder_Core {
 	/**
 	 * Add conditions to the HAVING clause (AND)
 	 *
-	 * @param  mixed   Column name or array of columns => vals
+	 * @param  mixed   Column name or array of triplets
 	 * @param  string  Operation to perform
 	 * @param  mixed   Value
 	 * @return Database_Builder
 	 */
 	public function and_having($columns, $op = '=', $value = NULL)
 	{
-		$this->having[] = array('AND' => array($columns, $op, $value));
+		if (is_array($columns))
+		{
+			foreach ($columns as $column)
+			{
+				$this->having[] = array('AND' => $column);
+			}
+		}
+		else
+		{
+			$this->having[] = array('AND' => array($columns, $op, $value));
+		}
 		return $this;
 	}
 
 	/**
 	 * Add conditions to the HAVING clause (OR)
 	 *
-	 * @param  mixed   Column name or array of columns => vals
+	 * @param  mixed   Column name or array of triplets
 	 * @param  string  Operation to perform
 	 * @param  mixed   Value
 	 * @return Database_Builder
 	 */
 	public function or_having($columns, $op = '=', $value = NULL)
 	{
-		$this->having[] = array('OR' => array($columns, $op, $value));
+		if (is_array($columns))
+		{
+			foreach ($columns as $column)
+			{
+				$this->having[] = array('OR' => $column);
+			}
+		}
+		else
+		{
+			$this->having[] = array('OR' => array($columns, $op, $value));
+		}
 		return $this;
 	}
 
@@ -408,13 +436,25 @@ class Database_Builder_Core {
 	 */
 	public function order_by($columns, $direction = NULL)
 	{
-		if (is_string($columns))
+		if (is_array($columns))
 		{
-			$columns = array($columns => $direction);
+			foreach ($columns as $column => $direction)
+			{
+				if (is_string($column))
+				{
+					$this->order_by[] = array($column => $direction);
+				}
+				else
+				{
+					// $direction is the column name when the array key is numeric
+					$this->order_by[] = array($direction => NULL);
+				}
+			}
+		}
+		else
+		{
+			$this->order_by[] = array($columns => $direction);
 		}
-
-		$this->order_by[] = $columns;
-
 		return $this;
 	}
 
@@ -542,28 +582,48 @@ class Database_Builder_Core {
 	/**
 	 * Add conditions to the WHERE clause (AND)
 	 *
-	 * @param  mixed   Column name or array of columns => vals
+	 * @param  mixed   Column name or array of triplets
 	 * @param  string  Operation to perform
 	 * @param  mixed   Value
 	 * @return Database_Builder
 	 */
 	public function and_where($columns, $op = '=', $value = NULL)
 	{
-		$this->where[] = array('AND' => array($columns, $op, $value));
+		if (is_array($columns))
+		{
+			foreach ($columns as $column)
+			{
+				$this->where[] = array('AND' => $column);
+			}
+		}
+		else
+		{
+			$this->where[] = array('AND' => array($columns, $op, $value));
+		}
 		return $this;
 	}
 
 	/**
 	 * Add conditions to the WHERE clause (OR)
 	 *
-	 * @param  mixed   Column name or array of columns => vals
+	 * @param  mixed   Column name or array of triplets
 	 * @param  string  Operation to perform
 	 * @param  mixed   Value
 	 * @return Database_Builder
 	 */
 	public function or_where($columns, $op = '=', $value = NULL)
 	{
-		$this->where[] = array('OR' => array($columns, $op, $value));
+		if (is_array($columns))
+		{
+			foreach ($columns as $column)
+			{
+				$this->where[] = array('OR' => $column);
+			}
+		}
+		else
+		{
+			$this->where[] = array('OR' => array($columns, $op, $value));
+		}
 		return $this;
 	}
 
@@ -779,6 +839,19 @@ class Database_Builder_Core {
 		return $this;
 	}
 
+	/**
+	 * Create a SELECT query and specify selected columns
+	 *
+	 * @param   string|array    column name or array(alias => column)
+	 * @return  Database_Builder
+	 */
+	public function select_distinct($columns = NULL)
+	{
+		$this->select($columns);
+		$this->distinct = TRUE;
+		return $this;
+	}
+
 	/**
 	 * Create an UPDATE query
 	 *
diff --git a/system/libraries/Database_Mysql.php b/system/libraries/Database_Mysql.php
index cb531194..a5775037 100644
--- a/system/libraries/Database_Mysql.php
+++ b/system/libraries/Database_Mysql.php
@@ -2,7 +2,7 @@
 /**
  * MySQL database connection.
  *
- * $Id: Database_Mysql.php 4684 2009-11-18 14:26:48Z isaiah $
+ * $Id: Database_Mysql.php 4712 2009-12-10 21:47:09Z cbandy $
  *
  * @package    Kohana
  * @author     Kohana Team
@@ -31,16 +31,15 @@ class Database_Mysql_Core extends Database {
 
 		extract($this->config['connection']);
 
-		// Set the connection type
-		$connect = ($this->config['persistent'] === TRUE) ? 'mysql_pconnect' : 'mysql_connect';
-
 		$host = isset($host) ? $host : $socket;
 		$port = isset($port) ? ':'.$port : '';
 
 		try
 		{
 			// Connect to the database
-			$this->connection = $connect($host.$port, $user, $pass, TRUE);
+			$this->connection = ($this->config['persistent'] === TRUE)
+				? mysql_pconnect($host.$port, $user, $pass, $params)
+				: mysql_connect($host.$port, $user, $pass, TRUE, $params);
 		}
 		catch (Kohana_PHP_Exception $e)
 		{
diff --git a/system/libraries/Database_Mysqli.php b/system/libraries/Database_Mysqli.php
index 523fcb19..08ed99df 100644
--- a/system/libraries/Database_Mysqli.php
+++ b/system/libraries/Database_Mysqli.php
@@ -2,7 +2,7 @@
 /**
  * MySQL database connection.
  * 
- * $Id: Database_Mysqli.php 4679 2009-11-10 01:45:52Z isaiah $
+ * $Id: Database_Mysqli.php 4712 2009-12-10 21:47:09Z cbandy $
  * 
  * @package    Kohana
  * @author     Kohana Team
@@ -29,29 +29,29 @@ class Database_Mysqli_Core extends Database_Mysql {
 
 		$host = isset($host) ? $host : $socket;
 
-		if($this->connection = new mysqli($host, $user, $pass, $database, $port)) {
+		$mysqli = mysqli_init();
 			
-			if (isset($this->config['character_set']))
-			{
-				// Set the character set
-				$this->set_charset($this->config['character_set']);
-			}
+		if ( ! $mysqli->real_connect($host, $user, $pass, $database, $port, $socket, $params))
+			throw new Database_Exception('#:errno: :error',
+				array(':error' => $mysqli->connect_error, ':errno' => $mysqli->connect_errno));
 			
-			// Clear password after successful connect
-			$this->db_config['connection']['pass'] = NULL;
+		$this->connection = $mysqli;
 			
-			return $this->connection;
+		if (isset($this->config['character_set']))
+		{
+			// Set the character set
+			$this->set_charset($this->config['character_set']);
 		}
-
-		// Unable to connect to the database
-			throw new Database_Exception('#:errno: :error',
-				array(':error' => $this->connection->connect_error,
-				':errno' => $this->connection->connect_errno));
 	}
 
 	public function disconnect()
 	{
-		return is_object($this->connection) and $this->connection->close();
+		if (is_object($this->connection))
+		{
+			$this->connection->close();
+		}
+
+		$this->connection = NULL;
 	}
 
 	public function set_charset($charset)
diff --git a/system/libraries/Input.php b/system/libraries/Input.php
index 83f0ed17..04403854 100644
--- a/system/libraries/Input.php
+++ b/system/libraries/Input.php
@@ -2,7 +2,7 @@
 /**
  * Input library.
  *
- * $Id: Input.php 4680 2009-11-10 01:57:00Z isaiah $
+ * $Id: Input.php 4720 2009-12-17 21:15:03Z isaiah $
  *
  * @package    Core
  * @author     Kohana Team
@@ -54,7 +54,7 @@ class Input_Core {
 		$_COOKIE = Input::clean($_COOKIE);
 		$_SERVER = Input::clean($_SERVER);
 
-		if (PHP_SAPI == 'cli')
+		if (Kohana::$server_api === 'cli')
 		{
 			// Convert command line arguments
 			$_SERVER['argv'] = Input::clean($_SERVER['argv']);
@@ -311,7 +311,7 @@ class Input_Core {
 		if (trim($data) === '')
 			return $data;
 
-		if ($tool === TRUE)
+		if (is_bool($tool))
 		{
 			$tool = 'default';
 		}
@@ -371,7 +371,7 @@ class Input_Core {
 		$data = html_entity_decode($data, ENT_COMPAT, 'UTF-8');
 
 		// Remove any attribute starting with "on" or xmlns
-		$data = preg_replace('#(<[^>]+?[\x00-\x20"\'])(?:on|xmlns)[^>]*+>#iu', '$1>', $data);
+		$data = preg_replace('#(?:on[a-z]+|xmlns)\s*=\s*[\'"\x00-\x20]?[^\'>"]*[\'"\x00-\x20]?\s?#iu', '', $data);
 
 		// Remove javascript: and vbscript: protocols
 		$data = preg_replace('#([a-z]*)[\x00-\x20]*=[\x00-\x20]*([`\'"]*)[\x00-\x20]*j[\x00-\x20]*a[\x00-\x20]*v[\x00-\x20]*a[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2nojavascript...', $data);
diff --git a/system/libraries/Kohana_PHP_Exception.php b/system/libraries/Kohana_PHP_Exception.php
index fca5b30b..019c686b 100644
--- a/system/libraries/Kohana_PHP_Exception.php
+++ b/system/libraries/Kohana_PHP_Exception.php
@@ -2,7 +2,7 @@
 /**
  * Kohana PHP Error Exceptions
  *
- * $Id: Kohana_PHP_Exception.php 4679 2009-11-10 01:45:52Z isaiah $
+ * $Id: Kohana_PHP_Exception.php 4687 2009-11-30 21:59:26Z isaiah $
  *
  * @package    Core
  * @author     Kohana Team
@@ -89,7 +89,7 @@ class Kohana_PHP_Exception_Core extends Kohana_Exception {
 	 */
 	public static function shutdown_handler()
 	{
-		if (Kohana_PHP_Exception::$enabled AND $error = error_get_last())
+		if (Kohana_PHP_Exception::$enabled AND $error = error_get_last() AND (error_reporting() & $error['type']))
 		{
 			// Fake an exception for nice debugging
 			Kohana_Exception::handle(new Kohana_PHP_Exception($error['type'], $error['message'], $error['file'], $error['line']));
diff --git a/system/libraries/ORM.php b/system/libraries/ORM.php
index eff22fc0..2f2feed5 100644
--- a/system/libraries/ORM.php
+++ b/system/libraries/ORM.php
@@ -8,7 +8,7 @@
  * [ref-orm]: http://wikipedia.org/wiki/Object-relational_mapping
  * [ref-act]: http://wikipedia.org/wiki/Active_record
  *
- * $Id: ORM.php 4682 2009-11-11 20:53:23Z isaiah $
+ * $Id: ORM.php 4711 2009-12-10 20:40:52Z isaiah $
  *
  * @package    ORM
  * @author     Kohana Team
@@ -102,7 +102,7 @@ class ORM_Core {
 		$this->object_name   = strtolower(substr(get_class($this), 0, -6));
 		$this->object_plural = inflector::plural($this->object_name);
 
-		if (!isset($this->sorting))
+		if ( ! isset($this->sorting))
 		{
 			// Default sorting
 			$this->sorting = array($this->primary_key => 'asc');
@@ -119,7 +119,7 @@ class ORM_Core {
 			// Load an object
 			$this->_load_values((array) $id);
 		}
-		elseif (!empty($id))
+		elseif ( ! empty($id))
 		{
 			// Set the object's primary key, but don't load it until needed
 			$this->object[$this->primary_key] = $id;
@@ -234,7 +234,7 @@ class ORM_Core {
 				switch ($num_args)
 				{
 					case 0:
-						if (in_array($method, array('open', 'close', 'cache')))
+						if (in_array($method, array('open', 'and_open', 'or_open', 'close', 'cache')))
 						{
 							// Should return ORM, not Database
 							$this->db_builder->$method();
@@ -320,12 +320,12 @@ class ORM_Core {
 				}
 
 				// Foreign key lies in this table (this model belongs_to target model)
-				$where = array($model->foreign_key(TRUE) => $this->object[$this->foreign_key($column)]);
+				$where = array($model->foreign_key(TRUE), '=', $this->object[$this->foreign_key($column)]);
 			}
 			else
 			{
 				// Foreign key lies in the target table (this model has_one target model)
-				$where = array($this->foreign_key($column, $model->table_name) => $this->primary_key_value);
+				$where = array($this->foreign_key($column, $model->table_name), '=', $this->primary_key_value);
 			}
 
 			// one<>alias:one relationship
@@ -524,16 +524,6 @@ class ORM_Core {
 		unset($this->object[$column], $this->changed[$column], $this->related[$column]);
 	}
 
-	/**
-	 * Displays the primary key of a model when it is converted to a string.
-	 *
-	 * @return  string
-	 */
-	public function __toString()
-	{
-		return (string) $this->primary_key_value;
-	}
-
 	/**
 	 * Returns the values of this object as an array.
 	 *
@@ -665,7 +655,7 @@ class ORM_Core {
 			if (is_array($id))
 			{
 				// Search for all clauses
-				$this->db_builder->where($id);
+				$this->db_builder->where(array($id));
 			}
 			else
 			{
@@ -767,6 +757,9 @@ class ORM_Core {
 			ORM_Validation_Exception::handle_validation($this->table_name, $array);
 		}
 
+		// Fields may have been modified by filters
+		$this->object = array_merge($this->object, $array->getArrayCopy());
+
 		// Return validation status
 		return $this;
 	}
@@ -782,8 +775,10 @@ class ORM_Core {
 		if ( ! empty($this->changed))
 		{
 			// Require model validation before saving
-			if (!$this->_valid)
+			if ( ! $this->_valid)
+			{
 				$this->validate();
+			}
 
 			$data = array();
 			foreach ($this->changed as $column)
@@ -881,8 +876,8 @@ class ORM_Core {
 				}
 
 				// Foreign keys for the join table
-				$object_fk  = $this->foreign_key(NULL);
-				$related_fk = $model->foreign_key(NULL);
+				$object_fk  = $this->foreign_key($join_table);
+				$related_fk = $model->foreign_key($join_table);
 
 				if ( ! empty($added))
 				{
@@ -909,6 +904,12 @@ class ORM_Core {
 			}
 		}
 
+		if ($this->saved() === TRUE)
+		{
+			// Clear the per-request database cache
+			$this->db->clear_cache(NULL, Database::PER_REQUEST);
+		}
+
 		return $this;
 	}
 
@@ -933,6 +934,9 @@ class ORM_Core {
 			->where($this->primary_key, '=', $id)
 			->execute($this->db);
 
+		// Clear the per-request database cache
+		$this->db->clear_cache(NULL, Database::PER_REQUEST);
+
 		return $this->clear();
 	}
 
@@ -965,6 +969,9 @@ class ORM_Core {
 			return $this;
 		}
 
+		// Clear the per-request database cache
+		$this->db->clear_cache(NULL, Database::PER_REQUEST);
+
 		return $this->clear();
 	}
 
@@ -1154,12 +1161,13 @@ class ORM_Core {
 	 *
 	 * @chainable
 	 * @param   string  SQL query to clear
+	 * @param   integer  Type of cache to clear, Database::CROSS_REQUEST or Database::PER_REQUEST
 	 * @return  ORM
 	 */
-	public function clear_cache($sql = NULL)
+	public function clear_cache($sql = NULL, $type = NULL)
 	{
 		// Proxy to database
-		$this->db->clear_cache($sql);
+		$this->db->clear_cache($sql, $type);
 
 		ORM::$column_cache = array();
 
@@ -1550,9 +1558,9 @@ class ORM_Core {
 	 */
 	protected function load_relations($table, ORM $model)
 	{
-		$result = db::select(array('id' => $model->foreign_key(NULL)))
+		$result = db::select(array('id' => $model->foreign_key($table)))
 			->from($table)
-			->where($this->foreign_key(NULL, $table), '=', $this->primary_key_value)
+			->where($this->foreign_key($table, $table), '=', $this->primary_key_value)
 			->execute($this->db)
 			->as_object();
 
diff --git a/system/libraries/Profiler.php b/system/libraries/Profiler.php
index b7a5ecae..940e365d 100644
--- a/system/libraries/Profiler.php
+++ b/system/libraries/Profiler.php
@@ -8,7 +8,7 @@
  * POST Data    - The name and values of any POST data submitted to the current page.
  * Cookie Data  - All cookies sent for the current request.
  *
- * $Id: Profiler.php 4679 2009-11-10 01:45:52Z isaiah $
+ * $Id: Profiler.php 4719 2009-12-17 04:31:48Z isaiah $
  *
  * @package    Profiler
  * @author     Kohana Team
@@ -101,7 +101,7 @@ class Profiler_Core {
 
 		// Don't display if there's no profiles
 		if (empty(Profiler::$profiles))
-			return $output;
+			return Kohana::$output;
 
 		$styles = '';
 		foreach (Profiler::$profiles as $profile)
diff --git a/system/libraries/Router.php b/system/libraries/Router.php
index 82dbb9b5..18e01af2 100644
--- a/system/libraries/Router.php
+++ b/system/libraries/Router.php
@@ -2,7 +2,7 @@
 /**
  * Router
  *
- * $Id: Router.php 4679 2009-11-10 01:45:52Z isaiah $
+ * $Id: Router.php 4693 2009-12-04 17:11:16Z cbandy $
  *
  * @package    Core
  * @author     Kohana Team
@@ -38,7 +38,7 @@ class Router_Core {
 		if ( ! empty($_SERVER['QUERY_STRING']))
 		{
 			// Set the query string to the current query string
-			Router::$query_string = '?'.trim(urldecode($_SERVER['QUERY_STRING']), '&/');
+			Router::$query_string = '?'.urldecode(trim($_SERVER['QUERY_STRING'], '&'));
 		}
 
 		if (Router::$routes === NULL)
@@ -173,7 +173,7 @@ class Router_Core {
 	 */
 	public static function find_uri()
 	{
-		if (PHP_SAPI === 'cli')
+		if (Kohana::$server_api === 'cli')
 		{
 			// Command line requires a bit of hacking
 			if (isset($_SERVER['argv'][1]))
@@ -181,9 +181,9 @@ class Router_Core {
 				Router::$current_uri = $_SERVER['argv'][1];
 
 				// Remove GET string from segments
-				if (($query = strpos(Router::$current_uri, '?')) !== FALSE)
+				if (strpos(Router::$current_uri, '?') !== FALSE)
 				{
-					list (Router::$current_uri, $query) = explode('?', Router::$current_uri, 2);
+					list(Router::$current_uri, $query) = explode('?', Router::$current_uri, 2);
 
 					// Parse the query string into $_GET
 					parse_str($query, $_GET);
diff --git a/system/libraries/Validation.php b/system/libraries/Validation.php
index f340e63c..47f34584 100644
--- a/system/libraries/Validation.php
+++ b/system/libraries/Validation.php
@@ -2,7 +2,7 @@
 /**
  * Validation library.
  *
- * $Id: Validation.php 4679 2009-11-10 01:45:52Z isaiah $
+ * $Id: Validation.php 4713 2009-12-10 22:25:38Z isaiah $
  *
  * @package    Validation
  * @author     Kohana Team
@@ -26,12 +26,12 @@ class Validation_Core extends ArrayObject {
 	protected $errors = array();
 	protected $messages = array();
 
+	// Field labels
+	protected $labels = array();
+
 	// Fields that are expected to be arrays
 	protected $array_fields = array();
 
-	// Checks if there is data to validate.
-	protected $submitted;
-
 	/**
 	 * Creates a new Validation instance.
 	 *
@@ -52,9 +52,6 @@ class Validation_Core extends ArrayObject {
 	 */
 	public function __construct(array $array)
 	{
-		// The array is submitted if the array is not empty
-		$this->submitted = ! empty($array);
-
 		parent::__construct($array, ArrayObject::ARRAY_AS_PROPS | ArrayObject::STD_PROP_LIST);
 	}
 
@@ -85,21 +82,6 @@ class Validation_Core extends ArrayObject {
 		return $copy;
 	}
 
-	/**
-	 * Test if the data has been submitted.
-	 *
-	 * @return  boolean
-	 */
-	public function submitted($value = NULL)
-	{
-		if (is_bool($value))
-		{
-			$this->submitted = $value;
-		}
-
-		return $this->submitted;
-	}
-
 	/**
 	 * Returns an array of all the field names that have filters, rules, or callbacks.
 	 *
@@ -196,6 +178,42 @@ class Validation_Core extends ArrayObject {
 		return $this;
 	}
 
+	/**
+	* Sets or overwrites the label name for a field.
+	*
+	* @param string field name
+	* @param string label
+	* @return $this
+	*/
+	public function label($field, $label = NULL)
+	{
+		if ($label === NULL AND ($field !== TRUE OR $field !== '*') AND ! isset($this->labels[$field]))
+		{
+			// Set the field label to the field name
+			$this->labels[$field] = ucfirst(preg_replace('/[^\pL]+/u', ' ', $field));
+		}
+		elseif ($label !== NULL)
+		{
+			// Set the label for this field
+			$this->labels[$field] = $label;
+		}
+
+		return $this;
+	}
+
+	/**
+	* Sets labels using an array.
+	*
+	* @param array list of field => label names
+	* @return $this
+	*/
+	public function labels(array $labels)
+	{
+		$this->labels = $labels + $this->labels;
+
+		return $this;
+	}
+
 	/**
 	 * Converts a filter, rule, or callback into a fully-qualified callback array.
 	 *
@@ -338,6 +356,9 @@ class Validation_Core extends ArrayObject {
 		$rules = func_get_args();
 		$rules = array_slice($rules, 1);
 
+		// Set a default field label
+		$this->label($field);
+
 		if ($field === TRUE)
 		{
 			// Use wildcard
@@ -412,6 +433,9 @@ class Validation_Core extends ArrayObject {
 		$callbacks = func_get_args();
 		$callbacks = array_slice($callbacks, 1);
 
+		// Set a default field label
+		$this->label($field);
+
 		if ($field === TRUE)
 		{
 			// Use wildcard
@@ -471,9 +495,6 @@ class Validation_Core extends ArrayObject {
 			}
 		}
 
-		if ($this->submitted === FALSE)
-			return FALSE;
-
 		foreach ($this->rules as $field => $callbacks)
 		{
 			foreach ($callbacks as $callback)
@@ -534,6 +555,7 @@ class Validation_Core extends ArrayObject {
 
 					if (($result == $is_false))
 					{
+						$rule = $is_false ? '!'.$rule : $rule;
 						$this->add_error($field, $rule, $args);
 
 						// Stop validating this field when an error is found
@@ -618,46 +640,6 @@ class Validation_Core extends ArrayObject {
 		return $this;
 	}
 
-	/**
-	 * Sets or returns the message for an input.
-	 *
-	 * @chainable
-	 * @param   string   input key
-	 * @param   string   message to set
-	 * @return  string|object
-	 */
-	public function message($input = NULL, $message = NULL)
-	{
-		if ($message === NULL)
-		{
-			if ($input === NULL)
-			{
-				$messages = array();
-				$keys     = array_keys($this->messages);
-
-				foreach ($keys as $input)
-				{
-					$messages[] = $this->message($input);
-				}
-
-				return implode("\n", $messages);
-			}
-
-			// Return nothing if no message exists
-			if (empty($this->messages[$input]))
-				return '';
-
-			// Return the HTML message string
-			return $this->messages[$input];
-		}
-		else
-		{
-			$this->messages[$input] = $message;
-		}
-
-		return $this;
-	}
-
 	/**
 	 * Return the errors array.
 	 *
@@ -677,18 +659,49 @@ class Validation_Core extends ArrayObject {
 		}
 		else
 		{
-
 			$errors = array();
 			foreach ($this->errors as $input => $error)
 			{
-				// Key for this input error
-				$key = "$file.$input.$error[0]";
+				// Locations to check for error messages
+				$error_locations = array
+				(
+					"validation/{$file}.{$input}.{$error[0]}",
+					"validation/{$file}.{$input}.default",
+					"validation/default.{$error[0]}"
+				);
+
+				if (($message = Kohana::message($error_locations[0])) !== $error_locations[0])
+				{
+					// Found a message for this field and error
+				}
+				elseif (($message = Kohana::message($error_locations[1])) !== $error_locations[1])
+				{
+					// Found a default message for this field
+				}
+				elseif (($message = Kohana::message($error_locations[2])) !== $error_locations[2])
+				{
+					// Found a default message for this error
+				}
+				else
+				{
+					// No message exists, display the path expected
+					$message = "validation/{$file}.{$input}.{$error[0]}";
+				}
 
-				if (($errors[$input] = Kohana::message('validation/'.$key, $error[1])) === $key)
+				// Start the translation values list
+				$values = array(':field' => __($this->labels[$input]));
+
+				if ( ! empty($error[1]))
 				{
-					// Get the default error message
-					$errors[$input] = Kohana::message("$file.$input.default");
+					foreach ($error[1] as $key => $value)
+					{
+						// Add each parameter as a numbered value, starting from 1
+						$values[':param'.($key + 1)] = __($value);
+					}
 				}
+
+				// Translate the message using the default language
+				$errors[$input] = __($message, $values);
 			}
 
 			return $errors;
diff --git a/system/libraries/drivers/Cache/File.php b/system/libraries/drivers/Cache/File.php
index fc20c22d..d6ec0378 100644
--- a/system/libraries/drivers/Cache/File.php
+++ b/system/libraries/drivers/Cache/File.php
@@ -183,7 +183,7 @@ class Cache_File_Driver extends Cache_Driver {
 				// Get the id from the filename
 				list($key, $junk) = explode('~', basename($path), 2);
 
-				if (($data = $this->get($key)) !== FALSE)
+				if (($data = $this->get($key, TRUE)) !== FALSE)
 				{
 					// Add the result to the array
 					$result[$key] = $data;
@@ -211,7 +211,7 @@ class Cache_File_Driver extends Cache_Driver {
 			// Remove the cache file
 			if ( ! unlink($path))
 			{
-				Kohana::log('error', 'Cache: Unable to delete cache file: '.$path);
+				Kohana_Log::add('error', 'Cache: Unable to delete cache file: '.$path);
 				$success = FALSE;
 			}
 		}
diff --git a/system/libraries/drivers/Cache/Memcache.php b/system/libraries/drivers/Cache/Memcache.php
index 636191d4..13d61d82 100644
--- a/system/libraries/drivers/Cache/Memcache.php
+++ b/system/libraries/drivers/Cache/Memcache.php
@@ -17,7 +17,7 @@ class Cache_Memcache_Driver extends Cache_Driver {
 	public function __construct($config)
 	{
 		if ( ! extension_loaded('memcache'))
-			throw new Kohana_Exception('The memcache PHP extension must be loaded to use this driver.');
+			throw new Cache_Exception('The memcache PHP extension must be loaded to use this driver.');
 
 		ini_set('memcache.allow_failover', (isset($config['allow_failover']) AND $config['allow_failover']) ? TRUE : FALSE);
 
@@ -79,7 +79,10 @@ class Cache_Memcache_Driver extends Cache_Driver {
 
 		if ($single)
 		{
-			return ($items === FALSE OR count($items) > 0) ? current($items) : NULL;
+			if ($items === FALSE)
+			    return NULL;
+
+			return (count($items) > 0) ? current($items) : NULL;
 		}
 		else
 		{
diff --git a/system/libraries/drivers/Cache/Xcache.php b/system/libraries/drivers/Cache/Xcache.php
index ad11e6d7..4c08405e 100644
--- a/system/libraries/drivers/Cache/Xcache.php
+++ b/system/libraries/drivers/Cache/Xcache.php
@@ -16,7 +16,7 @@ class Cache_Xcache_Driver extends Cache_Driver {
 	public function __construct($config)
 	{
 		if ( ! extension_loaded('xcache'))
-			throw new Kohana_Exception('The xcache PHP extension must be loaded to use this driver.');
+			throw new Cache_Exception('The xcache PHP extension must be loaded to use this driver.');
 
 		$this->config = $config;
 	}
diff --git a/system/messages/core.php b/system/messages/core.php
index 4bf6ee8c..64f897e8 100644
--- a/system/messages/core.php
+++ b/system/messages/core.php
@@ -8,7 +8,6 @@ $messages = array
 		E_PAGE_NOT_FOUND     => __('Page Not Found'),    // __('The requested page was not found. It may have moved, been deleted, or archived.'),
 		E_DATABASE_ERROR     => __('Database Error'),    // __('A database error occurred while performing the requested procedure. Please review the database error below for more information.'),
 		E_RECOVERABLE_ERROR  => __('Recoverable Error'), // __('An error was detected which prevented the loading of this page. If this problem persists, please contact the website administrator.'),
-
 		E_ERROR              => __('Fatal Error'),
 		E_COMPILE_ERROR      => __('Fatal Error'),
 		E_CORE_ERROR         => __('Fatal Error'),
@@ -29,4 +28,10 @@ $messages = array
 	'driver'             => 'driver',
 	'model'              => 'model',
 	'view'               => 'view',
-);
\ No newline at end of file
+);
+
+// E_DEPRECATED is only defined in PHP >= 5.3.0
+if (defined('E_DEPRECATED'))
+{
+	$messages['errors'][E_DEPRECATED] = __('Deprecated');
+}
\ No newline at end of file
diff --git a/system/messages/validation/default.php b/system/messages/validation/default.php
new file mode 100644
index 00000000..2c59fa06
--- /dev/null
+++ b/system/messages/validation/default.php
@@ -0,0 +1,17 @@
+ 'The :field field is required',
+	'length'        => 'The :field field must be between :param1 and :param2 characters long',
+	'depends_on'    => 'The :field field requires the :param1 field',
+	'matches'       => 'The :field field must be the same as :param1',
+	'email'         => 'The :field field must be a valid email address',
+	'decimal'       => 'The :field field must be a decimal with :param1 places',
+	'digit'         => 'The :field field must be a digit',
+	'in_array'      => 'The :field field must be one of the available options',
+	'alpha_numeric' => 'The :field field must consist only of alphabetical or numeric characters',
+	'alpha_dash '   => 'The :field field must consist only of alphabetical, numeric, underscore and dash characters',
+	'numeric '      => 'The :field field must be a valid number',
+	'url'           => 'The :field field must be a valid url',
+	'phone'         => 'The :field field must be a valid phone number',
+);
diff --git a/system/vendor/Markdown.php b/system/vendor/Markdown.php
deleted file mode 100644
index b649f6c1..00000000
--- a/system/vendor/Markdown.php
+++ /dev/null
@@ -1,2909 +0,0 @@
-
-#
-# Original Markdown
-# Copyright (c) 2004-2006 John Gruber  
-# 
-#
-
-
-define( 'MARKDOWN_VERSION',  "1.0.1m" ); # Sat 21 Jun 2008
-define( 'MARKDOWNEXTRA_VERSION',  "1.2.3" ); # Wed 31 Dec 2008
-
-
-#
-# Global default settings:
-#
-
-# Change to ">" for HTML output
-@define( 'MARKDOWN_EMPTY_ELEMENT_SUFFIX',  " />");
-
-# Define the width of a tab for code blocks.
-@define( 'MARKDOWN_TAB_WIDTH',     4 );
-
-# Optional title attribute for footnote links and backlinks.
-@define( 'MARKDOWN_FN_LINK_TITLE',         "" );
-@define( 'MARKDOWN_FN_BACKLINK_TITLE',     "" );
-
-# Optional class attribute for footnote links and backlinks.
-@define( 'MARKDOWN_FN_LINK_CLASS',         "" );
-@define( 'MARKDOWN_FN_BACKLINK_CLASS',     "" );
-
-
-#
-# WordPress settings:
-#
-
-# Change to false to remove Markdown from posts and/or comments.
-@define( 'MARKDOWN_WP_POSTS',      true );
-@define( 'MARKDOWN_WP_COMMENTS',   true );
-
-
-
-### Standard Function Interface ###
-
-@define( 'MARKDOWN_PARSER_CLASS',  'MarkdownExtra_Parser' );
-
-function Markdown($text) {
-#
-# Initialize the parser and return the result of its transform method.
-#
-	# Setup static parser variable.
-	static $parser;
-	if (!isset($parser)) {
-		$parser_class = MARKDOWN_PARSER_CLASS;
-		$parser = new $parser_class;
-	}
-
-	# Transform text using parser.
-	return $parser->transform($text);
-}
-
-
-### WordPress Plugin Interface ###
-
-/*
-Plugin Name: Markdown Extra
-Plugin URI: http://www.michelf.com/projects/php-markdown/
-Description: Markdown syntax allows you to write using an easy-to-read, easy-to-write plain text format. Based on the original Perl version by John Gruber. More...
-Version: 1.2.2
-Author: Michel Fortin
-Author URI: http://www.michelf.com/
-*/
-
-if (isset($wp_version)) {
-	# More details about how it works here:
-	# 
-	
-	# Post content and excerpts
-	# - Remove WordPress paragraph generator.
-	# - Run Markdown on excerpt, then remove all tags.
-	# - Add paragraph tag around the excerpt, but remove it for the excerpt rss.
-	if (MARKDOWN_WP_POSTS) {
-		remove_filter('the_content',     'wpautop');
-        remove_filter('the_content_rss', 'wpautop');
-		remove_filter('the_excerpt',     'wpautop');
-		add_filter('the_content',     'mdwp_MarkdownPost', 6);
-        add_filter('the_content_rss', 'mdwp_MarkdownPost', 6);
-		add_filter('get_the_excerpt', 'mdwp_MarkdownPost', 6);
-		add_filter('get_the_excerpt', 'trim', 7);
-		add_filter('the_excerpt',     'mdwp_add_p');
-		add_filter('the_excerpt_rss', 'mdwp_strip_p');
-		
-		remove_filter('content_save_pre',  'balanceTags', 50);
-		remove_filter('excerpt_save_pre',  'balanceTags', 50);
-		add_filter('the_content',  	  'balanceTags', 50);
-		add_filter('get_the_excerpt', 'balanceTags', 9);
-	}
-	
-	# Add a footnote id prefix to posts when inside a loop.
-	function mdwp_MarkdownPost($text) {
-		static $parser;
-		if (!$parser) {
-			$parser_class = MARKDOWN_PARSER_CLASS;
-			$parser = new $parser_class;
-		}
-		if (is_single() || is_page() || is_feed()) {
-			$parser->fn_id_prefix = "";
-		} else {
-			$parser->fn_id_prefix = get_the_ID() . ".";
-		}
-		return $parser->transform($text);
-	}
-	
-	# Comments
-	# - Remove WordPress paragraph generator.
-	# - Remove WordPress auto-link generator.
-	# - Scramble important tags before passing them to the kses filter.
-	# - Run Markdown on excerpt then remove paragraph tags.
-	if (MARKDOWN_WP_COMMENTS) {
-		remove_filter('comment_text', 'wpautop', 30);
-		remove_filter('comment_text', 'make_clickable');
-		add_filter('pre_comment_content', 'Markdown', 6);
-		add_filter('pre_comment_content', 'mdwp_hide_tags', 8);
-		add_filter('pre_comment_content', 'mdwp_show_tags', 12);
-		add_filter('get_comment_text',    'Markdown', 6);
-		add_filter('get_comment_excerpt', 'Markdown', 6);
-		add_filter('get_comment_excerpt', 'mdwp_strip_p', 7);
-	
-		global $mdwp_hidden_tags, $mdwp_placeholders;
-		$mdwp_hidden_tags = explode(' ',
-			' 
  
  
   ');
-		$mdwp_placeholders = explode(' ', str_rot13(
-			'pEj07ZbbBZ U1kqgh4w4p pre2zmeN6K QTi31t9pre ol0MP1jzJR '.
-			'ML5IjmbRol ulANi1NsGY J7zRLJqPul liA8ctl16T K9nhooUHli'));
-	}
-	
-	function mdwp_add_p($text) {
-		if (!preg_match('{^$|^<(p|ul|ol|dl|pre|blockquote)>}i', $text)) {
-			$text = ''.$text.'
';
-			$text = preg_replace('{\n{2,}}', "
\n\n", $text);
-		}
-		return $text;
-	}
-	
-	function mdwp_strip_p($t) { return preg_replace('{?p>}i', '', $t); }
-
-	function mdwp_hide_tags($text) {
-		global $mdwp_hidden_tags, $mdwp_placeholders;
-		return str_replace($mdwp_hidden_tags, $mdwp_placeholders, $text);
-	}
-	function mdwp_show_tags($text) {
-		global $mdwp_hidden_tags, $mdwp_placeholders;
-		return str_replace($mdwp_placeholders, $mdwp_hidden_tags, $text);
-	}
-}
-
-
-### bBlog Plugin Info ###
-
-function identify_modifier_markdown() {
-	return array(
-		'name' => 'markdown',
-		'type' => 'modifier',
-		'nicename' => 'PHP Markdown Extra',
-		'description' => 'A text-to-HTML conversion tool for web writers',
-		'authors' => 'Michel Fortin and John Gruber',
-		'licence' => 'GPL',
-		'version' => MARKDOWNEXTRA_VERSION,
-		'help' => 'Markdown syntax allows you to write using an easy-to-read, easy-to-write plain text format. Based on the original Perl version by John Gruber. More...',
-		);
-}
-
-
-### Smarty Modifier Interface ###
-
-function smarty_modifier_markdown($text) {
-	return Markdown($text);
-}
-
-
-### Textile Compatibility Mode ###
-
-# Rename this file to "classTextile.php" and it can replace Textile everywhere.
-
-if (strcasecmp(substr(__FILE__, -16), "classTextile.php") == 0) {
-	# Try to include PHP SmartyPants. Should be in the same directory.
-	@include_once 'smartypants.php';
-	# Fake Textile class. It calls Markdown instead.
-	class Textile {
-		function TextileThis($text, $lite='', $encode='') {
-			if ($lite == '' && $encode == '')    $text = Markdown($text);
-			if (function_exists('SmartyPants'))  $text = SmartyPants($text);
-			return $text;
-		}
-		# Fake restricted version: restrictions are not supported for now.
-		function TextileRestricted($text, $lite='', $noimage='') {
-			return $this->TextileThis($text, $lite);
-		}
-		# Workaround to ensure compatibility with TextPattern 4.0.3.
-		function blockLite($text) { return $text; }
-	}
-}
-
-
-
-#
-# Markdown Parser Class
-#
-
-class Markdown_Parser {
-
-	# Regex to match balanced [brackets].
-	# Needed to insert a maximum bracked depth while converting to PHP.
-	var $nested_brackets_depth = 6;
-	var $nested_brackets_re;
-	
-	var $nested_url_parenthesis_depth = 4;
-	var $nested_url_parenthesis_re;
-
-	# Table of hash values for escaped characters:
-	var $escape_chars = '\`*_{}[]()>#+-.!';
-	var $escape_chars_re;
-
-	# Change to ">" for HTML output.
-	var $empty_element_suffix = MARKDOWN_EMPTY_ELEMENT_SUFFIX;
-	var $tab_width = MARKDOWN_TAB_WIDTH;
-	
-	# Change to `true` to disallow markup or entities.
-	var $no_markup = false;
-	var $no_entities = false;
-	
-	# Predefined urls and titles for reference links and images.
-	var $predef_urls = array();
-	var $predef_titles = array();
-
-
-	function Markdown_Parser() {
-	#
-	# Constructor function. Initialize appropriate member variables.
-	#
-		$this->_initDetab();
-		$this->prepareItalicsAndBold();
-	
-		$this->nested_brackets_re = 
-			str_repeat('(?>[^\[\]]+|\[', $this->nested_brackets_depth).
-			str_repeat('\])*', $this->nested_brackets_depth);
-	
-		$this->nested_url_parenthesis_re = 
-			str_repeat('(?>[^()\s]+|\(', $this->nested_url_parenthesis_depth).
-			str_repeat('(?>\)))*', $this->nested_url_parenthesis_depth);
-		
-		$this->escape_chars_re = '['.preg_quote($this->escape_chars).']';
-		
-		# Sort document, block, and span gamut in ascendent priority order.
-		asort($this->document_gamut);
-		asort($this->block_gamut);
-		asort($this->span_gamut);
-	}
-
-
-	# Internal hashes used during transformation.
-	var $urls = array();
-	var $titles = array();
-	var $html_hashes = array();
-	
-	# Status flag to avoid invalid nesting.
-	var $in_anchor = false;
-	
-	
-	function setup() {
-	#
-	# Called before the transformation process starts to setup parser 
-	# states.
-	#
-		# Clear global hashes.
-		$this->urls = $this->predef_urls;
-		$this->titles = $this->predef_titles;
-		$this->html_hashes = array();
-		
-		$in_anchor = false;
-	}
-	
-	function teardown() {
-	#
-	# Called after the transformation process to clear any variable 
-	# which may be taking up memory unnecessarly.
-	#
-		$this->urls = array();
-		$this->titles = array();
-		$this->html_hashes = array();
-	}
-
-
-	function transform($text) {
-	#
-	# Main function. Performs some preprocessing on the input text
-	# and pass it through the document gamut.
-	#
-		$this->setup();
-	
-		# Remove UTF-8 BOM and marker character in input, if present.
-		$text = preg_replace('{^\xEF\xBB\xBF|\x1A}', '', $text);
-
-		# Standardize line endings:
-		#   DOS to Unix and Mac to Unix
-		$text = preg_replace('{\r\n?}', "\n", $text);
-
-		# Make sure $text ends with a couple of newlines:
-		$text .= "\n\n";
-
-		# Convert all tabs to spaces.
-		$text = $this->detab($text);
-
-		# Turn block-level HTML blocks into hash entries
-		$text = $this->hashHTMLBlocks($text);
-
-		# Strip any lines consisting only of spaces and tabs.
-		# This makes subsequent regexen easier to write, because we can
-		# match consecutive blank lines with /\n+/ instead of something
-		# contorted like /[ ]*\n+/ .
-		$text = preg_replace('/^[ ]+$/m', '', $text);
-
-		# Run document gamut methods.
-		foreach ($this->document_gamut as $method => $priority) {
-			$text = $this->$method($text);
-		}
-		
-		$this->teardown();
-
-		return $text . "\n";
-	}
-	
-	var $document_gamut = array(
-		# Strip link definitions, store in hashes.
-		"stripLinkDefinitions" => 20,
-		
-		"runBasicBlockGamut"   => 30,
-		);
-
-
-	function stripLinkDefinitions($text) {
-	#
-	# Strips link definitions from text, stores the URLs and titles in
-	# hash references.
-	#
-		$less_than_tab = $this->tab_width - 1;
-
-		# Link defs are in the form: ^[id]: url "optional title"
-		$text = preg_replace_callback('{
-							^[ ]{0,'.$less_than_tab.'}\[(.+)\][ ]?:	# id = $1
-							  [ ]*
-							  \n?				# maybe *one* newline
-							  [ ]*
-							(\S+?)>?			# url = $2
-							  [ ]*
-							  \n?				# maybe one newline
-							  [ ]*
-							(?:
-								(?<=\s)			# lookbehind for whitespace
-								["(]
-								(.*?)			# title = $3
-								[")]
-								[ ]*
-							)?	# title is optional
-							(?:\n+|\Z)
-			}xm',
-			array(&$this, '_stripLinkDefinitions_callback'),
-			$text);
-		return $text;
-	}
-	function _stripLinkDefinitions_callback($matches) {
-		$link_id = strtolower($matches[1]);
-		$this->urls[$link_id] = $matches[2];
-		$this->titles[$link_id] =& $matches[3];
-		return ''; # String that will replace the block
-	}
-
-
-	function hashHTMLBlocks($text) {
-		if ($this->no_markup)  return $text;
-
-		$less_than_tab = $this->tab_width - 1;
-
-		# Hashify HTML blocks:
-		# We only want to do this for block-level HTML tags, such as headers,
-		# lists, and tables. That's because we still want to wrap 
s around
-		# "paragraphs" that are wrapped in non-block-level tags, such as anchors,
-		# phrase emphasis, and spans. The list of tags we're looking for is
-		# hard-coded:
-		#
-		# *  List "a" is made of tags which can be both inline or block-level.
-		#    These will be treated block-level when the start tag is alone on 
-		#    its line, otherwise they're not matched here and will be taken as 
-		#    inline later.
-		# *  List "b" is made of tags which are always block-level;
-		#
-		$block_tags_a_re = 'ins|del';
-		$block_tags_b_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|'.
-						   'script|noscript|form|fieldset|iframe|math';
-
-		# Regular expression for the content of a block tag.
-		$nested_tags_level = 4;
-		$attr = '
-			(?>				# optional tag attributes
-			  \s			# starts with whitespace
-			  (?>
-				[^>"/]+		# text outside quotes
-			  |
-				/+(?!>)		# slash not followed by ">"
-			  |
-				"[^"]*"		# text inside double quotes (tolerate ">")
-			  |
-				\'[^\']*\'	# text inside single quotes (tolerate ">")
-			  )*
-			)?	
-			';
-		$content =
-			str_repeat('
-				(?>
-				  [^<]+			# content without tag
-				|
-				  <\2			# nested opening tag
-					'.$attr.'	# attributes
-					(?>
-					  />
-					|
-					  >', $nested_tags_level).	# end of opening tag
-					  '.*?'.					# last level nested tag content
-			str_repeat('
-					  \2\s*>	# closing nested tag
-					)
-				  |				
-					<(?!/\2\s*>	# other tags with a different name
-				  )
-				)*',
-				$nested_tags_level);
-		$content2 = str_replace('\2', '\3', $content);
-
-		# First, look for nested blocks, e.g.:
-		# 	
-		# 		
-		# 		tags for inner block must be indented.
-		# 		
-		# 	
 
-		#
-		# The outermost tags must start at the left margin for this to match, and
-		# the inner nested divs must be indented.
-		# We need to do this before the next, more liberal match, because the next
-		# match will start at the first `` and stop at the first `
`.
-		$text = preg_replace_callback('{(?>
-			(?>
-				(?<=\n\n)		# Starting after a blank line
-				|				# or
-				\A\n?			# the beginning of the doc
-			)
-			(						# save in $1
-
-			  # Match from `\n` to `\n`, handling nested tags 
-			  # in between.
-					
-						[ ]{0,'.$less_than_tab.'}
-						<('.$block_tags_b_re.')# start tag = $2
-						'.$attr.'>			# attributes followed by > and \n
-						'.$content.'		# content, support nesting
-						\2>				# the matching end tag
-						[ ]*				# trailing spaces/tabs
-						(?=\n+|\Z)	# followed by a newline or end of document
-
-			| # Special version for tags of group a.
-
-						[ ]{0,'.$less_than_tab.'}
-						<('.$block_tags_a_re.')# start tag = $3
-						'.$attr.'>[ ]*\n	# attributes followed by >
-						'.$content2.'		# content, support nesting
-						\3>				# the matching end tag
-						[ ]*				# trailing spaces/tabs
-						(?=\n+|\Z)	# followed by a newline or end of document
-					
-			| # Special case just for 
. It was easier to make a special 
-			  # case than to make the other regex more complicated.
-			
-						[ ]{0,'.$less_than_tab.'}
-						<(hr)				# start tag = $2
-						'.$attr.'			# attributes
-						/?>					# the matching end tag
-						[ ]*
-						(?=\n{2,}|\Z)		# followed by a blank line or end of document
-			
-			| # Special case for standalone HTML comments:
-			
-					[ ]{0,'.$less_than_tab.'}
-					(?s:
-						
-					)
-					[ ]*
-					(?=\n{2,}|\Z)		# followed by a blank line or end of document
-			
-			| # PHP and ASP-style processor instructions ( and <%)
-			
-					[ ]{0,'.$less_than_tab.'}
-					(?s:
-						<([?%])			# $2
-						.*?
-						\2>
-					)
-					[ ]*
-					(?=\n{2,}|\Z)		# followed by a blank line or end of document
-					
-			)
-			)}Sxmi',
-			array(&$this, '_hashHTMLBlocks_callback'),
-			$text);
-
-		return $text;
-	}
-	function _hashHTMLBlocks_callback($matches) {
-		$text = $matches[1];
-		$key  = $this->hashBlock($text);
-		return "\n\n$key\n\n";
-	}
-	
-	
-	function hashPart($text, $boundary = 'X') {
-	#
-	# Called whenever a tag must be hashed when a function insert an atomic 
-	# element in the text stream. Passing $text to through this function gives
-	# a unique text-token which will be reverted back when calling unhash.
-	#
-	# The $boundary argument specify what character should be used to surround
-	# the token. By convension, "B" is used for block elements that needs not
-	# to be wrapped into paragraph tags at the end, ":" is used for elements
-	# that are word separators and "X" is used in the general case.
-	#
-		# Swap back any tag hash found in $text so we do not have to `unhash`
-		# multiple times at the end.
-		$text = $this->unhash($text);
-		
-		# Then hash the block.
-		static $i = 0;
-		$key = "$boundary\x1A" . ++$i . $boundary;
-		$this->html_hashes[$key] = $text;
-		return $key; # String that will replace the tag.
-	}
-
-
-	function hashBlock($text) {
-	#
-	# Shortcut function for hashPart with block-level boundaries.
-	#
-		return $this->hashPart($text, 'B');
-	}
-
-
-	var $block_gamut = array(
-	#
-	# These are all the transformations that form block-level
-	# tags like paragraphs, headers, and list items.
-	#
-		"doHeaders"         => 10,
-		"doHorizontalRules" => 20,
-		
-		"doLists"           => 40,
-		"doCodeBlocks"      => 50,
-		"doBlockQuotes"     => 60,
-		);
-
-	function runBlockGamut($text) {
-	#
-	# Run block gamut tranformations.
-	#
-		# We need to escape raw HTML in Markdown source before doing anything 
-		# else. This need to be done for each block, and not only at the 
-		# begining in the Markdown function since hashed blocks can be part of
-		# list items and could have been indented. Indented blocks would have 
-		# been seen as a code block in a previous pass of hashHTMLBlocks.
-		$text = $this->hashHTMLBlocks($text);
-		
-		return $this->runBasicBlockGamut($text);
-	}
-	
-	function runBasicBlockGamut($text) {
-	#
-	# Run block gamut tranformations, without hashing HTML blocks. This is 
-	# useful when HTML blocks are known to be already hashed, like in the first
-	# whole-document pass.
-	#
-		foreach ($this->block_gamut as $method => $priority) {
-			$text = $this->$method($text);
-		}
-		
-		# Finally form paragraph and restore hashed blocks.
-		$text = $this->formParagraphs($text);
-
-		return $text;
-	}
-	
-	
-	function doHorizontalRules($text) {
-		# Do Horizontal Rules:
-		return preg_replace(
-			'{
-				^[ ]{0,3}	# Leading space
-				([-*_])		# $1: First marker
-				(?>			# Repeated marker group
-					[ ]{0,2}	# Zero, one, or two spaces.
-					\1			# Marker character
-				){2,}		# Group repeated at least twice
-				[ ]*		# Tailing spaces
-				$			# End of line.
-			}mx',
-			"\n".$this->hashBlock("
empty_element_suffix")."\n", 
-			$text);
-	}
-
-
-	var $span_gamut = array(
-	#
-	# These are all the transformations that occur *within* block-level
-	# tags like paragraphs, headers, and list items.
-	#
-		# Process character escapes, code spans, and inline HTML
-		# in one shot.
-		"parseSpan"           => -30,
-
-		# Process anchor and image tags. Images must come first,
-		# because ![foo][f] looks like an anchor.
-		"doImages"            =>  10,
-		"doAnchors"           =>  20,
-		
-		# Make links out of things like ``
-		# Must come after doAnchors, because you can use < and >
-		# delimiters in inline links like [this]().
-		"doAutoLinks"         =>  30,
-		"encodeAmpsAndAngles" =>  40,
-
-		"doItalicsAndBold"    =>  50,
-		"doHardBreaks"        =>  60,
-		);
-
-	function runSpanGamut($text) {
-	#
-	# Run span gamut tranformations.
-	#
-		foreach ($this->span_gamut as $method => $priority) {
-			$text = $this->$method($text);
-		}
-
-		return $text;
-	}
-	
-	
-	function doHardBreaks($text) {
-		# Do hard breaks:
-		return preg_replace_callback('/ {2,}\n/', 
-			array(&$this, '_doHardBreaks_callback'), $text);
-	}
-	function _doHardBreaks_callback($matches) {
-		return $this->hashPart("
empty_element_suffix\n");
-	}
-
-
-	function doAnchors($text) {
-	#
-	# Turn Markdown link shortcuts into XHTML  tags.
-	#
-		if ($this->in_anchor) return $text;
-		$this->in_anchor = true;
-		
-		#
-		# First, handle reference-style links: [link text] [id]
-		#
-		$text = preg_replace_callback('{
-			(					# wrap whole match in $1
-			  \[
-				('.$this->nested_brackets_re.')	# link text = $2
-			  \]
-
-			  [ ]?				# one optional space
-			  (?:\n[ ]*)?		# one optional newline followed by spaces
-
-			  \[
-				(.*?)		# id = $3
-			  \]
-			)
-			}xs',
-			array(&$this, '_doAnchors_reference_callback'), $text);
-
-		#
-		# Next, inline-style links: [link text](url "optional title")
-		#
-		$text = preg_replace_callback('{
-			(				# wrap whole match in $1
-			  \[
-				('.$this->nested_brackets_re.')	# link text = $2
-			  \]
-			  \(			# literal paren
-				[ ]*
-				(?:
-					<(\S*)>	# href = $3
-				|
-					('.$this->nested_url_parenthesis_re.')	# href = $4
-				)
-				[ ]*
-				(			# $5
-				  ([\'"])	# quote char = $6
-				  (.*?)		# Title = $7
-				  \6		# matching quote
-				  [ ]*	# ignore any spaces/tabs between closing quote and )
-				)?			# title is optional
-			  \)
-			)
-			}xs',
-			array(&$this, '_DoAnchors_inline_callback'), $text);
-
-		#
-		# Last, handle reference-style shortcuts: [link text]
-		# These must come last in case you've also got [link test][1]
-		# or [link test](/foo)
-		#
-//		$text = preg_replace_callback('{
-//			(					# wrap whole match in $1
-//			  \[
-//				([^\[\]]+)		# link text = $2; can\'t contain [ or ]
-//			  \]
-//			)
-//			}xs',
-//			array(&$this, '_doAnchors_reference_callback'), $text);
-
-		$this->in_anchor = false;
-		return $text;
-	}
-	function _doAnchors_reference_callback($matches) {
-		$whole_match =  $matches[1];
-		$link_text   =  $matches[2];
-		$link_id     =& $matches[3];
-
-		if ($link_id == "") {
-			# for shortcut links like [this][] or [this].
-			$link_id = $link_text;
-		}
-		
-		# lower-case and turn embedded newlines into spaces
-		$link_id = strtolower($link_id);
-		$link_id = preg_replace('{[ ]?\n}', ' ', $link_id);
-
-		if (isset($this->urls[$link_id])) {
-			$url = $this->urls[$link_id];
-			$url = $this->encodeAttribute($url);
-			
-			$result = "titles[$link_id] ) ) {
-				$title = $this->titles[$link_id];
-				$title = $this->encodeAttribute($title);
-				$result .=  " title=\"$title\"";
-			}
-		
-			$link_text = $this->runSpanGamut($link_text);
-			$result .= ">$link_text";
-			$result = $this->hashPart($result);
-		}
-		else {
-			$result = $whole_match;
-		}
-		return $result;
-	}
-	function _doAnchors_inline_callback($matches) {
-		$whole_match	=  $matches[1];
-		$link_text		=  $this->runSpanGamut($matches[2]);
-		$url			=  $matches[3] == '' ? $matches[4] : $matches[3];
-		$title			=& $matches[7];
-
-		$url = $this->encodeAttribute($url);
-
-		$result = "encodeAttribute($title);
-			$result .=  " title=\"$title\"";
-		}
-		
-		$link_text = $this->runSpanGamut($link_text);
-		$result .= ">$link_text";
-
-		return $this->hashPart($result);
-	}
-
-
-	function doImages($text) {
-	#
-	# Turn Markdown image shortcuts into 
 tags.
-	#
-		#
-		# First, handle reference-style labeled images: ![alt text][id]
-		#
-		$text = preg_replace_callback('{
-			(				# wrap whole match in $1
-			  !\[
-				('.$this->nested_brackets_re.')		# alt text = $2
-			  \]
-
-			  [ ]?				# one optional space
-			  (?:\n[ ]*)?		# one optional newline followed by spaces
-
-			  \[
-				(.*?)		# id = $3
-			  \]
-
-			)
-			}xs', 
-			array(&$this, '_doImages_reference_callback'), $text);
-
-		#
-		# Next, handle inline images:  
-		# Don't forget: encode * and _
-		#
-		$text = preg_replace_callback('{
-			(				# wrap whole match in $1
-			  !\[
-				('.$this->nested_brackets_re.')		# alt text = $2
-			  \]
-			  \s?			# One optional whitespace character
-			  \(			# literal paren
-				[ ]*
-				(?:
-					<(\S*)>	# src url = $3
-				|
-					('.$this->nested_url_parenthesis_re.')	# src url = $4
-				)
-				[ ]*
-				(			# $5
-				  ([\'"])	# quote char = $6
-				  (.*?)		# title = $7
-				  \6		# matching quote
-				  [ ]*
-				)?			# title is optional
-			  \)
-			)
-			}xs',
-			array(&$this, '_doImages_inline_callback'), $text);
-
-		return $text;
-	}
-	function _doImages_reference_callback($matches) {
-		$whole_match = $matches[1];
-		$alt_text    = $matches[2];
-		$link_id     = strtolower($matches[3]);
-
-		if ($link_id == "") {
-			$link_id = strtolower($alt_text); # for shortcut links like ![this][].
-		}
-
-		$alt_text = $this->encodeAttribute($alt_text);
-		if (isset($this->urls[$link_id])) {
-			$url = $this->encodeAttribute($this->urls[$link_id]);
-			$result = "
titles[$link_id])) {
-				$title = $this->titles[$link_id];
-				$title = $this->encodeAttribute($title);
-				$result .=  " title=\"$title\"";
-			}
-			$result .= $this->empty_element_suffix;
-			$result = $this->hashPart($result);
-		}
-		else {
-			# If there's no such link ID, leave intact:
-			$result = $whole_match;
-		}
-
-		return $result;
-	}
-	function _doImages_inline_callback($matches) {
-		$whole_match	= $matches[1];
-		$alt_text		= $matches[2];
-		$url			= $matches[3] == '' ? $matches[4] : $matches[3];
-		$title			=& $matches[7];
-
-		$alt_text = $this->encodeAttribute($alt_text);
-		$url = $this->encodeAttribute($url);
-		$result = "
encodeAttribute($title);
-			$result .=  " title=\"$title\""; # $title already quoted
-		}
-		$result .= $this->empty_element_suffix;
-
-		return $this->hashPart($result);
-	}
-
-
-	function doHeaders($text) {
-		# Setext-style headers:
-		#	  Header 1
-		#	  ========
-		#  
-		#	  Header 2
-		#	  --------
-		#
-		$text = preg_replace_callback('{ ^(.+?)[ ]*\n(=+|-+)[ ]*\n+ }mx',
-			array(&$this, '_doHeaders_callback_setext'), $text);
-
-		# atx-style headers:
-		#	# Header 1
-		#	## Header 2
-		#	## Header 2 with closing hashes ##
-		#	...
-		#	###### Header 6
-		#
-		$text = preg_replace_callback('{
-				^(\#{1,6})	# $1 = string of #\'s
-				[ ]*
-				(.+?)		# $2 = Header text
-				[ ]*
-				\#*			# optional closing #\'s (not counted)
-				\n+
-			}xm',
-			array(&$this, '_doHeaders_callback_atx'), $text);
-
-		return $text;
-	}
-	function _doHeaders_callback_setext($matches) {
-		# Terrible hack to check we haven't found an empty list item.
-		if ($matches[2] == '-' && preg_match('{^-(?: |$)}', $matches[1]))
-			return $matches[0];
-		
-		$level = $matches[2]{0} == '=' ? 1 : 2;
-		$block = "".$this->runSpanGamut($matches[1])."";
-		return "\n" . $this->hashBlock($block) . "\n\n";
-	}
-	function _doHeaders_callback_atx($matches) {
-		$level = strlen($matches[1]);
-		$block = "".$this->runSpanGamut($matches[2])."";
-		return "\n" . $this->hashBlock($block) . "\n\n";
-	}
-
-
-	function doLists($text) {
-	#
-	# Form HTML ordered (numbered) and unordered (bulleted) lists.
-	#
-		$less_than_tab = $this->tab_width - 1;
-
-		# Re-usable patterns to match list item bullets and number markers:
-		$marker_ul_re  = '[*+-]';
-		$marker_ol_re  = '\d+[.]';
-		$marker_any_re = "(?:$marker_ul_re|$marker_ol_re)";
-
-		$markers_relist = array($marker_ul_re, $marker_ol_re);
-
-		foreach ($markers_relist as $marker_re) {
-			# Re-usable pattern to match any entirel ul or ol list:
-			$whole_list_re = '
-				(								# $1 = whole list
-				  (								# $2
-					[ ]{0,'.$less_than_tab.'}
-					('.$marker_re.')			# $3 = first list item marker
-					[ ]+
-				  )
-				  (?s:.+?)
-				  (								# $4
-					  \z
-					|
-					  \n{2,}
-					  (?=\S)
-					  (?!						# Negative lookahead for another list item marker
-						[ ]*
-						'.$marker_re.'[ ]+
-					  )
-				  )
-				)
-			'; // mx
-			
-			# We use a different prefix before nested lists than top-level lists.
-			# See extended comment in _ProcessListItems().
-		
-			if ($this->list_level) {
-				$text = preg_replace_callback('{
-						^
-						'.$whole_list_re.'
-					}mx',
-					array(&$this, '_doLists_callback'), $text);
-			}
-			else {
-				$text = preg_replace_callback('{
-						(?:(?<=\n)\n|\A\n?) # Must eat the newline
-						'.$whole_list_re.'
-					}mx',
-					array(&$this, '_doLists_callback'), $text);
-			}
-		}
-
-		return $text;
-	}
-	function _doLists_callback($matches) {
-		# Re-usable patterns to match list item bullets and number markers:
-		$marker_ul_re  = '[*+-]';
-		$marker_ol_re  = '\d+[.]';
-		$marker_any_re = "(?:$marker_ul_re|$marker_ol_re)";
-		
-		$list = $matches[1];
-		$list_type = preg_match("/$marker_ul_re/", $matches[3]) ? "ul" : "ol";
-		
-		$marker_any_re = ( $list_type == "ul" ? $marker_ul_re : $marker_ol_re );
-		
-		$list .= "\n";
-		$result = $this->processListItems($list, $marker_any_re);
-		
-		$result = $this->hashBlock("<$list_type>\n" . $result . "$list_type>");
-		return "\n". $result ."\n\n";
-	}
-
-	var $list_level = 0;
-
-	function processListItems($list_str, $marker_any_re) {
-	#
-	#	Process the contents of a single ordered or unordered list, splitting it
-	#	into individual list items.
-	#
-		# The $this->list_level global keeps track of when we're inside a list.
-		# Each time we enter a list, we increment it; when we leave a list,
-		# we decrement. If it's zero, we're not in a list anymore.
-		#
-		# We do this because when we're not inside a list, we want to treat
-		# something like this:
-		#
-		#		I recommend upgrading to version
-		#		8. Oops, now this line is treated
-		#		as a sub-list.
-		#
-		# As a single paragraph, despite the fact that the second line starts
-		# with a digit-period-space sequence.
-		#
-		# Whereas when we're inside a list (or sub-list), that line will be
-		# treated as the start of a sub-list. What a kludge, huh? This is
-		# an aspect of Markdown's syntax that's hard to parse perfectly
-		# without resorting to mind-reading. Perhaps the solution is to
-		# change the syntax rules such that sub-lists must start with a
-		# starting cardinal number; e.g. "1." or "a.".
-		
-		$this->list_level++;
-
-		# trim trailing blank lines:
-		$list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str);
-
-		$list_str = preg_replace_callback('{
-			(\n)?							# leading line = $1
-			(^[ ]*)							# leading whitespace = $2
-			('.$marker_any_re.'				# list marker and space = $3
-				(?:[ ]+|(?=\n))	# space only required if item is not empty
-			)
-			((?s:.*?))						# list item text   = $4
-			(?:(\n+(?=\n))|\n)				# tailing blank line = $5
-			(?= \n* (\z | \2 ('.$marker_any_re.') (?:[ ]+|(?=\n))))
-			}xm',
-			array(&$this, '_processListItems_callback'), $list_str);
-
-		$this->list_level--;
-		return $list_str;
-	}
-	function _processListItems_callback($matches) {
-		$item = $matches[4];
-		$leading_line =& $matches[1];
-		$leading_space =& $matches[2];
-		$marker_space = $matches[3];
-		$tailing_blank_line =& $matches[5];
-
-		if ($leading_line || $tailing_blank_line || 
-			preg_match('/\n{2,}/', $item))
-		{
-			# Replace marker with the appropriate whitespace indentation
-			$item = $leading_space . str_repeat(' ', strlen($marker_space)) . $item;
-			$item = $this->runBlockGamut($this->outdent($item)."\n");
-		}
-		else {
-			# Recursion for sub-lists:
-			$item = $this->doLists($this->outdent($item));
-			$item = preg_replace('/\n+$/', '', $item);
-			$item = $this->runSpanGamut($item);
-		}
-
-		return "" . $item . "\n";
-	}
-
-
-	function doCodeBlocks($text) {
-	#
-	#	Process Markdown `` blocks.
-	#
-		$text = preg_replace_callback('{
-				(?:\n\n|\A\n?)
-				(	            # $1 = the code block -- one or more lines, starting with a space/tab
-				  (?>
-					[ ]{'.$this->tab_width.'}  # Lines must start with a tab or a tab-width of spaces
-					.*\n+
-				  )+
-				)
-				((?=^[ ]{0,'.$this->tab_width.'}\S)|\Z)	# Lookahead for non-space at line-start, or end of doc
-			}xm',
-			array(&$this, '_doCodeBlocks_callback'), $text);
-
-		return $text;
-	}
-	function _doCodeBlocks_callback($matches) {
-		$codeblock = $matches[1];
-
-		$codeblock = $this->outdent($codeblock);
-		$codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
-
-		# trim leading newlines and trailing newlines
-		$codeblock = preg_replace('/\A\n+|\n+\z/', '', $codeblock);
-
-		$codeblock = "$codeblock\n
";
-		return "\n\n".$this->hashBlock($codeblock)."\n\n";
-	}
-
-
-	function makeCodeSpan($code) {
-	#
-	# Create a code span markup for $code. Called from handleSpanToken.
-	#
-		$code = htmlspecialchars(trim($code), ENT_NOQUOTES);
-		return $this->hashPart("$code");
-	}
-
-
-	var $em_relist = array(
-		''  => '(?:(? '(?<=\S)(? '(?<=\S)(? '(?:(? '(?<=\S)(? '(?<=\S)(? '(?:(? '(?<=\S)(? '(?<=\S)(?em_relist as $em => $em_re) {
-			foreach ($this->strong_relist as $strong => $strong_re) {
-				# Construct list of allowed token expressions.
-				$token_relist = array();
-				if (isset($this->em_strong_relist["$em$strong"])) {
-					$token_relist[] = $this->em_strong_relist["$em$strong"];
-				}
-				$token_relist[] = $em_re;
-				$token_relist[] = $strong_re;
-				
-				# Construct master expression from list.
-				$token_re = '{('. implode('|', $token_relist) .')}';
-				$this->em_strong_prepared_relist["$em$strong"] = $token_re;
-			}
-		}
-	}
-	
-	function doItalicsAndBold($text) {
-		$token_stack = array('');
-		$text_stack = array('');
-		$em = '';
-		$strong = '';
-		$tree_char_em = false;
-		
-		while (1) {
-			#
-			# Get prepared regular expression for seraching emphasis tokens
-			# in current context.
-			#
-			$token_re = $this->em_strong_prepared_relist["$em$strong"];
-			
-			#
-			# Each loop iteration seach for the next emphasis token. 
-			# Each token is then passed to handleSpanToken.
-			#
-			$parts = preg_split($token_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE);
-			$text_stack[0] .= $parts[0];
-			$token =& $parts[1];
-			$text =& $parts[2];
-			
-			if (empty($token)) {
-				# Reached end of text span: empty stack without emitting.
-				# any more emphasis.
-				while ($token_stack[0]) {
-					$text_stack[1] .= array_shift($token_stack);
-					$text_stack[0] .= array_shift($text_stack);
-				}
-				break;
-			}
-			
-			$token_len = strlen($token);
-			if ($tree_char_em) {
-				# Reached closing marker while inside a three-char emphasis.
-				if ($token_len == 3) {
-					# Three-char closing marker, close em and strong.
-					array_shift($token_stack);
-					$span = array_shift($text_stack);
-					$span = $this->runSpanGamut($span);
-					$span = "$span";
-					$text_stack[0] .= $this->hashPart($span);
-					$em = '';
-					$strong = '';
-				} else {
-					# Other closing marker: close one em or strong and
-					# change current token state to match the other
-					$token_stack[0] = str_repeat($token{0}, 3-$token_len);
-					$tag = $token_len == 2 ? "strong" : "em";
-					$span = $text_stack[0];
-					$span = $this->runSpanGamut($span);
-					$span = "<$tag>$span$tag>";
-					$text_stack[0] = $this->hashPart($span);
-					$$tag = ''; # $$tag stands for $em or $strong
-				}
-				$tree_char_em = false;
-			} else if ($token_len == 3) {
-				if ($em) {
-					# Reached closing marker for both em and strong.
-					# Closing strong marker:
-					for ($i = 0; $i < 2; ++$i) {
-						$shifted_token = array_shift($token_stack);
-						$tag = strlen($shifted_token) == 2 ? "strong" : "em";
-						$span = array_shift($text_stack);
-						$span = $this->runSpanGamut($span);
-						$span = "<$tag>$span$tag>";
-						$text_stack[0] .= $this->hashPart($span);
-						$$tag = ''; # $$tag stands for $em or $strong
-					}
-				} else {
-					# Reached opening three-char emphasis marker. Push on token 
-					# stack; will be handled by the special condition above.
-					$em = $token{0};
-					$strong = "$em$em";
-					array_unshift($token_stack, $token);
-					array_unshift($text_stack, '');
-					$tree_char_em = true;
-				}
-			} else if ($token_len == 2) {
-				if ($strong) {
-					# Unwind any dangling emphasis marker:
-					if (strlen($token_stack[0]) == 1) {
-						$text_stack[1] .= array_shift($token_stack);
-						$text_stack[0] .= array_shift($text_stack);
-					}
-					# Closing strong marker:
-					array_shift($token_stack);
-					$span = array_shift($text_stack);
-					$span = $this->runSpanGamut($span);
-					$span = "$span";
-					$text_stack[0] .= $this->hashPart($span);
-					$strong = '';
-				} else {
-					array_unshift($token_stack, $token);
-					array_unshift($text_stack, '');
-					$strong = $token;
-				}
-			} else {
-				# Here $token_len == 1
-				if ($em) {
-					if (strlen($token_stack[0]) == 1) {
-						# Closing emphasis marker:
-						array_shift($token_stack);
-						$span = array_shift($text_stack);
-						$span = $this->runSpanGamut($span);
-						$span = "$span";
-						$text_stack[0] .= $this->hashPart($span);
-						$em = '';
-					} else {
-						$text_stack[0] .= $token;
-					}
-				} else {
-					array_unshift($token_stack, $token);
-					array_unshift($text_stack, '');
-					$em = $token;
-				}
-			}
-		}
-		return $text_stack[0];
-	}
-
-
-	function doBlockQuotes($text) {
-		$text = preg_replace_callback('/
-			  (								# Wrap whole match in $1
-				(?>
-				  ^[ ]*>[ ]?			# ">" at the start of a line
-					.+\n					# rest of the first line
-				  (.+\n)*					# subsequent consecutive lines
-				  \n*						# blanks
-				)+
-			  )
-			/xm',
-			array(&$this, '_doBlockQuotes_callback'), $text);
-
-		return $text;
-	}
-	function _doBlockQuotes_callback($matches) {
-		$bq = $matches[1];
-		# trim one level of quoting - trim whitespace-only lines
-		$bq = preg_replace('/^[ ]*>[ ]?|^[ ]+$/m', '', $bq);
-		$bq = $this->runBlockGamut($bq);		# recurse
-
-		$bq = preg_replace('/^/m', "  ", $bq);
-		# These leading spaces cause problem with  content, 
-		# so we need to fix that:
-		$bq = preg_replace_callback('{(\s*.+?
)}sx', 
-			array(&$this, '_DoBlockQuotes_callback2'), $bq);
-
-		return "\n". $this->hashBlock("\n$bq\n
")."\n\n";
-	}
-	function _doBlockQuotes_callback2($matches) {
-		$pre = $matches[1];
-		$pre = preg_replace('/^  /m', '', $pre);
-		return $pre;
-	}
-
-
-	function formParagraphs($text) {
-	#
-	#	Params:
-	#		$text - string to process with html  tags
-	#
-		# Strip leading and trailing lines:
-		$text = preg_replace('/\A\n+|\n+\z/', '', $text);
-
-		$grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY);
-
-		#
-		# Wrap 
 tags and unhashify HTML blocks
-		#
-		foreach ($grafs as $key => $value) {
-			if (!preg_match('/^B\x1A[0-9]+B$/', $value)) {
-				# Is a paragraph.
-				$value = $this->runSpanGamut($value);
-				$value = preg_replace('/^([ ]*)/', "
", $value);
-				$value .= "
";
-				$grafs[$key] = $this->unhash($value);
-			}
-			else {
-				# Is a block.
-				# Modify elements of @grafs in-place...
-				$graf = $value;
-				$block = $this->html_hashes[$graf];
-				$graf = $block;
-//				if (preg_match('{
-//					\A
-//					(							# $1 =  tag
-//					  
]*
-//					  \b
-//					  markdown\s*=\s*  ([\'"])	#	$2 = attr quote char
-//					  1
-//					  \2
-//					  [^>]*
-//					  >
-//					)
-//					(							# $3 = contents
-//					.*
-//					)
-//					(
)					# $4 = closing tag
-//					\z
-//					}xs', $block, $matches))
-//				{
-//					list(, $div_open, , $div_content, $div_close) = $matches;
-//
-//					# We can't call Markdown(), because that resets the hash;
-//					# that initialization code should be pulled into its own sub, though.
-//					$div_content = $this->hashHTMLBlocks($div_content);
-//					
-//					# Run document gamut methods on the content.
-//					foreach ($this->document_gamut as $method => $priority) {
-//						$div_content = $this->$method($div_content);
-//					}
-//
-//					$div_open = preg_replace(
-//						'{\smarkdown\s*=\s*([\'"]).+?\1}', '', $div_open);
-//
-//					$graf = $div_open . "\n" . $div_content . "\n" . $div_close;
-//				}
-				$grafs[$key] = $graf;
-			}
-		}
-
-		return implode("\n\n", $grafs);
-	}
-
-
-	function encodeAttribute($text) {
-	#
-	# Encode text for a double-quoted HTML attribute. This function
-	# is *not* suitable for attributes enclosed in single quotes.
-	#
-		$text = $this->encodeAmpsAndAngles($text);
-		$text = str_replace('"', '"', $text);
-		return $text;
-	}
-	
-	
-	function encodeAmpsAndAngles($text) {
-	#
-	# Smart processing for ampersands and angle brackets that need to 
-	# be encoded. Valid character entities are left alone unless the
-	# no-entities mode is set.
-	#
-		if ($this->no_entities) {
-			$text = str_replace('&', '&', $text);
-		} else {
-			# Ampersand-encoding based entirely on Nat Irons's Amputator
-			# MT plugin: 
-			$text = preg_replace('/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/', 
-								'&', $text);;
-		}
-		# Encode remaining <'s
-		$text = str_replace('<', '<', $text);
-
-		return $text;
-	}
-
-
-	function doAutoLinks($text) {
-		$text = preg_replace_callback('{<((https?|ftp|dict):[^\'">\s]+)>}i', 
-			array(&$this, '_doAutoLinks_url_callback'), $text);
-
-		# Email addresses: 
-		$text = preg_replace_callback('{
-			<
-			(?:mailto:)?
-			(
-				[-.\w\x80-\xFF]+
-				\@
-				[-a-z0-9\x80-\xFF]+(\.[-a-z0-9\x80-\xFF]+)*\.[a-z]+
-			)
-			>
-			}xi',
-			array(&$this, '_doAutoLinks_email_callback'), $text);
-
-		return $text;
-	}
-	function _doAutoLinks_url_callback($matches) {
-		$url = $this->encodeAttribute($matches[1]);
-		$link = "$url";
-		return $this->hashPart($link);
-	}
-	function _doAutoLinks_email_callback($matches) {
-		$address = $matches[1];
-		$link = $this->encodeEmailAddress($address);
-		return $this->hashPart($link);
-	}
-
-
-	function encodeEmailAddress($addr) {
-	#
-	#	Input: an email address, e.g. "foo@example.com"
-	#
-	#	Output: the email address as a mailto link, with each character
-	#		of the address encoded as either a decimal or hex entity, in
-	#		the hopes of foiling most address harvesting spam bots. E.g.:
-	#
-	#	  foo@exampl
-	#        e.com
-	#
-	#	Based by a filter by Matthew Wickline, posted to BBEdit-Talk.
-	#   With some optimizations by Milian Wolff.
-	#
-		$addr = "mailto:" . $addr;
-		$chars = preg_split('/(? $char) {
-			$ord = ord($char);
-			# Ignore non-ascii chars.
-			if ($ord < 128) {
-				$r = ($seed * (1 + $key)) % 100; # Pseudo-random function.
-				# roughly 10% raw, 45% hex, 45% dec
-				# '@' *must* be encoded. I insist.
-				if ($r > 90 && $char != '@') /* do nothing */;
-				else if ($r < 45) $chars[$key] = ''.dechex($ord).';';
-				else              $chars[$key] = ''.$ord.';';
-			}
-		}
-		
-		$addr = implode('', $chars);
-		$text = implode('', array_slice($chars, 7)); # text without `mailto:`
-		$addr = "$text";
-
-		return $addr;
-	}
-
-
-	function parseSpan($str) {
-	#
-	# Take the string $str and parse it into tokens, hashing embeded HTML,
-	# escaped characters and handling code spans.
-	#
-		$output = '';
-		
-		$span_re = '{
-				(
-					\\\\'.$this->escape_chars_re.'
-				|
-					(?no_markup ? '' : '
-				|
-							# comment
-				|
-					<\?.*?\?> | <%.*?%>		# processing instruction
-				|
-					<[/!$]?[-a-zA-Z0-9:]+	# regular tags
-					(?>
-						\s
-						(?>[^"\'>]+|"[^"]*"|\'[^\']*\')*
-					)?
-					>
-			').'
-				)
-				}xs';
-
-		while (1) {
-			#
-			# Each loop iteration seach for either the next tag, the next 
-			# openning code span marker, or the next escaped character. 
-			# Each token is then passed to handleSpanToken.
-			#
-			$parts = preg_split($span_re, $str, 2, PREG_SPLIT_DELIM_CAPTURE);
-			
-			# Create token from text preceding tag.
-			if ($parts[0] != "") {
-				$output .= $parts[0];
-			}
-			
-			# Check if we reach the end.
-			if (isset($parts[1])) {
-				$output .= $this->handleSpanToken($parts[1], $parts[2]);
-				$str = $parts[2];
-			}
-			else {
-				break;
-			}
-		}
-		
-		return $output;
-	}
-	
-	
-	function handleSpanToken($token, &$str) {
-	#
-	# Handle $token provided by parseSpan by determining its nature and 
-	# returning the corresponding value that should replace it.
-	#
-		switch ($token{0}) {
-			case "\\":
-				return $this->hashPart("". ord($token{1}). ";");
-			case "`":
-				# Search for end marker in remaining text.
-				if (preg_match('/^(.*?[^`])'.preg_quote($token).'(?!`)(.*)$/sm', 
-					$str, $matches))
-				{
-					$str = $matches[2];
-					$codespan = $this->makeCodeSpan($matches[1]);
-					return $this->hashPart($codespan);
-				}
-				return $token; // return as text since no ending marker found.
-			default:
-				return $this->hashPart($token);
-		}
-	}
-
-
-	function outdent($text) {
-	#
-	# Remove one level of line-leading tabs or spaces
-	#
-		return preg_replace('/^(\t|[ ]{1,'.$this->tab_width.'})/m', '', $text);
-	}
-
-
-	# String length function for detab. `_initDetab` will create a function to 
-	# hanlde UTF-8 if the default function does not exist.
-	var $utf8_strlen = 'mb_strlen';
-	
-	function detab($text) {
-	#
-	# Replace tabs with the appropriate amount of space.
-	#
-		# For each line we separate the line in blocks delemited by
-		# tab characters. Then we reconstruct every line by adding the 
-		# appropriate number of space between each blocks.
-		
-		$text = preg_replace_callback('/^.*\t.*$/m',
-			array(&$this, '_detab_callback'), $text);
-
-		return $text;
-	}
-	function _detab_callback($matches) {
-		$line = $matches[0];
-		$strlen = $this->utf8_strlen; # strlen function for UTF-8.
-		
-		# Split in blocks.
-		$blocks = explode("\t", $line);
-		# Add each blocks to the line.
-		$line = $blocks[0];
-		unset($blocks[0]); # Do not add first block twice.
-		foreach ($blocks as $block) {
-			# Calculate amount of space, insert spaces, insert block.
-			$amount = $this->tab_width - 
-				$strlen($line, 'UTF-8') % $this->tab_width;
-			$line .= str_repeat(" ", $amount) . $block;
-		}
-		return $line;
-	}
-	function _initDetab() {
-	#
-	# Check for the availability of the function in the `utf8_strlen` property
-	# (initially `mb_strlen`). If the function is not available, create a 
-	# function that will loosely count the number of UTF-8 characters with a
-	# regular expression.
-	#
-		if (function_exists($this->utf8_strlen)) return;
-		$this->utf8_strlen = create_function('$text', 'return preg_match_all(
-			"/[\\\\x00-\\\\xBF]|[\\\\xC0-\\\\xFF][\\\\x80-\\\\xBF]*/", 
-			$text, $m);');
-	}
-
-
-	function unhash($text) {
-	#
-	# Swap back in all the tags hashed by _HashHTMLBlocks.
-	#
-		return preg_replace_callback('/(.)\x1A[0-9]+\1/', 
-			array(&$this, '_unhash_callback'), $text);
-	}
-	function _unhash_callback($matches) {
-		return $this->html_hashes[$matches[0]];
-	}
-
-}
-
-
-#
-# Markdown Extra Parser Class
-#
-
-class MarkdownExtra_Parser extends Markdown_Parser {
-
-	# Prefix for footnote ids.
-	var $fn_id_prefix = "";
-	
-	# Optional title attribute for footnote links and backlinks.
-	var $fn_link_title = MARKDOWN_FN_LINK_TITLE;
-	var $fn_backlink_title = MARKDOWN_FN_BACKLINK_TITLE;
-	
-	# Optional class attribute for footnote links and backlinks.
-	var $fn_link_class = MARKDOWN_FN_LINK_CLASS;
-	var $fn_backlink_class = MARKDOWN_FN_BACKLINK_CLASS;
-	
-	# Predefined abbreviations.
-	var $predef_abbr = array();
-
-
-	function MarkdownExtra_Parser() {
-	#
-	# Constructor function. Initialize the parser object.
-	#
-		# Add extra escapable characters before parent constructor 
-		# initialize the table.
-		$this->escape_chars .= ':|';
-		
-		# Insert extra document, block, and span transformations. 
-		# Parent constructor will do the sorting.
-		$this->document_gamut += array(
-			"doFencedCodeBlocks" => 5,
-			"stripFootnotes"     => 15,
-			"stripAbbreviations" => 25,
-			"appendFootnotes"    => 50,
-			);
-		$this->block_gamut += array(
-			"doFencedCodeBlocks" => 5,
-			"doTables"           => 15,
-			"doDefLists"         => 45,
-			);
-		$this->span_gamut += array(
-			"doFootnotes"        => 5,
-			"doAbbreviations"    => 70,
-			);
-		
-		parent::Markdown_Parser();
-	}
-	
-	
-	# Extra variables used during extra transformations.
-	var $footnotes = array();
-	var $footnotes_ordered = array();
-	var $abbr_desciptions = array();
-	var $abbr_word_re = '';
-	
-	# Give the current footnote number.
-	var $footnote_counter = 1;
-	
-	
-	function setup() {
-	#
-	# Setting up Extra-specific variables.
-	#
-		parent::setup();
-		
-		$this->footnotes = array();
-		$this->footnotes_ordered = array();
-		$this->abbr_desciptions = array();
-		$this->abbr_word_re = '';
-		$this->footnote_counter = 1;
-		
-		foreach ($this->predef_abbr as $abbr_word => $abbr_desc) {
-			if ($this->abbr_word_re)
-				$this->abbr_word_re .= '|';
-			$this->abbr_word_re .= preg_quote($abbr_word);
-			$this->abbr_desciptions[$abbr_word] = trim($abbr_desc);
-		}
-	}
-	
-	function teardown() {
-	#
-	# Clearing Extra-specific variables.
-	#
-		$this->footnotes = array();
-		$this->footnotes_ordered = array();
-		$this->abbr_desciptions = array();
-		$this->abbr_word_re = '';
-		
-		parent::teardown();
-	}
-	
-	
-	### HTML Block Parser ###
-	
-	# Tags that are always treated as block tags:
-	var $block_tags_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|form|fieldset|iframe|hr|legend';
-	
-	# Tags treated as block tags only if the opening tag is alone on it's line:
-	var $context_block_tags_re = 'script|noscript|math|ins|del';
-	
-	# Tags where markdown="1" default to span mode:
-	var $contain_span_tags_re = 'p|h[1-6]|li|dd|dt|td|th|legend|address';
-	
-	# Tags which must not have their contents modified, no matter where 
-	# they appear:
-	var $clean_tags_re = 'script|math';
-	
-	# Tags that do not need to be closed.
-	var $auto_close_tags_re = 'hr|img';
-	
-
-	function hashHTMLBlocks($text) {
-	#
-	# Hashify HTML Blocks and "clean tags".
-	#
-	# We only want to do this for block-level HTML tags, such as headers,
-	# lists, and tables. That's because we still want to wrap s around
-	# "paragraphs" that are wrapped in non-block-level tags, such as anchors,
-	# phrase emphasis, and spans. The list of tags we're looking for is
-	# hard-coded.
-	#
-	# This works by calling _HashHTMLBlocks_InMarkdown, which then calls
-	# _HashHTMLBlocks_InHTML when it encounter block tags. When the markdown="1" 
-	# attribute is found whitin a tag, _HashHTMLBlocks_InHTML calls back
-	#  _HashHTMLBlocks_InMarkdown to handle the Markdown syntax within the tag.
-	# These two functions are calling each other. It's recursive!
-	#
-		#
-		# Call the HTML-in-Markdown hasher.
-		#
-		list($text, ) = $this->_hashHTMLBlocks_inMarkdown($text);
-		
-		return $text;
-	}
-	function _hashHTMLBlocks_inMarkdown($text, $indent = 0, 
-										$enclosing_tag_re = '', $span = false)
-	{
-	#
-	# Parse markdown text, calling _HashHTMLBlocks_InHTML for block tags.
-	#
-	# *   $indent is the number of space to be ignored when checking for code 
-	#     blocks. This is important because if we don't take the indent into 
-	#     account, something like this (which looks right) won't work as expected:
-	#
-	#     
-	#         
-	#         Hello World.  <-- Is this a Markdown code block or text?
-	#         
  <-- Is this a Markdown code block or a real tag?
-	#     
-	#
-	#     If you don't like this, just don't indent the tag on which
-	#     you apply the markdown="1" attribute.
-	#
-	# *   If $enclosing_tag_re is not empty, stops at the first unmatched closing 
-	#     tag with that name. Nested tags supported.
-	#
-	# *   If $span is true, text inside must treated as span. So any double 
-	#     newline will be replaced by a single newline so that it does not create 
-	#     paragraphs.
-	#
-	# Returns an array of that form: ( processed text , remaining text )
-	#
-		if ($text === '') return array('', '');
-
-		# Regex to check for the presense of newlines around a block tag.
-		$newline_before_re = '/(?:^\n?|\n\n)*$/';
-		$newline_after_re = 
-			'{
-				^						# Start of text following the tag.
-				(?>[ ]*)?		# Optional comment.
-				[ ]*\n					# Must be followed by newline.
-			}xs';
-		
-		# Regex to match any tag.
-		$block_tag_re =
-			'{
-				(					# $2: Capture hole tag.
-					?					# Any opening or closing tag.
-						(?>				# Tag name.
-							'.$this->block_tags_re.'			|
-							'.$this->context_block_tags_re.'	|
-							'.$this->clean_tags_re.'        	|
-							(?!\s)'.$enclosing_tag_re.'
-						)
-						(?:
-							(?=[\s"\'/a-zA-Z0-9])	# Allowed characters after tag name.
-							(?>
-								".*?"		|	# Double quotes (can contain `>`)
-								\'.*?\'   	|	# Single quotes (can contain `>`)
-								.+?				# Anything but quotes and `>`.
-							)*?
-						)?
-					>					# End of tag.
-				|
-						# HTML Comment
-				|
-					<\?.*?\?> | <%.*?%>	# Processing instruction
-				|
-						# CData Block
-				|
-					# Code span marker
-					`+
-				'. ( !$span ? ' # If not in span.
-				|
-					# Indented code block
-					(?> ^[ ]*\n? | \n[ ]*\n )
-					[ ]{'.($indent+4).'}[^\n]* \n
-					(?>
-						(?: [ ]{'.($indent+4).'}[^\n]* | [ ]* ) \n
-					)*
-				|
-					# Fenced code block marker
-					(?> ^ | \n )
-					[ ]{'.($indent).'}~~~+[ ]*\n
-				' : '' ). ' # End (if not is span).
-				)
-			}xs';
-
-		
-		$depth = 0;		# Current depth inside the tag tree.
-		$parsed = "";	# Parsed text that will be returned.
-
-		#
-		# Loop through every tag until we find the closing tag of the parent
-		# or loop until reaching the end of text if no parent tag specified.
-		#
-		do {
-			#
-			# Split the text using the first $tag_match pattern found.
-			# Text before  pattern will be first in the array, text after
-			# pattern will be at the end, and between will be any catches made 
-			# by the pattern.
-			#
-			$parts = preg_split($block_tag_re, $text, 2, 
-								PREG_SPLIT_DELIM_CAPTURE);
-			
-			# If in Markdown span mode, add a empty-string span-level hash 
-			# after each newline to prevent triggering any block element.
-			if ($span) {
-				$void = $this->hashPart("", ':');
-				$newline = "$void\n";
-				$parts[0] = $void . str_replace("\n", $newline, $parts[0]) . $void;
-			}
-			
-			$parsed .= $parts[0]; # Text before current tag.
-			
-			# If end of $text has been reached. Stop loop.
-			if (count($parts) < 3) {
-				$text = "";
-				break;
-			}
-			
-			$tag  = $parts[1]; # Tag to handle.
-			$text = $parts[2]; # Remaining text after current tag.
-			$tag_re = preg_quote($tag); # For use in a regular expression.
-			
-			#
-			# Check for: Code span marker
-			#
-			if ($tag{0} == "`") {
-				# Find corresponding end marker.
-				$tag_re = preg_quote($tag);
-				if (preg_match('{^(?>.+?|\n(?!\n))*?(?.*\n)+?'.$tag_re.' *\n}', $text, 
-						$matches)) 
-					{
-						# End marker found: pass text unchanged until marker.
-						$parsed .= $tag . $matches[0];
-						$text = substr($text, strlen($matches[0]));
-					}
-					else {
-						# No end marker: just skip it.
-						$parsed .= $tag;
-					}
-				}
-			}
-			#
-			# Check for: Opening Block level tag or
-			#            Opening Context Block tag (like ins and del) 
-			#               used as a block tag (tag is alone on it's line).
-			#
-			else if (preg_match('{^<(?:'.$this->block_tags_re.')\b}', $tag) ||
-				(	preg_match('{^<(?:'.$this->context_block_tags_re.')\b}', $tag) &&
-					preg_match($newline_before_re, $parsed) &&
-					preg_match($newline_after_re, $text)	)
-				)
-			{
-				# Need to parse tag and following text using the HTML parser.
-				list($block_text, $text) = 
-					$this->_hashHTMLBlocks_inHTML($tag . $text, "hashBlock", true);
-				
-				# Make sure it stays outside of any paragraph by adding newlines.
-				$parsed .= "\n\n$block_text\n\n";
-			}
-			#
-			# Check for: Clean tag (like script, math)
-			#            HTML Comments, processing instructions.
-			#
-			else if (preg_match('{^<(?:'.$this->clean_tags_re.')\b}', $tag) ||
-				$tag{1} == '!' || $tag{1} == '?')
-			{
-				# Need to parse tag and following text using the HTML parser.
-				# (don't check for markdown attribute)
-				list($block_text, $text) = 
-					$this->_hashHTMLBlocks_inHTML($tag . $text, "hashClean", false);
-				
-				$parsed .= $block_text;
-			}
-			#
-			# Check for: Tag with same name as enclosing tag.
-			#
-			else if ($enclosing_tag_re !== '' &&
-				# Same name as enclosing tag.
-				preg_match('{^?(?:'.$enclosing_tag_re.')\b}', $tag))
-			{
-				#
-				# Increase/decrease nested tag count.
-				#
-				if ($tag{1} == '/')						$depth--;
-				else if ($tag{strlen($tag)-2} != '/')	$depth++;
-
-				if ($depth < 0) {
-					#
-					# Going out of parent element. Clean up and break so we
-					# return to the calling function.
-					#
-					$text = $tag . $text;
-					break;
-				}
-				
-				$parsed .= $tag;
-			}
-			else {
-				$parsed .= $tag;
-			}
-		} while ($depth >= 0);
-		
-		return array($parsed, $text);
-	}
-	function _hashHTMLBlocks_inHTML($text, $hash_method, $md_attr) {
-	#
-	# Parse HTML, calling _HashHTMLBlocks_InMarkdown for block tags.
-	#
-	# *   Calls $hash_method to convert any blocks.
-	# *   Stops when the first opening tag closes.
-	# *   $md_attr indicate if the use of the `markdown="1"` attribute is allowed.
-	#     (it is not inside clean tags)
-	#
-	# Returns an array of that form: ( processed text , remaining text )
-	#
-		if ($text === '') return array('', '');
-		
-		# Regex to match `markdown` attribute inside of a tag.
-		$markdown_attr_re = '
-			{
-				\s*			# Eat whitespace before the `markdown` attribute
-				markdown
-				\s*=\s*
-				(?>
-					(["\'])		# $1: quote delimiter		
-					(.*?)		# $2: attribute value
-					\1			# matching delimiter	
-				|
-					([^\s>]*)	# $3: unquoted attribute value
-				)
-				()				# $4: make $3 always defined (avoid warnings)
-			}xs';
-		
-		# Regex to match any tag.
-		$tag_re = '{
-				(					# $2: Capture hole tag.
-					?					# Any opening or closing tag.
-						[\w:$]+			# Tag name.
-						(?:
-							(?=[\s"\'/a-zA-Z0-9])	# Allowed characters after tag name.
-							(?>
-								".*?"		|	# Double quotes (can contain `>`)
-								\'.*?\'   	|	# Single quotes (can contain `>`)
-								.+?				# Anything but quotes and `>`.
-							)*?
-						)?
-					>					# End of tag.
-				|
-						# HTML Comment
-				|
-					<\?.*?\?> | <%.*?%>	# Processing instruction
-				|
-						# CData Block
-				)
-			}xs';
-		
-		$original_text = $text;		# Save original text in case of faliure.
-		
-		$depth		= 0;	# Current depth inside the tag tree.
-		$block_text	= "";	# Temporary text holder for current text.
-		$parsed		= "";	# Parsed text that will be returned.
-
-		#
-		# Get the name of the starting tag.
-		# (This pattern makes $base_tag_name_re safe without quoting.)
-		#
-		if (preg_match('/^<([\w:$]*)\b/', $text, $matches))
-			$base_tag_name_re = $matches[1];
-
-		#
-		# Loop through every tag until we find the corresponding closing tag.
-		#
-		do {
-			#
-			# Split the text using the first $tag_match pattern found.
-			# Text before  pattern will be first in the array, text after
-			# pattern will be at the end, and between will be any catches made 
-			# by the pattern.
-			#
-			$parts = preg_split($tag_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE);
-			
-			if (count($parts) < 3) {
-				#
-				# End of $text reached with unbalenced tag(s).
-				# In that case, we return original text unchanged and pass the
-				# first character as filtered to prevent an infinite loop in the 
-				# parent function.
-				#
-				return array($original_text{0}, substr($original_text, 1));
-			}
-			
-			$block_text .= $parts[0]; # Text before current tag.
-			$tag         = $parts[1]; # Tag to handle.
-			$text        = $parts[2]; # Remaining text after current tag.
-			
-			#
-			# Check for: Auto-close tag (like 
)
-			#			 Comments and Processing Instructions.
-			#
-			if (preg_match('{^?(?:'.$this->auto_close_tags_re.')\b}', $tag) ||
-				$tag{1} == '!' || $tag{1} == '?')
-			{
-				# Just add the tag to the block as if it was text.
-				$block_text .= $tag;
-			}
-			else {
-				#
-				# Increase/decrease nested tag count. Only do so if
-				# the tag's name match base tag's.
-				#
-				if (preg_match('{^?'.$base_tag_name_re.'\b}', $tag)) {
-					if ($tag{1} == '/')						$depth--;
-					else if ($tag{strlen($tag)-2} != '/')	$depth++;
-				}
-				
-				#
-				# Check for `markdown="1"` attribute and handle it.
-				#
-				if ($md_attr && 
-					preg_match($markdown_attr_re, $tag, $attr_m) &&
-					preg_match('/^1|block|span$/', $attr_m[2] . $attr_m[3]))
-				{
-					# Remove `markdown` attribute from opening tag.
-					$tag = preg_replace($markdown_attr_re, '', $tag);
-					
-					# Check if text inside this tag must be parsed in span mode.
-					$this->mode = $attr_m[2] . $attr_m[3];
-					$span_mode = $this->mode == 'span' || $this->mode != 'block' &&
-						preg_match('{^<(?:'.$this->contain_span_tags_re.')\b}', $tag);
-					
-					# Calculate indent before tag.
-					if (preg_match('/(?:^|\n)( *?)(?! ).*?$/', $block_text, $matches)) {
-						$strlen = $this->utf8_strlen;
-						$indent = $strlen($matches[1], 'UTF-8');
-					} else {
-						$indent = 0;
-					}
-					
-					# End preceding block with this tag.
-					$block_text .= $tag;
-					$parsed .= $this->$hash_method($block_text);
-					
-					# Get enclosing tag name for the ParseMarkdown function.
-					# (This pattern makes $tag_name_re safe without quoting.)
-					preg_match('/^<([\w:$]*)\b/', $tag, $matches);
-					$tag_name_re = $matches[1];
-					
-					# Parse the content using the HTML-in-Markdown parser.
-					list ($block_text, $text)
-						= $this->_hashHTMLBlocks_inMarkdown($text, $indent, 
-							$tag_name_re, $span_mode);
-					
-					# Outdent markdown text.
-					if ($indent > 0) {
-						$block_text = preg_replace("/^[ ]{1,$indent}/m", "", 
-													$block_text);
-					}
-					
-					# Append tag content to parsed text.
-					if (!$span_mode)	$parsed .= "\n\n$block_text\n\n";
-					else				$parsed .= "$block_text";
-					
-					# Start over a new block.
-					$block_text = "";
-				}
-				else $block_text .= $tag;
-			}
-			
-		} while ($depth > 0);
-		
-		#
-		# Hash last block text that wasn't processed inside the loop.
-		#
-		$parsed .= $this->$hash_method($block_text);
-		
-		return array($parsed, $text);
-	}
-
-
-	function hashClean($text) {
-	#
-	# Called whenever a tag must be hashed when a function insert a "clean" tag
-	# in $text, it pass through this function and is automaticaly escaped, 
-	# blocking invalid nested overlap.
-	#
-		return $this->hashPart($text, 'C');
-	}
-
-
-	function doHeaders($text) {
-	#
-	# Redefined to add id attribute support.
-	#
-		# Setext-style headers:
-		#	  Header 1  {#header1}
-		#	  ========
-		#  
-		#	  Header 2  {#header2}
-		#	  --------
-		#
-		$text = preg_replace_callback(
-			'{
-				(^.+?)								# $1: Header text
-				(?:[ ]+\{\#([-_:a-zA-Z0-9]+)\})?	# $2: Id attribute
-				[ ]*\n(=+|-+)[ ]*\n+				# $3: Header footer
-			}mx',
-			array(&$this, '_doHeaders_callback_setext'), $text);
-
-		# atx-style headers:
-		#	# Header 1        {#header1}
-		#	## Header 2       {#header2}
-		#	## Header 2 with closing hashes ##  {#header3}
-		#	...
-		#	###### Header 6   {#header2}
-		#
-		$text = preg_replace_callback('{
-				^(\#{1,6})	# $1 = string of #\'s
-				[ ]*
-				(.+?)		# $2 = Header text
-				[ ]*
-				\#*			# optional closing #\'s (not counted)
-				(?:[ ]+\{\#([-_:a-zA-Z0-9]+)\})? # id attribute
-				[ ]*
-				\n+
-			}xm',
-			array(&$this, '_doHeaders_callback_atx'), $text);
-
-		return $text;
-	}
-	function _doHeaders_attr($attr) {
-		if (empty($attr))  return "";
-		return " id=\"$attr\"";
-	}
-	function _doHeaders_callback_setext($matches) {
-		if ($matches[3] == '-' && preg_match('{^- }', $matches[1]))
-			return $matches[0];
-		$level = $matches[3]{0} == '=' ? 1 : 2;
-		$attr  = $this->_doHeaders_attr($id =& $matches[2]);
-		$block = "
".$this->runSpanGamut($matches[1])."";
-		return "\n" . $this->hashBlock($block) . "\n\n";
-	}
-	function _doHeaders_callback_atx($matches) {
-		$level = strlen($matches[1]);
-		$attr  = $this->_doHeaders_attr($id =& $matches[3]);
-		$block = "
".$this->runSpanGamut($matches[2])."";
-		return "\n" . $this->hashBlock($block) . "\n\n";
-	}
-
-
-	function doTables($text) {
-	#
-	# Form HTML tables.
-	#
-		$less_than_tab = $this->tab_width - 1;
-		#
-		# Find tables with leading pipe.
-		#
-		#	| Header 1 | Header 2
-		#	| -------- | --------
-		#	| Cell 1   | Cell 2
-		#	| Cell 3   | Cell 4
-		#
-		$text = preg_replace_callback('
-			{
-				^							# Start of a line
-				[ ]{0,'.$less_than_tab.'}	# Allowed whitespace.
-				[|]							# Optional leading pipe (present)
-				(.+) \n						# $1: Header row (at least one pipe)
-				
-				[ ]{0,'.$less_than_tab.'}	# Allowed whitespace.
-				[|] ([ ]*[-:]+[-| :]*) \n	# $2: Header underline
-				
-				(							# $3: Cells
-					(?>
-						[ ]*				# Allowed whitespace.
-						[|] .* \n			# Row content.
-					)*
-				)
-				(?=\n|\Z)					# Stop at final double newline.
-			}xm',
-			array(&$this, '_doTable_leadingPipe_callback'), $text);
-		
-		#
-		# Find tables without leading pipe.
-		#
-		#	Header 1 | Header 2
-		#	-------- | --------
-		#	Cell 1   | Cell 2
-		#	Cell 3   | Cell 4
-		#
-		$text = preg_replace_callback('
-			{
-				^							# Start of a line
-				[ ]{0,'.$less_than_tab.'}	# Allowed whitespace.
-				(\S.*[|].*) \n				# $1: Header row (at least one pipe)
-				
-				[ ]{0,'.$less_than_tab.'}	# Allowed whitespace.
-				([-:]+[ ]*[|][-| :]*) \n	# $2: Header underline
-				
-				(							# $3: Cells
-					(?>
-						.* [|] .* \n		# Row content
-					)*
-				)
-				(?=\n|\Z)					# Stop at final double newline.
-			}xm',
-			array(&$this, '_DoTable_callback'), $text);
-
-		return $text;
-	}
-	function _doTable_leadingPipe_callback($matches) {
-		$head		= $matches[1];
-		$underline	= $matches[2];
-		$content	= $matches[3];
-		
-		# Remove leading pipe for each row.
-		$content	= preg_replace('/^ *[|]/m', '', $content);
-		
-		return $this->_doTable_callback(array($matches[0], $head, $underline, $content));
-	}
-	function _doTable_callback($matches) {
-		$head		= $matches[1];
-		$underline	= $matches[2];
-		$content	= $matches[3];
-
-		# Remove any tailing pipes for each line.
-		$head		= preg_replace('/[|] *$/m', '', $head);
-		$underline	= preg_replace('/[|] *$/m', '', $underline);
-		$content	= preg_replace('/[|] *$/m', '', $content);
-		
-		# Reading alignement from header underline.
-		$separators	= preg_split('/ *[|] */', $underline);
-		foreach ($separators as $n => $s) {
-			if (preg_match('/^ *-+: *$/', $s))		$attr[$n] = ' align="right"';
-			else if (preg_match('/^ *:-+: *$/', $s))$attr[$n] = ' align="center"';
-			else if (preg_match('/^ *:-+ *$/', $s))	$attr[$n] = ' align="left"';
-			else									$attr[$n] = '';
-		}
-		
-		# Parsing span elements, including code spans, character escapes, 
-		# and inline HTML tags, so that pipes inside those gets ignored.
-		$head		= $this->parseSpan($head);
-		$headers	= preg_split('/ *[|] */', $head);
-		$col_count	= count($headers);
-		
-		# Write column headers.
-		$text = "
\n";
-		$text .= "\n";
-		$text .= "\n";
-		foreach ($headers as $n => $header)
-			$text .= "  | ".$this->runSpanGamut(trim($header))." | \n";
-		$text .= "
\n";
-		$text .= "\n";
-		
-		# Split content by row.
-		$rows = explode("\n", trim($content, "\n"));
-		
-		$text .= "\n";
-		foreach ($rows as $row) {
-			# Parsing span elements, including code spans, character escapes, 
-			# and inline HTML tags, so that pipes inside those gets ignored.
-			$row = $this->parseSpan($row);
-			
-			# Split row by cell.
-			$row_cells = preg_split('/ *[|] */', $row, $col_count);
-			$row_cells = array_pad($row_cells, $col_count, '');
-			
-			$text .= "\n";
-			foreach ($row_cells as $n => $cell)
-				$text .= "  | ".$this->runSpanGamut(trim($cell))." | \n";
-			$text .= "
\n";
-		}
-		$text .= "\n";
-		$text .= "
";
-		
-		return $this->hashBlock($text) . "\n";
-	}
-
-	
-	function doDefLists($text) {
-	#
-	# Form HTML definition lists.
-	#
-		$less_than_tab = $this->tab_width - 1;
-
-		# Re-usable pattern to match any entire dl list:
-		$whole_list_re = '(?>
-			(								# $1 = whole list
-			  (								# $2
-				[ ]{0,'.$less_than_tab.'}
-				((?>.*\S.*\n)+)				# $3 = defined term
-				\n?
-				[ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition
-			  )
-			  (?s:.+?)
-			  (								# $4
-				  \z
-				|
-				  \n{2,}
-				  (?=\S)
-				  (?!						# Negative lookahead for another term
-					[ ]{0,'.$less_than_tab.'}
-					(?: \S.*\n )+?			# defined term
-					\n?
-					[ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition
-				  )
-				  (?!						# Negative lookahead for another definition
-					[ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition
-				  )
-			  )
-			)
-		)'; // mx
-
-		$text = preg_replace_callback('{
-				(?>\A\n?|(?<=\n\n))
-				'.$whole_list_re.'
-			}mx',
-			array(&$this, '_doDefLists_callback'), $text);
-
-		return $text;
-	}
-	function _doDefLists_callback($matches) {
-		# Re-usable patterns to match list item bullets and number markers:
-		$list = $matches[1];
-		
-		# Turn double returns into triple returns, so that we can make a
-		# paragraph for the last item in a list, if necessary:
-		$result = trim($this->processDefListItems($list));
-		$result = "
\n" . $result . "\n
";
-		return $this->hashBlock($result) . "\n\n";
-	}
-
-
-	function processDefListItems($list_str) {
-	#
-	#	Process the contents of a single definition list, splitting it
-	#	into individual term and definition list items.
-	#
-		$less_than_tab = $this->tab_width - 1;
-		
-		# trim trailing blank lines:
-		$list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str);
-
-		# Process definition terms.
-		$list_str = preg_replace_callback('{
-			(?>\A\n?|\n\n+)					# leading line
-			(								# definition terms = $1
-				[ ]{0,'.$less_than_tab.'}	# leading whitespace
-				(?![:][ ]|[ ])				# negative lookahead for a definition 
-											#   mark (colon) or more whitespace.
-				(?> \S.* \n)+?				# actual term (not whitespace).	
-			)			
-			(?=\n?[ ]{0,3}:[ ])				# lookahead for following line feed 
-											#   with a definition mark.
-			}xm',
-			array(&$this, '_processDefListItems_callback_dt'), $list_str);
-
-		# Process actual definitions.
-		$list_str = preg_replace_callback('{
-			\n(\n+)?						# leading line = $1
-			(								# marker space = $2
-				[ ]{0,'.$less_than_tab.'}	# whitespace before colon
-				[:][ ]+						# definition mark (colon)
-			)
-			((?s:.+?))						# definition text = $3
-			(?= \n+ 						# stop at next definition mark,
-				(?:							# next term or end of text
-					[ ]{0,'.$less_than_tab.'} [:][ ]	|
-					
 | \z
-				)						
-			)					
-			}xm',
-			array(&$this, '_processDefListItems_callback_dd'), $list_str);
-
-		return $list_str;
-	}
-	function _processDefListItems_callback_dt($matches) {
-		$terms = explode("\n", trim($matches[1]));
-		$text = '';
-		foreach ($terms as $term) {
-			$term = $this->runSpanGamut(trim($term));
-			$text .= "\n" . $term . "";
-		}
-		return $text . "\n";
-	}
-	function _processDefListItems_callback_dd($matches) {
-		$leading_line	= $matches[1];
-		$marker_space	= $matches[2];
-		$def			= $matches[3];
-
-		if ($leading_line || preg_match('/\n{2,}/', $def)) {
-			# Replace marker with the appropriate whitespace indentation
-			$def = str_repeat(' ', strlen($marker_space)) . $def;
-			$def = $this->runBlockGamut($this->outdent($def . "\n\n"));
-			$def = "\n". $def ."\n";
-		}
-		else {
-			$def = rtrim($def);
-			$def = $this->runSpanGamut($this->outdent($def));
-		}
-
-		return "\n" . $def . "\n";
-	}
-
-
-	function doFencedCodeBlocks($text) {
-	#
-	# Adding the fenced code block syntax to regular Markdown:
-	#
-	# ~~~
-	# Code block
-	# ~~~
-	#
-		$less_than_tab = $this->tab_width;
-		
-		$text = preg_replace_callback('{
-				(?:\n|\A)
-				# 1: Opening marker
-				(
-					~{3,} # Marker: three tilde or more.
-				)
-				[ ]* \n # Whitespace and newline following marker.
-				
-				# 2: Content
-				(
-					(?>
-						(?!\1 [ ]* \n)	# Not a closing marker.
-						.*\n+
-					)+
-				)
-				
-				# Closing marker.
-				\1 [ ]* \n
-			}xm',
-			array(&$this, '_doFencedCodeBlocks_callback'), $text);
-
-		return $text;
-	}
-	function _doFencedCodeBlocks_callback($matches) {
-		$codeblock = $matches[2];
-		$codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
-		$codeblock = preg_replace_callback('/^\n+/',
-			array(&$this, '_doFencedCodeBlocks_newlines'), $codeblock);
-		$codeblock = "
$codeblock
";
-		return "\n\n".$this->hashBlock($codeblock)."\n\n";
-	}
-	function _doFencedCodeBlocks_newlines($matches) {
-		return str_repeat("
empty_element_suffix", 
-			strlen($matches[0]));
-	}
-
-
-	#
-	# Redefining emphasis markers so that emphasis by underscore does not
-	# work in the middle of a word.
-	#
-	var $em_relist = array(
-		''  => '(?:(? '(?<=\S)(? '(?<=\S)(? '(?:(? '(?<=\S)(? '(?<=\S)(? '(?:(? '(?<=\S)(? '(?<=\S)(? tags
-	#
-		# Strip leading and trailing lines:
-		$text = preg_replace('/\A\n+|\n+\z/', '', $text);
-		
-		$grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY);
-
-		#
-		# Wrap 
 tags and unhashify HTML blocks
-		#
-		foreach ($grafs as $key => $value) {
-			$value = trim($this->runSpanGamut($value));
-			
-			# Check if this should be enclosed in a paragraph.
-			# Clean tag hashes & block tag hashes are left alone.
-			$is_p = !preg_match('/^B\x1A[0-9]+B|^C\x1A[0-9]+C$/', $value);
-			
-			if ($is_p) {
-				$value = "
$value
";
-			}
-			$grafs[$key] = $value;
-		}
-		
-		# Join grafs in one text, then unhash HTML tags. 
-		$text = implode("\n\n", $grafs);
-		
-		# Finish by removing any tag hashes still present in $text.
-		$text = $this->unhash($text);
-		
-		return $text;
-	}
-	
-	
-	### Footnotes
-	
-	function stripFootnotes($text) {
-	#
-	# Strips link definitions from text, stores the URLs and titles in
-	# hash references.
-	#
-		$less_than_tab = $this->tab_width - 1;
-
-		# Link defs are in the form: [^id]: url "optional title"
-		$text = preg_replace_callback('{
-			^[ ]{0,'.$less_than_tab.'}\[\^(.+?)\][ ]?:	# note_id = $1
-			  [ ]*
-			  \n?					# maybe *one* newline
-			(						# text = $2 (no blank lines allowed)
-				(?:					
-					.+				# actual text
-				|
-					\n				# newlines but 
-					(?!\[\^.+?\]:\s)# negative lookahead for footnote marker.
-					(?!\n+[ ]{0,3}\S)# ensure line is not blank and followed 
-									# by non-indented content
-				)*
-			)		
-			}xm',
-			array(&$this, '_stripFootnotes_callback'),
-			$text);
-		return $text;
-	}
-	function _stripFootnotes_callback($matches) {
-		$note_id = $this->fn_id_prefix . $matches[1];
-		$this->footnotes[$note_id] = $this->outdent($matches[2]);
-		return ''; # String that will replace the block
-	}
-
-
-	function doFootnotes($text) {
-	#
-	# Replace footnote references in $text [^id] with a special text-token 
-	# which will be replaced by the actual footnote marker in appendFootnotes.
-	#
-		if (!$this->in_anchor) {
-			$text = preg_replace('{\[\^(.+?)\]}', "F\x1Afn:\\1\x1A:", $text);
-		}
-		return $text;
-	}
-
-	
-	function appendFootnotes($text) {
-	#
-	# Append footnote list to text.
-	#
-		$text = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}', 
-			array(&$this, '_appendFootnotes_callback'), $text);
-	
-		if (!empty($this->footnotes_ordered)) {
-			$text .= "\n\n";
-			$text .= "";
-		}
-		return $text;
-	}
-	function _appendFootnotes_callback($matches) {
-		$node_id = $this->fn_id_prefix . $matches[1];
-		
-		# Create footnote marker only if it has a corresponding footnote *and*
-		# the footnote hasn't been used by another marker.
-		if (isset($this->footnotes[$node_id])) {
-			# Transfert footnote content to the ordered list.
-			$this->footnotes_ordered[$node_id] = $this->footnotes[$node_id];
-			unset($this->footnotes[$node_id]);
-			
-			$num = $this->footnote_counter++;
-			$attr = " rel=\"footnote\"";
-			if ($this->fn_link_class != "") {
-				$class = $this->fn_link_class;
-				$class = $this->encodeAttribute($class);
-				$attr .= " class=\"$class\"";
-			}
-			if ($this->fn_link_title != "") {
-				$title = $this->fn_link_title;
-				$title = $this->encodeAttribute($title);
-				$attr .= " title=\"$title\"";
-			}
-			
-			$attr = str_replace("%%", $num, $attr);
-			$node_id = $this->encodeAttribute($node_id);
-			
-			return
-				"
".
-				"$num".
-				"";
-		}
-		
-		return "[^".$matches[1]."]";
-	}
-		
-	
-	### Abbreviations ###
-	
-	function stripAbbreviations($text) {
-	#
-	# Strips abbreviations from text, stores titles in hash references.
-	#
-		$less_than_tab = $this->tab_width - 1;
-
-		# Link defs are in the form: [id]*: url "optional title"
-		$text = preg_replace_callback('{
-			^[ ]{0,'.$less_than_tab.'}\*\[(.+?)\][ ]?:	# abbr_id = $1
-			(.*)					# text = $2 (no blank lines allowed)	
-			}xm',
-			array(&$this, '_stripAbbreviations_callback'),
-			$text);
-		return $text;
-	}
-	function _stripAbbreviations_callback($matches) {
-		$abbr_word = $matches[1];
-		$abbr_desc = $matches[2];
-		if ($this->abbr_word_re)
-			$this->abbr_word_re .= '|';
-		$this->abbr_word_re .= preg_quote($abbr_word);
-		$this->abbr_desciptions[$abbr_word] = trim($abbr_desc);
-		return ''; # String that will replace the block
-	}
-	
-	
-	function doAbbreviations($text) {
-	#
-	# Find defined abbreviations in text and wrap them in 
 elements.
-	#
-		if ($this->abbr_word_re) {
-			// cannot use the /x modifier because abbr_word_re may 
-			// contain significant spaces:
-			$text = preg_replace_callback('{'.
-				'(?abbr_word_re.')'.
-				'(?![\w\x1A])'.
-				'}', 
-				array(&$this, '_doAbbreviations_callback'), $text);
-		}
-		return $text;
-	}
-	function _doAbbreviations_callback($matches) {
-		$abbr = $matches[0];
-		if (isset($this->abbr_desciptions[$abbr])) {
-			$desc = $this->abbr_desciptions[$abbr];
-			if (empty($desc)) {
-				return $this->hashPart("$abbr");
-			} else {
-				$desc = $this->encodeAttribute($desc);
-				return $this->hashPart("$abbr");
-			}
-		} else {
-			return $matches[0];
-		}
-	}
-
-}
-
-
-/*
-
-PHP Markdown Extra
-==================
-
-Description
------------
-
-This is a PHP port of the original Markdown formatter written in Perl 
-by John Gruber. This special "Extra" version of PHP Markdown features 
-further enhancements to the syntax for making additional constructs 
-such as tables and definition list.
-
-Markdown is a text-to-HTML filter; it translates an easy-to-read /
-easy-to-write structured text format into HTML. Markdown's text format
-is most similar to that of plain text email, and supports features such
-as headers, *emphasis*, code blocks, blockquotes, and links.
-
-Markdown's syntax is designed not as a generic markup language, but
-specifically to serve as a front-end to (X)HTML. You can use span-level
-HTML tags anywhere in a Markdown document, and you can use block level
-HTML tags (like  and 
 as well).
-
-For more information about Markdown's syntax, see:
-
-
-
-
-Bugs
-----
-
-To file bug reports please send email to:
-
-
-
-Please include with your report: (1) the example input; (2) the output you
-expected; (3) the output Markdown actually produced.
-
-
-Version History
---------------- 
-
-See the readme file for detailed release notes for this version.
-
-
-Copyright and License
----------------------
-
-PHP Markdown & Extra
-Copyright (c) 2004-2008 Michel Fortin  
-  
-All rights reserved.
-
-Based on Markdown  
-Copyright (c) 2003-2006 John Gruber   
-   
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
-*	Redistributions of source code must retain the above copyright notice,
-	this list of conditions and the following disclaimer.
-
-*	Redistributions in binary form must reproduce the above copyright
-	notice, this list of conditions and the following disclaimer in the
-	documentation and/or other materials provided with the distribution.
-
-*	Neither the name "Markdown" nor the names of its contributors may
-	be used to endorse or promote products derived from this software
-	without specific prior written permission.
-
-This software is provided by the copyright holders and contributors "as
-is" and any express or implied warranties, including, but not limited
-to, the implied warranties of merchantability and fitness for a
-particular purpose are disclaimed. In no event shall the copyright owner
-or contributors be liable for any direct, indirect, incidental, special,
-exemplary, or consequential damages (including, but not limited to,
-procurement of substitute goods or services; loss of use, data, or
-profits; or business interruption) however caused and on any theory of
-liability, whether in contract, strict liability, or tort (including
-negligence or otherwise) arising in any way out of the use of this
-software, even if advised of the possibility of such damage.
-
-*/
-?>
\ No newline at end of file
-- 
cgit v1.2.3