summaryrefslogtreecommitdiff
path: root/system/libraries/drivers
diff options
context:
space:
mode:
authorBharat Mediratta <bharat@menalto.com>2009-05-27 15:11:53 -0700
committerBharat Mediratta <bharat@menalto.com>2009-05-27 15:11:53 -0700
commit12fe58d997d2066dc362fd393a18b4e5da190513 (patch)
tree3ad8e5afb77829e1541ec96d86785760d65c04ac /system/libraries/drivers
parent00f47d4ddddcd1902db817018dd79ac01bcc8e82 (diff)
Rename 'kohana' to 'system' to conform to the Kohana filesystem layout. I'm comfortable with us not clearly drawing the distinction about the fact that it's Kohana.
Diffstat (limited to 'system/libraries/drivers')
-rw-r--r--system/libraries/drivers/Cache.php40
-rw-r--r--system/libraries/drivers/Cache/Apc.php64
-rw-r--r--system/libraries/drivers/Cache/Eaccelerator.php66
-rw-r--r--system/libraries/drivers/Cache/File.php261
-rw-r--r--system/libraries/drivers/Cache/Memcache.php191
-rw-r--r--system/libraries/drivers/Cache/Sqlite.php257
-rw-r--r--system/libraries/drivers/Cache/Xcache.php119
-rw-r--r--system/libraries/drivers/Captcha.php227
-rw-r--r--system/libraries/drivers/Captcha/Alpha.php92
-rw-r--r--system/libraries/drivers/Captcha/Basic.php81
-rw-r--r--system/libraries/drivers/Captcha/Black.php72
-rw-r--r--system/libraries/drivers/Captcha/Math.php61
-rw-r--r--system/libraries/drivers/Captcha/Riddle.php47
-rw-r--r--system/libraries/drivers/Captcha/Word.php37
-rw-r--r--system/libraries/drivers/Database.php636
-rw-r--r--system/libraries/drivers/Database/Mssql.php462
-rw-r--r--system/libraries/drivers/Database/Mysql.php496
-rw-r--r--system/libraries/drivers/Database/Mysqli.php358
-rw-r--r--system/libraries/drivers/Database/Pdosqlite.php486
-rw-r--r--system/libraries/drivers/Database/Pgsql.php538
-rw-r--r--system/libraries/drivers/Image.php156
-rw-r--r--system/libraries/drivers/Image/GD.php401
-rw-r--r--system/libraries/drivers/Image/GraphicsMagick.php221
-rw-r--r--system/libraries/drivers/Image/ImageMagick.php222
-rw-r--r--system/libraries/drivers/Session.php70
-rw-r--r--system/libraries/drivers/Session/Cache.php105
-rw-r--r--system/libraries/drivers/Session/Cookie.php80
-rw-r--r--system/libraries/drivers/Session/Database.php163
28 files changed, 6009 insertions, 0 deletions
diff --git a/system/libraries/drivers/Cache.php b/system/libraries/drivers/Cache.php
new file mode 100644
index 00000000..7c5e3c31
--- /dev/null
+++ b/system/libraries/drivers/Cache.php
@@ -0,0 +1,40 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Cache driver interface.
+ *
+ * $Id: Cache.php 4046 2009-03-05 19:23:29Z Shadowhand $
+ *
+ * @package Cache
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+interface Cache_Driver {
+
+ /**
+ * Set a cache item.
+ */
+ public function set($id, $data, array $tags = NULL, $lifetime);
+
+ /**
+ * Find all of the cache ids for a given tag.
+ */
+ public function find($tag);
+
+ /**
+ * Get a cache item.
+ * Return NULL if the cache item is not found.
+ */
+ public function get($id);
+
+ /**
+ * Delete cache items by id or tag.
+ */
+ public function delete($id, $tag = FALSE);
+
+ /**
+ * Deletes all expired cache items.
+ */
+ public function delete_expired();
+
+} // End Cache Driver \ No newline at end of file
diff --git a/system/libraries/drivers/Cache/Apc.php b/system/libraries/drivers/Cache/Apc.php
new file mode 100644
index 00000000..f7be048f
--- /dev/null
+++ b/system/libraries/drivers/Cache/Apc.php
@@ -0,0 +1,64 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * APC-based Cache driver.
+ *
+ * $Id: Apc.php 4046 2009-03-05 19:23:29Z Shadowhand $
+ *
+ * @package Cache
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class Cache_Apc_Driver implements Cache_Driver {
+
+ public function __construct()
+ {
+ if ( ! extension_loaded('apc'))
+ throw new Kohana_Exception('cache.extension_not_loaded', 'apc');
+ }
+
+ public function get($id)
+ {
+ return (($return = apc_fetch($id)) === FALSE) ? NULL : $return;
+ }
+
+ public function set($id, $data, array $tags = NULL, $lifetime)
+ {
+ if ( ! empty($tags))
+ {
+ Kohana::log('error', 'Cache: tags are unsupported by the APC driver');
+ }
+
+ return apc_store($id, $data, $lifetime);
+ }
+
+ public function find($tag)
+ {
+ Kohana::log('error', 'Cache: tags are unsupported by the APC driver');
+
+ return array();
+ }
+
+ public function delete($id, $tag = FALSE)
+ {
+ if ($tag === TRUE)
+ {
+ Kohana::log('error', 'Cache: tags are unsupported by the APC driver');
+ return FALSE;
+ }
+ elseif ($id === TRUE)
+ {
+ return apc_clear_cache('user');
+ }
+ else
+ {
+ return apc_delete($id);
+ }
+ }
+
+ public function delete_expired()
+ {
+ return TRUE;
+ }
+
+} // End Cache APC Driver \ No newline at end of file
diff --git a/system/libraries/drivers/Cache/Eaccelerator.php b/system/libraries/drivers/Cache/Eaccelerator.php
new file mode 100644
index 00000000..a45616d5
--- /dev/null
+++ b/system/libraries/drivers/Cache/Eaccelerator.php
@@ -0,0 +1,66 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Eaccelerator-based Cache driver.
+ *
+ * $Id: Eaccelerator.php 4046 2009-03-05 19:23:29Z Shadowhand $
+ *
+ * @package Cache
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class Cache_Eaccelerator_Driver implements Cache_Driver {
+
+ public function __construct()
+ {
+ if ( ! extension_loaded('eaccelerator'))
+ throw new Kohana_Exception('cache.extension_not_loaded', 'eaccelerator');
+ }
+
+ public function get($id)
+ {
+ return eaccelerator_get($id);
+ }
+
+ public function find($tag)
+ {
+ Kohana::log('error', 'tags are unsupported by the eAccelerator driver');
+
+ return array();
+ }
+
+ public function set($id, $data, array $tags = NULL, $lifetime)
+ {
+ if ( ! empty($tags))
+ {
+ Kohana::log('error', 'tags are unsupported by the eAccelerator driver');
+ }
+
+ return eaccelerator_put($id, $data, $lifetime);
+ }
+
+ public function delete($id, $tag = FALSE)
+ {
+ if ($tag === TRUE)
+ {
+ Kohana::log('error', 'tags are unsupported by the eAccelerator driver');
+ return FALSE;
+ }
+ elseif ($id === TRUE)
+ {
+ return eaccelerator_clean();
+ }
+ else
+ {
+ return eaccelerator_rm($id);
+ }
+ }
+
+ public function delete_expired()
+ {
+ eaccelerator_gc();
+
+ return TRUE;
+ }
+
+} // End Cache eAccelerator Driver \ No newline at end of file
diff --git a/system/libraries/drivers/Cache/File.php b/system/libraries/drivers/Cache/File.php
new file mode 100644
index 00000000..cc9d48d3
--- /dev/null
+++ b/system/libraries/drivers/Cache/File.php
@@ -0,0 +1,261 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * File-based Cache driver.
+ *
+ * $Id: File.php 4046 2009-03-05 19:23:29Z Shadowhand $
+ *
+ * @package Cache
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class Cache_File_Driver implements Cache_Driver {
+
+ protected $directory = '';
+
+ /**
+ * Tests that the storage location is a directory and is writable.
+ */
+ public function __construct($directory)
+ {
+ // Find the real path to the directory
+ $directory = str_replace('\\', '/', realpath($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;
+ }
+
+ /**
+ * Finds an array of files matching the given id or tag.
+ *
+ * @param string cache id or tag
+ * @param bool search for tags
+ * @return array of filenames matching the id or tag
+ */
+ public function exists($id, $tag = FALSE)
+ {
+ if ($id === TRUE)
+ {
+ // Find all the files
+ return glob($this->directory.'*~*~*');
+ }
+ elseif ($tag === TRUE)
+ {
+ // Find all the files that have the tag name
+ $paths = glob($this->directory.'*~*'.$id.'*~*');
+
+ // Find all tags matching the given tag
+ $files = array();
+ foreach ($paths as $path)
+ {
+ // Split the files
+ $tags = explode('~', basename($path));
+
+ // Find valid tags
+ if (count($tags) !== 3 OR empty($tags[1]))
+ continue;
+
+ // Split the tags by plus signs, used to separate tags
+ $tags = explode('+', $tags[1]);
+
+ if (in_array($tag, $tags))
+ {
+ // Add the file to the array, it has the requested tag
+ $files[] = $path;
+ }
+ }
+
+ return $files;
+ }
+ else
+ {
+ // Find the file matching the given id
+ return glob($this->directory.$id.'~*');
+ }
+ }
+
+ /**
+ * 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)
+ {
+ // Remove old cache files
+ $this->delete($id);
+
+ // Cache File driver expects unix timestamp
+ if ($lifetime !== 0)
+ {
+ $lifetime += time();
+ }
+
+ if ( ! empty($tags))
+ {
+ // Convert the tags into a string list
+ $tags = implode('+', $tags);
+ }
+
+ // Write out a serialized cache
+ return (bool) file_put_contents($this->directory.$id.'~'.$tags.'~'.$lifetime, serialize($data));
+ }
+
+ /**
+ * 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))
+ {
+ // Length of directory name
+ $offset = strlen($this->directory);
+
+ // 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);
+
+ if (($data = $this->get($id)) !== FALSE)
+ {
+ // Add the result to the array
+ $result[$id] = $data;
+ }
+ }
+ }
+
+ 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)
+ {
+ if ($file = $this->exists($id))
+ {
+ // Use the first file
+ $file = current($file);
+
+ // Validate that the cache has not expired
+ if ($this->expired($file))
+ {
+ // Remove this cache, it has expired
+ $this->delete($id);
+ }
+ else
+ {
+ // Turn off errors while reading the file
+ $ER = error_reporting(0);
+
+ if (($data = file_get_contents($file)) !== FALSE)
+ {
+ // Unserialize the data
+ $data = unserialize($data);
+ }
+ else
+ {
+ // Delete the data
+ unset($data);
+ }
+
+ // Turn errors back on
+ error_reporting($ER);
+ }
+ }
+
+ // Return NULL if there is no data
+ return isset($data) ? $data : NULL;
+ }
+
+ /**
+ * 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
+ */
+ public function delete($id, $tag = FALSE)
+ {
+ $files = $this->exists($id, $tag);
+
+ if (empty($files))
+ return FALSE;
+
+ // Disable all error reporting while deleting
+ $ER = error_reporting(0);
+
+ foreach ($files as $file)
+ {
+ // Remove the cache file
+ if ( ! unlink($file))
+ Kohana::log('error', 'Cache: Unable to delete cache file: '.$file);
+ }
+
+ // Turn on error reporting again
+ error_reporting($ER);
+
+ return TRUE;
+ }
+
+ /**
+ * Deletes all cache files that are older than the current time.
+ *
+ * @return void
+ */
+ public function delete_expired()
+ {
+ 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);
+ }
+ }
+
+ // Turn on error reporting again
+ error_reporting($ER);
+ }
+ }
+
+ /**
+ * Check if a cache file has expired by filename.
+ *
+ * @param string filename
+ * @return bool
+ */
+ protected function expired($file)
+ {
+ // Get the expiration time
+ $expires = (int) substr($file, strrpos($file, '~') + 1);
+
+ // Expirations of 0 are "never expire"
+ return ($expires !== 0 AND $expires <= time());
+ }
+
+} // End Cache File Driver \ No newline at end of file
diff --git a/system/libraries/drivers/Cache/Memcache.php b/system/libraries/drivers/Cache/Memcache.php
new file mode 100644
index 00000000..d801de9c
--- /dev/null
+++ b/system/libraries/drivers/Cache/Memcache.php
@@ -0,0 +1,191 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Memcache-based Cache driver.
+ *
+ * $Id: Memcache.php 4102 2009-03-19 12:55:54Z Shadowhand $
+ *
+ * @package Cache
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class Cache_Memcache_Driver implements Cache_Driver {
+
+ const TAGS_KEY = 'memcache_tags_array';
+
+ // Cache backend object and flags
+ protected $backend;
+ protected $flags;
+
+ // Tags array
+ protected static $tags;
+
+ // Have the tags been changed?
+ protected static $tags_changed = FALSE;
+
+ public function __construct()
+ {
+ if ( ! extension_loaded('memcache'))
+ throw new Kohana_Exception('cache.extension_not_loaded', 'memcache');
+
+ $this->backend = new Memcache;
+ $this->flags = Kohana::config('cache_memcache.compression') ? MEMCACHE_COMPRESSED : FALSE;
+
+ $servers = Kohana::config('cache_memcache.servers');
+
+ foreach ($servers as $server)
+ {
+ // Make sure all required keys are set
+ $server += array('host' => '127.0.0.1', 'port' => 11211, 'persistent' => FALSE);
+
+ // 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;
+ }
+ }
+
+ public function __destruct()
+ {
+ 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;
+ }
+ }
+
+ public function find($tag)
+ {
+ if (isset(self::$tags[$tag]) AND $results = $this->backend->get(self::$tags[$tag]))
+ {
+ // Return all the found caches
+ return $results;
+ }
+ else
+ {
+ // No matching tags
+ return array();
+ }
+ }
+
+ public function get($id)
+ {
+ return (($return = $this->backend->get($id)) === FALSE) ? NULL : $return;
+ }
+
+ public function set($id, $data, array $tags = NULL, $lifetime)
+ {
+ if ( ! empty($tags))
+ {
+ // Tags will be changed
+ self::$tags_changed = TRUE;
+
+ foreach ($tags as $tag)
+ {
+ // Add the id to each tag
+ self::$tags[$tag][$id] = $id;
+ }
+ }
+
+ if ($lifetime !== 0)
+ {
+ // Memcache driver expects unix timestamp
+ $lifetime += time();
+ }
+
+ // Set a new value
+ return $this->backend->set($id, $data, $this->flags, $lifetime);
+ }
+
+ public function delete($id, $tag = FALSE)
+ {
+ // Tags will be changed
+ self::$tags_changed = TRUE;
+
+ if ($id === TRUE)
+ {
+ 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;
+ }
+ 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);
+ }
+ }
+
+ public function delete_expired()
+ {
+ // Tags will be changed
+ self::$tags_changed = TRUE;
+
+ foreach (self::$tags as $tag => $_ids)
+ {
+ foreach ($_ids as $id)
+ {
+ 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]);
+ }
+ }
+
+ // Memcache handles garbage collection internally
+ return TRUE;
+ }
+
+} // End Cache Memcache Driver
diff --git a/system/libraries/drivers/Cache/Sqlite.php b/system/libraries/drivers/Cache/Sqlite.php
new file mode 100644
index 00000000..9458d851
--- /dev/null
+++ b/system/libraries/drivers/Cache/Sqlite.php
@@ -0,0 +1,257 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * SQLite-based Cache driver.
+ *
+ * $Id: Sqlite.php 4046 2009-03-05 19:23:29Z Shadowhand $
+ *
+ * @package Cache
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class Cache_Sqlite_Driver implements Cache_Driver {
+
+ // SQLite database instance
+ protected $db;
+
+ // Database error messages
+ protected $error;
+
+ /**
+ * Logs an SQLite error.
+ */
+ protected static function log_error($code)
+ {
+ // Log an error
+ Kohana::log('error', 'Cache: SQLite error: '.sqlite_error_string($error));
+ }
+
+ /**
+ * Tests that the storage location is a directory and is writable.
+ */
+ public function __construct($filename)
+ {
+ // Get the directory name
+ $directory = str_replace('\\', '/', realpath(pathinfo($filename, PATHINFO_DIRNAME))).'/';
+
+ // Set the filename from the real directory path
+ $filename = $directory.basename($filename);
+
+ // Make sure the cache directory is writable
+ if ( ! is_dir($directory) OR ! is_writable($directory))
+ throw new Kohana_Exception('cache.unwritable', $directory);
+
+ // Make sure the cache database is writable
+ if (is_file($filename) AND ! is_writable($filename))
+ throw new Kohana_Exception('cache.unwritable', $filename);
+
+ // Open up an instance of the database
+ $this->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
new file mode 100644
index 00000000..6254bbb6
--- /dev/null
+++ b/system/libraries/drivers/Cache/Xcache.php
@@ -0,0 +1,119 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Xcache Cache driver.
+ *
+ * $Id: Xcache.php 4046 2009-03-05 19:23:29Z Shadowhand $
+ *
+ * @package Cache
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class Cache_Xcache_Driver implements Cache_Driver {
+
+ public function __construct()
+ {
+ if ( ! extension_loaded('xcache'))
+ throw new Kohana_Exception('cache.extension_not_loaded', 'xcache');
+ }
+
+ public function get($id)
+ {
+ if (xcache_isset($id))
+ return xcache_get($id);
+
+ return NULL;
+ }
+
+ public function set($id, $data, array $tags = NULL, $lifetime)
+ {
+ if ( ! empty($tags))
+ {
+ Kohana::log('error', 'Cache: tags are unsupported by the Xcache driver');
+ }
+
+ return xcache_set($id, $data, $lifetime);
+ }
+
+ public function find($tag)
+ {
+ Kohana::log('error', 'Cache: tags are unsupported by the Xcache driver');
+ return FALSE;
+ }
+
+ public function delete($id, $tag = FALSE)
+ {
+ if ($tag !== FALSE)
+ {
+ Kohana::log('error', 'Cache: tags are unsupported by the Xcache driver');
+ return TRUE;
+ }
+ elseif ($id !== TRUE)
+ {
+ if (xcache_isset($id))
+ return xcache_unset($id);
+
+ return FALSE;
+ }
+ else
+ {
+ // Do the login
+ $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;
+ }
+
+ return TRUE;
+ }
+
+ public function delete_expired()
+ {
+ return TRUE;
+ }
+
+ private function auth($reverse = FALSE)
+ {
+ static $backup = array();
+
+ $keys = array('PHP_AUTH_USER', 'PHP_AUTH_PW');
+
+ foreach ($keys as $key)
+ {
+ if ($reverse)
+ {
+ if (isset($backup[$key]))
+ {
+ $_SERVER[$key] = $backup[$key];
+ unset($backup[$key]);
+ }
+ else
+ {
+ unset($_SERVER[$key]);
+ }
+ }
+ else
+ {
+ $value = getenv($key);
+
+ if ( ! empty($value))
+ {
+ $backup[$key] = $value;
+ }
+
+ $_SERVER[$key] = Kohana::config('cache_xcache.'.$key);
+ }
+ }
+ }
+
+} // End Cache Xcache Driver
diff --git a/system/libraries/drivers/Captcha.php b/system/libraries/drivers/Captcha.php
new file mode 100644
index 00000000..a4343e19
--- /dev/null
+++ b/system/libraries/drivers/Captcha.php
@@ -0,0 +1,227 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Captcha driver class.
+ *
+ * $Id: Captcha.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package Captcha
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+abstract class Captcha_Driver {
+
+ // The correct Captcha challenge answer
+ protected $response;
+
+ // Image resource identifier and type ("png", "gif" or "jpeg")
+ protected $image;
+ protected $image_type = 'png';
+
+ /**
+ * Constructs a new challenge.
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ // Generate a new challenge
+ $this->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 '<img alt="Captcha" src="'.url::site('captcha/'.Captcha::$config['group']).'" width="'.Captcha::$config['width'].'" height="'.Captcha::$config['height'].'" />';
+
+ // 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
new file mode 100644
index 00000000..b3a9c9d7
--- /dev/null
+++ b/system/libraries/drivers/Captcha/Alpha.php
@@ -0,0 +1,92 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Captcha driver for "alpha" style.
+ *
+ * $Id: Alpha.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package Captcha
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class Captcha_Alpha_Driver extends Captcha_Driver {
+
+ /**
+ * Generates a new Captcha challenge.
+ *
+ * @return string the challenge answer
+ */
+ public function generate_challenge()
+ {
+ // Complexity setting is used as character count
+ return text::random('distinct', max(1, Captcha::$config['complexity']));
+ }
+
+ /**
+ * Outputs the Captcha image.
+ *
+ * @param boolean html output
+ * @return mixed
+ */
+ public function render($html)
+ {
+ // Creates $this->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 = substr($chars, mt_rand(0, 14), 1);
+ 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
new file mode 100644
index 00000000..d212e72c
--- /dev/null
+++ b/system/libraries/drivers/Captcha/Basic.php
@@ -0,0 +1,81 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Captcha driver for "basic" style.
+ *
+ * $Id: Basic.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package Captcha
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class Captcha_Basic_Driver extends Captcha_Driver {
+
+ /**
+ * Generates a new Captcha challenge.
+ *
+ * @return string the challenge answer
+ */
+ public function generate_challenge()
+ {
+ // Complexity setting is used as character count
+ return text::random('distinct', max(1, Captcha::$config['complexity']));
+ }
+
+ /**
+ * Outputs the Captcha image.
+ *
+ * @param boolean html output
+ * @return mixed
+ */
+ public function render($html)
+ {
+ // Creates $this->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
new file mode 100644
index 00000000..6a2e2226
--- /dev/null
+++ b/system/libraries/drivers/Captcha/Black.php
@@ -0,0 +1,72 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Captcha driver for "black" style.
+ *
+ * $Id: Black.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package Captcha
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class Captcha_Black_Driver extends Captcha_Driver {
+
+ /**
+ * Generates a new Captcha challenge.
+ *
+ * @return string the challenge answer
+ */
+ public function generate_challenge()
+ {
+ // Complexity setting is used as character count
+ return text::random('distinct', max(1, ceil(Captcha::$config['complexity'] / 1.5)));
+ }
+
+ /**
+ * Outputs the Captcha image.
+ *
+ * @param boolean html output
+ * @return mixed
+ */
+ public function render($html)
+ {
+ // Creates a black image to start from
+ $this->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
new file mode 100644
index 00000000..4ac20248
--- /dev/null
+++ b/system/libraries/drivers/Captcha/Math.php
@@ -0,0 +1,61 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Captcha driver for "math" style.
+ *
+ * $Id: Math.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package Captcha
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class Captcha_Math_Driver extends Captcha_Driver {
+
+ private $math_exercice;
+
+ /**
+ * Generates a new Captcha challenge.
+ *
+ * @return string the challenge answer
+ */
+ public function generate_challenge()
+ {
+ // Easy
+ if (Captcha::$config['complexity'] < 4)
+ {
+ $numbers[] = mt_rand(1, 5);
+ $numbers[] = mt_rand(1, 4);
+ }
+ // Normal
+ elseif (Captcha::$config['complexity'] < 7)
+ {
+ $numbers[] = mt_rand(10, 20);
+ $numbers[] = mt_rand(1, 10);
+ }
+ // Difficult, well, not really ;)
+ else
+ {
+ $numbers[] = mt_rand(100, 200);
+ $numbers[] = mt_rand(10, 20);
+ $numbers[] = mt_rand(1, 10);
+ }
+
+ // Store the question for output
+ $this->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
new file mode 100644
index 00000000..765eeaad
--- /dev/null
+++ b/system/libraries/drivers/Captcha/Riddle.php
@@ -0,0 +1,47 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Captcha driver for "riddle" style.
+ *
+ * $Id: Riddle.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package Captcha
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class Captcha_Riddle_Driver extends Captcha_Driver {
+
+ private $riddle;
+
+ /**
+ * Generates a new Captcha challenge.
+ *
+ * @return string the challenge answer
+ */
+ public function generate_challenge()
+ {
+ // Load riddles from the current language
+ $riddles = Kohana::lang('captcha.riddles');
+
+ // Pick a random riddle
+ $riddle = $riddles[array_rand($riddles)];
+
+ // Store the question for output
+ $this->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
new file mode 100644
index 00000000..856bd9b4
--- /dev/null
+++ b/system/libraries/drivers/Captcha/Word.php
@@ -0,0 +1,37 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Captcha driver for "word" style.
+ *
+ * $Id: Word.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package Captcha
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class Captcha_Word_Driver extends Captcha_Basic_Driver {
+
+ /**
+ * Generates a new Captcha challenge.
+ *
+ * @return string the challenge answer
+ */
+ public function generate_challenge()
+ {
+ // Load words from the current language and randomize them
+ $words = Kohana::lang('captcha.words');
+ shuffle($words);
+
+ // Loop over each word...
+ foreach ($words as $word)
+ {
+ // ...until we find one of the desired length
+ if (abs(Captcha::$config['complexity'] - strlen($word)) < 2)
+ return strtoupper($word);
+ }
+
+ // Return any random word as final fallback
+ return strtoupper($words[array_rand($words)]);
+ }
+
+} // End Captcha Word Driver Class \ No newline at end of file
diff --git a/system/libraries/drivers/Database.php b/system/libraries/drivers/Database.php
new file mode 100644
index 00000000..807469f6
--- /dev/null
+++ b/system/libraries/drivers/Database.php
@@ -0,0 +1,636 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Database API driver
+ *
+ * $Id: Database.php 4343 2009-05-08 17:04:48Z jheathco $
+ *
+ * @package Core
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+abstract class Database_Driver {
+
+ protected $query_cache;
+
+ /**
+ * Connect to our database.
+ * Returns FALSE on failure or a MySQL resource.
+ *
+ * @return mixed
+ */
+ abstract public function connect();
+
+ /**
+ * Perform a query based on a manually written query.
+ *
+ * @param string SQL query to execute
+ * @return Database_Result
+ */
+ abstract public function query($sql);
+
+ /**
+ * Builds a DELETE query.
+ *
+ * @param string table name
+ * @param array where clause
+ * @return string
+ */
+ public function delete($table, $where)
+ {
+ return 'DELETE FROM '.$this->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 <charset>'.
+ *
+ * @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) ? ' 1' : ' 0';
+ }
+ 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 = (int) $value;
+ 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
new file mode 100644
index 00000000..6947679a
--- /dev/null
+++ b/system/libraries/drivers/Database/Mssql.php
@@ -0,0 +1,462 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * MSSQL Database Driver
+ *
+ * @package Core
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class Database_Mssql_Driver extends Database_Driver
+{
+ /**
+ * Database connection link
+ */
+ protected $link;
+
+ /**
+ * Database configuration
+ */
+ protected $db_config;
+
+ /**
+ * Sets the config for the class.
+ *
+ * @param array database configuration
+ */
+ public function __construct($config)
+ {
+ $this->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('SHOW COLUMNS FROM '.$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
new file mode 100644
index 00000000..d5222f50
--- /dev/null
+++ b/system/libraries/drivers/Database/Mysql.php
@@ -0,0 +1,496 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * MySQL Database Driver
+ *
+ * $Id: Mysql.php 4344 2009-05-11 16:41:39Z zombor $
+ *
+ * @package Core
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class Database_Mysql_Driver extends Database_Driver {
+
+ /**
+ * Database connection link
+ */
+ protected $link;
+
+ /**
+ * Database configuration
+ */
+ protected $db_config;
+
+ /**
+ * Sets the config for the class.
+ *
+ * @param array database configuration
+ */
+ public function __construct($config)
+ {
+ $this->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
new file mode 100644
index 00000000..0dd9f05c
--- /dev/null
+++ b/system/libraries/drivers/Database/Mysqli.php
@@ -0,0 +1,358 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * MySQLi Database Driver
+ *
+ * $Id: Mysqli.php 4344 2009-05-11 16:41:39Z zombor $
+ *
+ * @package Core
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class Database_Mysqli_Driver extends Database_Mysql_Driver {
+
+ // Database connection link
+ protected $link;
+ protected $db_config;
+ protected $statements = array();
+
+ /**
+ * Sets the config for the class.
+ *
+ * @param array database configuration
+ */
+ public function __construct($config)
+ {
+ $this->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
new file mode 100644
index 00000000..c2d1bb21
--- /dev/null
+++ b/system/libraries/drivers/Database/Pdosqlite.php
@@ -0,0 +1,486 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/*
+ * Class: Database_PdoSqlite_Driver
+ * Provides specific database items for Sqlite.
+ *
+ * Connection string should be, eg: "pdosqlite://path/to/database.db"
+ *
+ * Version 1.0 alpha
+ * author - Doutu, updated by gregmac
+ * copyright - (c) BSD
+ * license - <no>
+ */
+
+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
new file mode 100644
index 00000000..c53c8439
--- /dev/null
+++ b/system/libraries/drivers/Database/Pgsql.php
@@ -0,0 +1,538 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * PostgreSQL 8.1+ Database Driver
+ *
+ * $Id: Pgsql.php 4344 2009-05-11 16:41:39Z zombor $
+ *
+ * @package Core
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class Database_Pgsql_Driver extends Database_Driver {
+
+ // Database connection link
+ protected $link;
+ protected $db_config;
+
+ /**
+ * Sets the config for the class.
+ *
+ * @param array database configuration
+ */
+ public function __construct($config)
+ {
+ $this->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
new file mode 100644
index 00000000..f89ba953
--- /dev/null
+++ b/system/libraries/drivers/Image.php
@@ -0,0 +1,156 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Image API driver.
+ *
+ * $Id: Image.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package Image
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+abstract class Image_Driver {
+
+ // Reference to the current image
+ protected $image;
+
+ // Reference to the temporary processing image
+ protected $tmp_image;
+
+ // Processing errors
+ protected $errors = array();
+
+ /**
+ * Executes a set of actions, defined in pairs.
+ *
+ * @param array actions
+ * @return boolean
+ */
+ public function execute($actions)
+ {
+ foreach ($actions as $func => $args)
+ {
+ if ( ! $this->$func($args))
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ /**
+ * Sanitize and normalize a geometry array based on the temporary image
+ * width and height. Valid properties are: width, height, top, left.
+ *
+ * @param array geometry properties
+ * @return void
+ */
+ protected function sanitize_geometry( & $geometry)
+ {
+ list($width, $height) = $this->properties();
+
+ // Turn off error reporting
+ $reporting = error_reporting(0);
+
+ // Width and height cannot exceed current image size
+ $geometry['width'] = min($geometry['width'], $width);
+ $geometry['height'] = min($geometry['height'], $height);
+
+ // Set standard coordinates if given, otherwise use pixel values
+ if ($geometry['top'] === 'center')
+ {
+ $geometry['top'] = floor(($height / 2) - ($geometry['height'] / 2));
+ }
+ elseif ($geometry['top'] === 'top')
+ {
+ $geometry['top'] = 0;
+ }
+ elseif ($geometry['top'] === 'bottom')
+ {
+ $geometry['top'] = $height - $geometry['height'];
+ }
+
+ // Set standard coordinates if given, otherwise use pixel values
+ if ($geometry['left'] === 'center')
+ {
+ $geometry['left'] = floor(($width / 2) - ($geometry['width'] / 2));
+ }
+ elseif ($geometry['left'] === 'left')
+ {
+ $geometry['left'] = 0;
+ }
+ elseif ($geometry['left'] === 'right')
+ {
+ $geometry['left'] = $width - $geometry['height'];
+ }
+
+ // Restore error reporting
+ error_reporting($reporting);
+ }
+
+ /**
+ * Return the current width and height of the temporary image. This is mainly
+ * needed for sanitizing the geometry.
+ *
+ * @return array width, height
+ */
+ abstract protected function properties();
+
+ /**
+ * Process an image with a set of actions.
+ *
+ * @param string image filename
+ * @param array actions to execute
+ * @param string destination directory path
+ * @param string destination filename
+ * @return boolean
+ */
+ abstract public function process($image, $actions, $dir, $file);
+
+ /**
+ * Flip an image. Valid directions are horizontal and vertical.
+ *
+ * @param integer direction to flip
+ * @return boolean
+ */
+ abstract function flip($direction);
+
+ /**
+ * Crop an image. Valid properties are: width, height, top, left.
+ *
+ * @param array new properties
+ * @return boolean
+ */
+ abstract function crop($properties);
+
+ /**
+ * Resize an image. Valid properties are: width, height, and master.
+ *
+ * @param array new properties
+ * @return boolean
+ */
+ abstract public function resize($properties);
+
+ /**
+ * Rotate an image. Valid amounts are -180 to 180.
+ *
+ * @param integer amount to rotate
+ * @return boolean
+ */
+ abstract public function rotate($amount);
+
+ /**
+ * Sharpen and image. Valid amounts are 1 to 100.
+ *
+ * @param integer amount to sharpen
+ * @return boolean
+ */
+ abstract public function sharpen($amount);
+
+ /**
+ * Overlay a second image. Valid properties are: overlay_file, mime, x, y and transparency.
+ *
+ * @return boolean
+ */
+ abstract public function composite($properties);
+
+} // End Image Driver \ No newline at end of file
diff --git a/system/libraries/drivers/Image/GD.php b/system/libraries/drivers/Image/GD.php
new file mode 100644
index 00000000..be2af4e2
--- /dev/null
+++ b/system/libraries/drivers/Image/GD.php
@@ -0,0 +1,401 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * GD Image Driver.
+ *
+ * $Id: GD.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package Image
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class Image_GD_Driver extends Image_Driver {
+
+ // A transparent PNG as a string
+ protected static $blank_png;
+ protected static $blank_png_width;
+ protected static $blank_png_height;
+
+ public function __construct()
+ {
+ // Make sure that GD2 is available
+ if ( ! function_exists('gd_info'))
+ throw new Kohana_Exception('image.gd.requires_v2');
+
+ // 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');
+ }
+
+ public function process($image, $actions, $dir, $file, $render = FALSE)
+ {
+ // Set the "create" function
+ switch ($image['type'])
+ {
+ case IMAGETYPE_JPEG:
+ $create = 'imagecreatefromjpeg';
+ break;
+ case IMAGETYPE_GIF:
+ $create = 'imagecreatefromgif';
+ break;
+ case IMAGETYPE_PNG:
+ $create = 'imagecreatefrompng';
+ break;
+ }
+
+ // Set the "save" function
+ switch (strtolower(substr(strrchr($file, '.'), 1)))
+ {
+ case 'jpg':
+ case 'jpeg':
+ $save = 'imagejpeg';
+ break;
+ case 'gif':
+ $save = 'imagegif';
+ break;
+ case 'png':
+ $save = 'imagepng';
+ break;
+ }
+
+ // 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']);
+
+ // 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);
+
+ // Load the image
+ $this->image = $image;
+
+ // Create the GD image resource
+ $this->tmp_image = $create($image['file']);
+
+ // Get the quality setting from the actions
+ $quality = arr::remove('quality', $actions);
+
+ if ($status = $this->execute($actions))
+ {
+ // Prevent the alpha from being lost
+ imagealphablending($this->tmp_image, TRUE);
+ imagesavealpha($this->tmp_image, TRUE);
+
+ switch ($save)
+ {
+ case 'imagejpeg':
+ // Default the quality to 95
+ ($quality === NULL) and $quality = 95;
+ break;
+ case 'imagegif':
+ // Remove the quality setting, GIF doesn't use it
+ unset($quality);
+ break;
+ case 'imagepng':
+ // Always use a compression level of 9 for PNGs. This does not
+ // affect quality, it only increases the level of compression!
+ $quality = 9;
+ break;
+ }
+
+ if ($render === FALSE)
+ {
+ // Set the status to the save return value, saving with the quality requested
+ $status = isset($quality) ? $save($this->tmp_image, $dir.$file, $quality) : $save($this->tmp_image, $dir.$file);
+ }
+ else
+ {
+ // Output the image directly to the browser
+ switch ($save)
+ {
+ case 'imagejpeg':
+ header('Content-Type: image/jpeg');
+ break;
+ case 'imagegif':
+ header('Content-Type: image/gif');
+ break;
+ case 'imagepng':
+ header('Content-Type: image/png');
+ break;
+ }
+
+ $status = isset($quality) ? $save($this->tmp_image, NULL, $quality) : $save($this->tmp_image);
+ }
+
+ // Destroy the temporary image
+ imagedestroy($this->tmp_image);
+ }
+
+ return $status;
+ }
+
+ public function flip($direction)
+ {
+ // Get the current width and height
+ $width = imagesx($this->tmp_image);
+ $height = imagesy($this->tmp_image);
+
+ // Create the flipped image
+ $flipped = $this->imagecreatetransparent($width, $height);
+
+ if ($direction === Image::HORIZONTAL)
+ {
+ for ($x = 0; $x < $width; $x++)
+ {
+ $status = imagecopy($flipped, $this->tmp_image, $x, 0, $width - $x - 1, 0, 1, $height);
+ }
+ }
+ elseif ($direction === Image::VERTICAL)
+ {
+ for ($y = 0; $y < $height; $y++)
+ {
+ $status = imagecopy($flipped, $this->tmp_image, 0, $y, 0, $height - $y - 1, $width, 1);
+ }
+ }
+ else
+ {
+ // Do nothing
+ return TRUE;
+ }
+
+ if ($status === TRUE)
+ {
+ // Swap the new image for the old one
+ imagedestroy($this->tmp_image);
+ $this->tmp_image = $flipped;
+ }
+
+ return $status;
+ }
+
+ public function crop($properties)
+ {
+ // Sanitize the cropping settings
+ $this->sanitize_geometry($properties);
+
+ // Get the current width and height
+ $width = imagesx($this->tmp_image);
+ $height = imagesy($this->tmp_image);
+
+ // Create the temporary image to copy to
+ $img = $this->imagecreatetransparent($properties['width'], $properties['height']);
+
+ // Execute the crop
+ if ($status = imagecopyresampled($img, $this->tmp_image, 0, 0, $properties['left'], $properties['top'], $width, $height, $width, $height))
+ {
+ // Swap the new image for the old one
+ imagedestroy($this->tmp_image);
+ $this->tmp_image = $img;
+ }
+
+ return $status;
+ }
+
+ public function resize($properties)
+ {
+ // Get the current width and height
+ $width = imagesx($this->tmp_image);
+ $height = imagesy($this->tmp_image);
+
+ if (substr($properties['width'], -1) === '%')
+ {
+ // Recalculate the percentage to a pixel size
+ $properties['width'] = round($width * (substr($properties['width'], 0, -1) / 100));
+ }
+
+ if (substr($properties['height'], -1) === '%')
+ {
+ // 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
+ $properties['master'] = (($width / $properties['width']) > ($height / $properties['height'])) ? Image::WIDTH : Image::HEIGHT;
+ }
+
+ if (empty($properties['height']) OR $properties['master'] === Image::WIDTH)
+ {
+ // Recalculate the height based on the width
+ $properties['height'] = round($height * $properties['width'] / $width);
+ }
+
+ if (empty($properties['width']) OR $properties['master'] === Image::HEIGHT)
+ {
+ // Recalculate the width based on the height
+ $properties['width'] = round($width * $properties['height'] / $height);
+ }
+
+ // Test if we can do a resize without resampling to speed up the final resize
+ if ($properties['width'] > $width / 2 AND $properties['height'] > $height / 2)
+ {
+ // Presize width and height
+ $pre_width = $width;
+ $pre_height = $height;
+
+ // The maximum reduction is 10% greater than the final size
+ $max_reduction_width = round($properties['width'] * 1.1);
+ $max_reduction_height = round($properties['height'] * 1.1);
+
+ // Reduce the size using an O(2n) algorithm, until it reaches the maximum reduction
+ while ($pre_width / 2 > $max_reduction_width AND $pre_height / 2 > $max_reduction_height)
+ {
+ $pre_width /= 2;
+ $pre_height /= 2;
+ }
+
+ // Create the temporary image to copy to
+ $img = $this->imagecreatetransparent($pre_width, $pre_height);
+
+ if ($status = imagecopyresized($img, $this->tmp_image, 0, 0, 0, 0, $pre_width, $pre_height, $width, $height))
+ {
+ // Swap the new image for the old one
+ imagedestroy($this->tmp_image);
+ $this->tmp_image = $img;
+ }
+
+ // Set the width and height to the presize
+ $width = $pre_width;
+ $height = $pre_height;
+ }
+
+ // Create the temporary image to copy to
+ $img = $this->imagecreatetransparent($properties['width'], $properties['height']);
+
+ // Execute the resize
+ if ($status = imagecopyresampled($img, $this->tmp_image, 0, 0, 0, 0, $properties['width'], $properties['height'], $width, $height))
+ {
+ // Swap the new image for the old one
+ imagedestroy($this->tmp_image);
+ $this->tmp_image = $img;
+ }
+
+ return $status;
+ }
+
+ public function rotate($amount)
+ {
+ // Use current image to rotate
+ $img = $this->tmp_image;
+
+ // White, with an alpha of 0
+ $transparent = imagecolorallocatealpha($img, 255, 255, 255, 127);
+
+ // Rotate, setting the transparent color
+ $img = imagerotate($img, 360 - $amount, $transparent, -1);
+
+ // Fill the background with the transparent "color"
+ imagecolortransparent($img, $transparent);
+
+ // Merge the images
+ if ($status = imagecopymerge($this->tmp_image, $img, 0, 0, 0, 0, imagesx($this->tmp_image), imagesy($this->tmp_image), 100))
+ {
+ // Prevent the alpha from being lost
+ imagealphablending($img, TRUE);
+ imagesavealpha($img, TRUE);
+
+ // Swap the new image for the old one
+ imagedestroy($this->tmp_image);
+ $this->tmp_image = $img;
+ }
+
+ return $status;
+ }
+
+ public function sharpen($amount)
+ {
+ // Make sure that the sharpening function is available
+ if ( ! function_exists('imageconvolution'))
+ throw new Kohana_Exception('image.unsupported_method', __FUNCTION__);
+
+ // Amount should be in the range of 18-10
+ $amount = round(abs(-18 + ($amount * 0.08)), 2);
+
+ // Gaussian blur matrix
+ $matrix = array
+ (
+ array(-1, -1, -1),
+ array(-1, $amount, -1),
+ array(-1, -1, -1),
+ );
+
+ // Perform the sharpen
+ return imageconvolution($this->tmp_image, $matrix, $amount - 8, 0);
+ }
+
+ public function composite($properties)
+ {
+ switch($properties['mime'])
+ {
+ case "image/jpeg":
+ $overlay_img = imagecreatefromjpeg($properties['overlay_file']);
+ break;
+
+ case "image/gif":
+ $overlay_img = imagecreatefromgif($properties['overlay_file']);
+ break;
+
+ case "image/png":
+ $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']);
+ imagedestroy($overlay_img);
+ return TRUE;
+ }
+
+ protected function properties()
+ {
+ return array(imagesx($this->tmp_image), imagesy($this->tmp_image));
+ }
+
+ /**
+ * Returns an image with a transparent background. Used for rotating to
+ * prevent unfilled backgrounds.
+ *
+ * @param integer image width
+ * @param integer image height
+ * @return resource
+ */
+ protected function imagecreatetransparent($width, $height)
+ {
+ if (self::$blank_png === NULL)
+ {
+ // Decode the blank PNG if it has not been done already
+ self::$blank_png = imagecreatefromstring(base64_decode
+ (
+ 'iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29'.
+ 'mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAADqSURBVHjaYvz//z/DYAYAAcTEMMgBQAANegcCBN'.
+ 'CgdyBAAA16BwIE0KB3IEAADXoHAgTQoHcgQAANegcCBNCgdyBAAA16BwIE0KB3IEAADXoHAgTQoHcgQ'.
+ 'AANegcCBNCgdyBAAA16BwIE0KB3IEAADXoHAgTQoHcgQAANegcCBNCgdyBAAA16BwIE0KB3IEAADXoH'.
+ 'AgTQoHcgQAANegcCBNCgdyBAAA16BwIE0KB3IEAADXoHAgTQoHcgQAANegcCBNCgdyBAAA16BwIE0KB'.
+ '3IEAADXoHAgTQoHcgQAANegcCBNCgdyBAgAEAMpcDTTQWJVEAAAAASUVORK5CYII='
+ ));
+
+ // Set the blank PNG width and height
+ self::$blank_png_width = imagesx(self::$blank_png);
+ self::$blank_png_height = imagesy(self::$blank_png);
+ }
+
+ $img = imagecreatetruecolor($width, $height);
+
+ // Resize the blank image
+ imagecopyresized($img, self::$blank_png, 0, 0, 0, 0, $width, $height, self::$blank_png_width, self::$blank_png_height);
+
+ // Prevent the alpha from being lost
+ imagealphablending($img, FALSE);
+ imagesavealpha($img, TRUE);
+
+ return $img;
+ }
+
+} // End Image GD Driver \ No newline at end of file
diff --git a/system/libraries/drivers/Image/GraphicsMagick.php b/system/libraries/drivers/Image/GraphicsMagick.php
new file mode 100644
index 00000000..a8bc4d9b
--- /dev/null
+++ b/system/libraries/drivers/Image/GraphicsMagick.php
@@ -0,0 +1,221 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * GraphicsMagick Image Driver.
+ *
+ * @package Image
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class Image_GraphicsMagick_Driver extends Image_Driver {
+
+ // Directory that GM is installed in
+ protected $dir = '';
+
+ // Command extension (exe for windows)
+ protected $ext = '';
+
+ // Temporary image filename
+ protected $tmp_image;
+
+ /**
+ * Attempts to detect the GraphicsMagick installation directory.
+ *
+ * @throws Kohana_Exception
+ * @param array configuration
+ * @return void
+ */
+ public function __construct($config)
+ {
+ if (empty($config['directory']))
+ {
+ // 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');
+
+ $config['directory'] = dirname($path);
+ }
+
+ // Set the command extension
+ $this->ext = (PHP_SHLIB_SUFFIX === 'dll') ? '.exe' : '';
+
+ // 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);
+
+
+ // Set the installation directory
+ $this->dir = str_replace('\\', '/', realpath($config['directory'])).'/';
+ }
+
+ /**
+ * 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)
+ {
+ // We only need the filename
+ $image = $image['file'];
+
+ // Unique temporary filename
+ $this->tmp_image = $dir.'k2img--'.sha1(time().$dir.$file).substr($file, strrpos($file, '.'));
+
+ // Copy the image to the temporary file
+ copy($image, $this->tmp_image);
+
+ // Quality change is done last
+ $quality = (int) arr::remove('quality', $actions);
+
+ // Use 95 for the default quality
+ empty($quality) and $quality = 95;
+
+ // 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);
+
+ 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.'gm'.$this->ext.' convert').' -quality '.$quality.'% '.$this->cmd_image.' '.$this->new_image))
+ {
+ $this->errors[] = $error;
+ }
+ else
+ {
+ // Output the image directly to the browser
+ if ($render !== FALSE)
+ {
+ $contents = file_get_contents($this->tmp_image);
+ switch (substr($file, strrpos($file, '.') + 1))
+ {
+ case 'jpg':
+ case 'jpeg':
+ header('Content-Type: image/jpeg');
+ break;
+ case 'gif':
+ header('Content-Type: image/gif');
+ break;
+ case 'png':
+ header('Content-Type: image/png');
+ break;
+ }
+ echo $contents;
+ }
+ }
+ }
+
+ // Remove the temporary image
+ unlink($this->tmp_image);
+ $this->tmp_image = '';
+
+ return $status;
+ }
+
+ public function crop($prop)
+ {
+ // Sanitize and normalize the properties into geometry
+ $this->sanitize_geometry($prop);
+
+ // 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.'gm'.$this->ext.' convert').' -crop '.$geometry.' '.$this->cmd_image.' '.$this->cmd_image))
+ {
+ $this->errors[] = $error;
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ public function flip($dir)
+ {
+ // Convert the direction into a GM command
+ $dir = ($dir === Image::HORIZONTAL) ? '-flop' : '-flip';
+
+ if ($error = exec(escapeshellcmd($this->dir.'gm'.$this->ext.' convert').' '.$dir.' '.$this->cmd_image.' '.$this->cmd_image))
+ {
+ $this->errors[] = $error;
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ public function resize($prop)
+ {
+ switch ($prop['master'])
+ {
+ case Image::WIDTH: // Wx
+ $dim = escapeshellarg($prop['width'].'x');
+ break;
+ case Image::HEIGHT: // xH
+ $dim = escapeshellarg('x'.$prop['height']);
+ break;
+ case Image::AUTO: // WxH
+ $dim = escapeshellarg($prop['width'].'x'.$prop['height']);
+ break;
+ case Image::NONE: // WxH!
+ $dim = escapeshellarg($prop['width'].'x'.$prop['height'].'!');
+ break;
+ }
+
+ // Use "convert" to change the width and height
+ if ($error = exec(escapeshellcmd($this->dir.'gm'.$this->ext.' convert').' -resize '.$dim.' '.$this->cmd_image.' '.$this->cmd_image))
+ {
+ $this->errors[] = $error;
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ public function rotate($amt)
+ {
+ if ($error = exec(escapeshellcmd($this->dir.'gm'.$this->ext.' convert').' -rotate '.escapeshellarg($amt).' -background transparent '.$this->cmd_image.' '.$this->cmd_image))
+ {
+ $this->errors[] = $error;
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ public function sharpen($amount)
+ {
+ // Set the sigma, radius, and amount. The amount formula allows a nice
+ // spread between 1 and 100 without pixelizing the image badly.
+ $sigma = 0.5;
+ $radius = $sigma * 2;
+ $amount = round(($amount / 80) * 3.14, 2);
+
+ // Convert the amount to an GM command
+ $sharpen = escapeshellarg($radius.'x'.$sigma.'+'.$amount.'+0');
+
+ if ($error = exec(escapeshellcmd($this->dir.'gm'.$this->ext.' convert').' -unsharp '.$sharpen.' '.$this->cmd_image.' '.$this->cmd_image))
+ {
+ $this->errors[] = $error;
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ public function composite($properties)
+ {
+ if ($error = exec(escapeshellcmd($this->dir.'gm'.$this->ext.' composite').' -geometry ' . escapeshellarg('+'.$properties['x'].'+'.$properties['y']).' -dissolve '.escapeshellarg($properties['transparency']).' '.escapeshellarg($properties['overlay_file']).' '.$this->cmd_image.' '.$this->cmd_image))
+ {
+ $this->errors[] = $error;
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ protected function properties()
+ {
+ return array_slice(getimagesize($this->tmp_image), 0, 2, FALSE);
+ }
+
+} // End Image GraphicsMagick Driver \ No newline at end of file
diff --git a/system/libraries/drivers/Image/ImageMagick.php b/system/libraries/drivers/Image/ImageMagick.php
new file mode 100644
index 00000000..4b381fd6
--- /dev/null
+++ b/system/libraries/drivers/Image/ImageMagick.php
@@ -0,0 +1,222 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * ImageMagick Image Driver.
+ *
+ * $Id: ImageMagick.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package Image
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class Image_ImageMagick_Driver extends Image_Driver {
+
+ // Directory that IM is installed in
+ protected $dir = '';
+
+ // Command extension (exe for windows)
+ protected $ext = '';
+
+ // Temporary image filename
+ protected $tmp_image;
+
+ /**
+ * Attempts to detect the ImageMagick installation directory.
+ *
+ * @throws Kohana_Exception
+ * @param array configuration
+ * @return void
+ */
+ public function __construct($config)
+ {
+ if (empty($config['directory']))
+ {
+ // 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');
+
+ $config['directory'] = dirname($path);
+ }
+
+ // Set the command extension
+ $this->ext = (PHP_SHLIB_SUFFIX === 'dll') ? '.exe' : '';
+
+ // 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);
+
+ // Set the installation directory
+ $this->dir = str_replace('\\', '/', realpath($config['directory'])).'/';
+ }
+
+ /**
+ * 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)
+ {
+ // We only need the filename
+ $image = $image['file'];
+
+ // Unique temporary filename
+ $this->tmp_image = $dir.'k2img--'.sha1(time().$dir.$file).substr($file, strrpos($file, '.'));
+
+ // Copy the image to the temporary file
+ copy($image, $this->tmp_image);
+
+ // Quality change is done last
+ $quality = (int) arr::remove('quality', $actions);
+
+ // Use 95 for the default quality
+ empty($quality) and $quality = 95;
+
+ // 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);
+
+ 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))
+ {
+ $this->errors[] = $error;
+ }
+ else
+ {
+ // Output the image directly to the browser
+ if ($render !== FALSE)
+ {
+ $contents = file_get_contents($this->tmp_image);
+ switch (substr($file, strrpos($file, '.') + 1))
+ {
+ case 'jpg':
+ case 'jpeg':
+ header('Content-Type: image/jpeg');
+ break;
+ case 'gif':
+ header('Content-Type: image/gif');
+ break;
+ case 'png':
+ header('Content-Type: image/png');
+ break;
+ }
+ echo $contents;
+ }
+ }
+ }
+
+ // Remove the temporary image
+ unlink($this->tmp_image);
+ $this->tmp_image = '';
+
+ return $status;
+ }
+
+ public function crop($prop)
+ {
+ // Sanitize and normalize the properties into geometry
+ $this->sanitize_geometry($prop);
+
+ // 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))
+ {
+ $this->errors[] = $error;
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ public function flip($dir)
+ {
+ // 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))
+ {
+ $this->errors[] = $error;
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ public function resize($prop)
+ {
+ switch ($prop['master'])
+ {
+ case Image::WIDTH: // Wx
+ $dim = escapeshellarg($prop['width'].'x');
+ break;
+ case Image::HEIGHT: // xH
+ $dim = escapeshellarg('x'.$prop['height']);
+ break;
+ case Image::AUTO: // WxH
+ $dim = escapeshellarg($prop['width'].'x'.$prop['height']);
+ break;
+ case Image::NONE: // WxH!
+ $dim = escapeshellarg($prop['width'].'x'.$prop['height'].'!');
+ break;
+ }
+
+ // 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))
+ {
+ $this->errors[] = $error;
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ public function rotate($amt)
+ {
+ if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -rotate '.escapeshellarg($amt).' -background transparent '.$this->cmd_image.' '.$this->cmd_image))
+ {
+ $this->errors[] = $error;
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ public function sharpen($amount)
+ {
+ // Set the sigma, radius, and amount. The amount formula allows a nice
+ // spread between 1 and 100 without pixelizing the image badly.
+ $sigma = 0.5;
+ $radius = $sigma * 2;
+ $amount = round(($amount / 80) * 3.14, 2);
+
+ // 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))
+ {
+ $this->errors[] = $error;
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ public function composite($properties)
+ {
+ if ($error = exec(escapeshellcmd($this->dir.'composite'.$this->ext).' -geometry ' . escapeshellarg('+'.$properties['x'].'+'.$properties['y']).' -dissolve '.escapeshellarg($properties['transparency']).' '.escapeshellarg($properties['overlay_file']).' '.$this->cmd_image.' '.$this->cmd_image))
+ {
+ $this->errors[] = $error;
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ protected function properties()
+ {
+ return array_slice(getimagesize($this->tmp_image), 0, 2, FALSE);
+ }
+
+} // End Image ImageMagick Driver \ No newline at end of file
diff --git a/system/libraries/drivers/Session.php b/system/libraries/drivers/Session.php
new file mode 100644
index 00000000..fb58c8d3
--- /dev/null
+++ b/system/libraries/drivers/Session.php
@@ -0,0 +1,70 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Session driver interface
+ *
+ * $Id: Session.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package Core
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+interface Session_Driver {
+
+ /**
+ * Opens a session.
+ *
+ * @param string save path
+ * @param string session name
+ * @return boolean
+ */
+ public function open($path, $name);
+
+ /**
+ * Closes a session.
+ *
+ * @return boolean
+ */
+ public function close();
+
+ /**
+ * Reads a session.
+ *
+ * @param string session id
+ * @return string
+ */
+ public function read($id);
+
+ /**
+ * Writes a session.
+ *
+ * @param string session id
+ * @param string session data
+ * @return boolean
+ */
+ public function write($id, $data);
+
+ /**
+ * Destroys a session.
+ *
+ * @param string session id
+ * @return boolean
+ */
+ public function destroy($id);
+
+ /**
+ * Regenerates the session id.
+ *
+ * @return string
+ */
+ public function regenerate();
+
+ /**
+ * Garbage collection.
+ *
+ * @param integer session expiration period
+ * @return boolean
+ */
+ public function gc($maxlifetime);
+
+} // End Session Driver Interface \ No newline at end of file
diff --git a/system/libraries/drivers/Session/Cache.php b/system/libraries/drivers/Session/Cache.php
new file mode 100644
index 00000000..7221c9f2
--- /dev/null
+++ b/system/libraries/drivers/Session/Cache.php
@@ -0,0 +1,105 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Session cache driver.
+ *
+ * Cache library config goes in the session.storage config entry:
+ * $config['storage'] = array(
+ * 'driver' => 'apc',
+ * 'requests' => 10000
+ * );
+ * Lifetime does not need to be set as it is
+ * overridden by the session expiration setting.
+ *
+ * $Id: Cache.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package Core
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class Session_Cache_Driver implements Session_Driver {
+
+ protected $cache;
+ protected $encrypt;
+
+ public function __construct()
+ {
+ // Load Encrypt library
+ if (Kohana::config('session.encryption'))
+ {
+ $this->encrypt = new Encrypt;
+ }
+
+ Kohana::log('debug', 'Session Cache Driver Initialized');
+ }
+
+ public function open($path, $name)
+ {
+ $config = Kohana::config('session.storage');
+
+ if (empty($config))
+ {
+ // Load the default group
+ $config = Kohana::config('cache.default');
+ }
+ elseif (is_string($config))
+ {
+ $name = $config;
+
+ // Test the config group name
+ if (($config = Kohana::config('cache.'.$config)) === NULL)
+ throw new Kohana_Exception('cache.undefined_group', $name);
+ }
+
+ $config['lifetime'] = (Kohana::config('session.expiration') == 0) ? 86400 : Kohana::config('session.expiration');
+ $this->cache = new Cache($config);
+
+ return is_object($this->cache);
+ }
+
+ public function close()
+ {
+ return TRUE;
+ }
+
+ public function read($id)
+ {
+ $id = 'session_'.$id;
+ if ($data = $this->cache->get($id))
+ {
+ return Kohana::config('session.encryption') ? $this->encrypt->decode($data) : $data;
+ }
+
+ // Return value must be string, NOT a boolean
+ return '';
+ }
+
+ public function write($id, $data)
+ {
+ $id = 'session_'.$id;
+ $data = Kohana::config('session.encryption') ? $this->encrypt->encode($data) : $data;
+
+ return $this->cache->set($id, $data);
+ }
+
+ public function destroy($id)
+ {
+ $id = 'session_'.$id;
+ return $this->cache->delete($id);
+ }
+
+ public function regenerate()
+ {
+ session_regenerate_id(TRUE);
+
+ // Return new session id
+ return session_id();
+ }
+
+ public function gc($maxlifetime)
+ {
+ // Just return, caches are automatically cleaned up
+ return TRUE;
+ }
+
+} // End Session Cache Driver
diff --git a/system/libraries/drivers/Session/Cookie.php b/system/libraries/drivers/Session/Cookie.php
new file mode 100644
index 00000000..7b791064
--- /dev/null
+++ b/system/libraries/drivers/Session/Cookie.php
@@ -0,0 +1,80 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Session cookie driver.
+ *
+ * $Id: Cookie.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package Core
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class Session_Cookie_Driver implements Session_Driver {
+
+ protected $cookie_name;
+ protected $encrypt; // Library
+
+ public function __construct()
+ {
+ $this->cookie_name = Kohana::config('session.name').'_data';
+
+ if (Kohana::config('session.encryption'))
+ {
+ $this->encrypt = Encrypt::instance();
+ }
+
+ Kohana::log('debug', 'Session Cookie Driver Initialized');
+ }
+
+ public function open($path, $name)
+ {
+ return TRUE;
+ }
+
+ public function close()
+ {
+ return TRUE;
+ }
+
+ public function read($id)
+ {
+ $data = (string) cookie::get($this->cookie_name);
+
+ if ($data == '')
+ return $data;
+
+ return empty($this->encrypt) ? base64_decode($data) : $this->encrypt->decode($data);
+ }
+
+ public function write($id, $data)
+ {
+ $data = empty($this->encrypt) ? base64_encode($data) : $this->encrypt->encode($data);
+
+ if (strlen($data) > 4048)
+ {
+ Kohana::log('error', 'Session ('.$id.') data exceeds the 4KB limit, ignoring write.');
+ return FALSE;
+ }
+
+ return cookie::set($this->cookie_name, $data, Kohana::config('session.expiration'));
+ }
+
+ public function destroy($id)
+ {
+ return cookie::delete($this->cookie_name);
+ }
+
+ public function regenerate()
+ {
+ session_regenerate_id(TRUE);
+
+ // Return new id
+ return session_id();
+ }
+
+ public function gc($maxlifetime)
+ {
+ return TRUE;
+ }
+
+} // End Session Cookie Driver Class \ No newline at end of file
diff --git a/system/libraries/drivers/Session/Database.php b/system/libraries/drivers/Session/Database.php
new file mode 100644
index 00000000..b4144ffb
--- /dev/null
+++ b/system/libraries/drivers/Session/Database.php
@@ -0,0 +1,163 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Session database driver.
+ *
+ * $Id: Database.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package Core
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class Session_Database_Driver implements Session_Driver {
+
+ /*
+ CREATE TABLE sessions
+ (
+ session_id VARCHAR(127) NOT NULL,
+ last_activity INT(10) UNSIGNED NOT NULL,
+ data TEXT NOT NULL,
+ PRIMARY KEY (session_id)
+ );
+ */
+
+ // Database settings
+ protected $db = 'default';
+ protected $table = 'sessions';
+
+ // Encryption
+ protected $encrypt;
+
+ // Session settings
+ protected $session_id;
+ protected $written = FALSE;
+
+ public function __construct()
+ {
+ // Load configuration
+ $config = Kohana::config('session');
+
+ if ( ! empty($config['encryption']))
+ {
+ // Load encryption
+ $this->encrypt = Encrypt::instance();
+ }
+
+ if (is_array($config['storage']))
+ {
+ if ( ! empty($config['storage']['group']))
+ {
+ // Set the group name
+ $this->db = $config['storage']['group'];
+ }
+
+ if ( ! empty($config['storage']['table']))
+ {
+ // Set the table name
+ $this->table = $config['storage']['table'];
+ }
+ }
+
+ // Load database
+ $this->db = Database::instance($this->db);
+
+ Kohana::log('debug', 'Session Database Driver Initialized');
+ }
+
+ public function open($path, $name)
+ {
+ return TRUE;
+ }
+
+ public function close()
+ {
+ return TRUE;
+ }
+
+ public function read($id)
+ {
+ // Load the session
+ $query = $this->db->from($this->table)->where('session_id', $id)->limit(1)->get()->result(TRUE);
+
+ if ($query->count() === 0)
+ {
+ // No current session
+ $this->session_id = NULL;
+
+ return '';
+ }
+
+ // Set the current session id
+ $this->session_id = $id;
+
+ // Load the data
+ $data = $query->current()->data;
+
+ return ($this->encrypt === NULL) ? base64_decode($data) : $this->encrypt->decode($data);
+ }
+
+ public function write($id, $data)
+ {
+ $data = array
+ (
+ 'session_id' => $id,
+ 'last_activity' => time(),
+ 'data' => ($this->encrypt === NULL) ? base64_encode($data) : $this->encrypt->encode($data)
+ );
+
+ if ($this->session_id === NULL)
+ {
+ // Insert a new session
+ $query = $this->db->insert($this->table, $data);
+ }
+ elseif ($id === $this->session_id)
+ {
+ // Do not update the session_id
+ unset($data['session_id']);
+
+ // Update the existing session
+ $query = $this->db->update($this->table, $data, array('session_id' => $id));
+ }
+ else
+ {
+ // Update the session and id
+ $query = $this->db->update($this->table, $data, array('session_id' => $this->session_id));
+
+ // Set the new session id
+ $this->session_id = $id;
+ }
+
+ return (bool) $query->count();
+ }
+
+ public function destroy($id)
+ {
+ // Delete the requested session
+ $this->db->delete($this->table, array('session_id' => $id));
+
+ // Session id is no longer valid
+ $this->session_id = NULL;
+
+ return TRUE;
+ }
+
+ public function regenerate()
+ {
+ // Generate a new session id
+ session_regenerate_id();
+
+ // Return new session id
+ return session_id();
+ }
+
+ public function gc($maxlifetime)
+ {
+ // Delete all expired sessions
+ $query = $this->db->delete($this->table, array('last_activity <' => time() - $maxlifetime));
+
+ Kohana::log('debug', 'Session garbage collected: '.$query->count().' row(s) deleted.');
+
+ return TRUE;
+ }
+
+} // End Session Database Driver