summaryrefslogtreecommitdiff
path: root/system/libraries
diff options
context:
space:
mode:
Diffstat (limited to 'system/libraries')
-rw-r--r--system/libraries/Cache.php208
-rw-r--r--system/libraries/Controller.php78
-rw-r--r--system/libraries/Database.php1444
-rw-r--r--system/libraries/Database_Expression.php26
-rw-r--r--system/libraries/Encrypt.php164
-rw-r--r--system/libraries/Event_Observer.php70
-rw-r--r--system/libraries/Event_Subject.php67
-rw-r--r--system/libraries/Image.php460
-rw-r--r--system/libraries/Input.php452
-rw-r--r--system/libraries/Model.php31
-rw-r--r--system/libraries/ORM.php1431
-rw-r--r--system/libraries/ORM_Iterator.php228
-rw-r--r--system/libraries/ORM_Tree.php76
-rw-r--r--system/libraries/ORM_Versioned.php143
-rw-r--r--system/libraries/Pagination.php236
-rw-r--r--system/libraries/Profiler.php271
-rw-r--r--system/libraries/Profiler_Table.php69
-rw-r--r--system/libraries/Router.php304
-rw-r--r--system/libraries/Session.php458
-rw-r--r--system/libraries/URI.php279
-rw-r--r--system/libraries/Validation.php826
-rw-r--r--system/libraries/View.php309
-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
50 files changed, 13639 insertions, 0 deletions
diff --git a/system/libraries/Cache.php b/system/libraries/Cache.php
new file mode 100644
index 00000000..8a02a905
--- /dev/null
+++ b/system/libraries/Cache.php
@@ -0,0 +1,208 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Provides a driver-based interface for finding, creating, and deleting cached
+ * resources. Caches are identified by a unique string. Tagging of caches is
+ * also supported, and caches can be found and deleted by id or tag.
+ *
+ * $Id: Cache.php 4321 2009-05-04 01:39:44Z kiall $
+ *
+ * @package Cache
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class Cache_Core {
+
+ protected static $instances = array();
+
+ // For garbage collection
+ protected static $loaded;
+
+ // Configuration
+ protected $config;
+
+ // Driver object
+ protected $driver;
+
+ /**
+ * Returns a singleton instance of Cache.
+ *
+ * @param string configuration
+ * @return Cache_Core
+ */
+ public static function & instance($config = FALSE)
+ {
+ if ( ! isset(Cache::$instances[$config]))
+ {
+ // Create a new instance
+ Cache::$instances[$config] = new Cache($config);
+ }
+
+ return Cache::$instances[$config];
+ }
+
+ /**
+ * Loads the configured driver and validates it.
+ *
+ * @param array|string custom configuration or config group name
+ * @return void
+ */
+ public function __construct($config = FALSE)
+ {
+ if (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);
+ }
+
+ if (is_array($config))
+ {
+ // Append the default configuration options
+ $config += Kohana::config('cache.default');
+ }
+ else
+ {
+ // Load the default group
+ $config = Kohana::config('cache.default');
+ }
+
+ // Cache the config in the object
+ $this->config = $config;
+
+ // Set driver name
+ $driver = 'Cache_'.ucfirst($this->config['driver']).'_Driver';
+
+ // Load the driver
+ if ( ! Kohana::auto_load($driver))
+ throw new Kohana_Exception('core.driver_not_found', $this->config['driver'], get_class($this));
+
+ // Initialize the driver
+ $this->driver = new $driver($this->config['params']);
+
+ // Validate the driver
+ if ( ! ($this->driver instanceof Cache_Driver))
+ throw new Kohana_Exception('core.driver_implements', $this->config['driver'], get_class($this), 'Cache_Driver');
+
+ Kohana::log('debug', 'Cache Library initialized');
+
+ if (Cache::$loaded !== TRUE)
+ {
+ $this->config['requests'] = (int) $this->config['requests'];
+
+ if ($this->config['requests'] > 0 AND mt_rand(1, $this->config['requests']) === 1)
+ {
+ // Do garbage collection
+ $this->driver->delete_expired();
+
+ Kohana::log('debug', 'Cache: Expired caches deleted.');
+ }
+
+ // Cache has been loaded once
+ Cache::$loaded = TRUE;
+ }
+ }
+
+ /**
+ * Fetches a cache by id. NULL is returned when a cache item is not found.
+ *
+ * @param string cache id
+ * @return mixed cached data or NULL
+ */
+ public function get($id)
+ {
+ // Sanitize the ID
+ $id = $this->sanitize_id($id);
+
+ return $this->driver->get($id);
+ }
+
+ /**
+ * Fetches all of the caches for a given tag. An empty array will be
+ * returned when no matching caches are found.
+ *
+ * @param string cache tag
+ * @return array all cache items matching the tag
+ */
+ public function find($tag)
+ {
+ return $this->driver->find($tag);
+ }
+
+ /**
+ * Set a cache item by id. Tags may also be added and a custom lifetime
+ * can be set. Non-string data is automatically serialized.
+ *
+ * @param string unique cache id
+ * @param mixed data to cache
+ * @param array|string tags for this item
+ * @param integer number of seconds until the cache expires
+ * @return boolean
+ */
+ function set($id, $data, $tags = NULL, $lifetime = NULL)
+ {
+ if (is_resource($data))
+ throw new Kohana_Exception('cache.resources');
+
+ // Sanitize the ID
+ $id = $this->sanitize_id($id);
+
+ if ($lifetime === NULL)
+ {
+ // Get the default lifetime
+ $lifetime = $this->config['lifetime'];
+ }
+
+ return $this->driver->set($id, $data, (array) $tags, $lifetime);
+ }
+
+ /**
+ * Delete a cache item by id.
+ *
+ * @param string cache id
+ * @return boolean
+ */
+ public function delete($id)
+ {
+ // Sanitize the ID
+ $id = $this->sanitize_id($id);
+
+ return $this->driver->delete($id);
+ }
+
+ /**
+ * Delete all cache items with a given tag.
+ *
+ * @param string cache tag name
+ * @return boolean
+ */
+ public function delete_tag($tag)
+ {
+ return $this->driver->delete($tag, TRUE);
+ }
+
+ /**
+ * Delete ALL cache items items.
+ *
+ * @return boolean
+ */
+ public function delete_all()
+ {
+ return $this->driver->delete(TRUE);
+ }
+
+ /**
+ * Replaces troublesome characters with underscores.
+ *
+ * @param string cache id
+ * @return string
+ */
+ protected function sanitize_id($id)
+ {
+ // Change slashes and spaces to underscores
+ return str_replace(array('/', '\\', ' '), '_', $id);
+ }
+
+} // End Cache
diff --git a/system/libraries/Controller.php b/system/libraries/Controller.php
new file mode 100644
index 00000000..d111f25e
--- /dev/null
+++ b/system/libraries/Controller.php
@@ -0,0 +1,78 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Kohana Controller class. The controller class must be extended to work
+ * properly, so this class is defined as abstract.
+ *
+ * $Id: Controller.php 3979 2009-02-13 16:46:12Z zombor $
+ *
+ * @package Core
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+abstract class Controller_Core {
+
+ // Allow all controllers to run in production by default
+ const ALLOW_PRODUCTION = TRUE;
+
+ /**
+ * Loads URI, and Input into this controller.
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ if (Kohana::$instance == NULL)
+ {
+ // Set the instance to the first controller loaded
+ Kohana::$instance = $this;
+ }
+
+ // URI should always be available
+ $this->uri = URI::instance();
+
+ // Input should always be available
+ $this->input = Input::instance();
+ }
+
+ /**
+ * Handles methods that do not exist.
+ *
+ * @param string method name
+ * @param array arguments
+ * @return void
+ */
+ public function __call($method, $args)
+ {
+ // Default to showing a 404 page
+ Event::run('system.404');
+ }
+
+ /**
+ * Includes a View within the controller scope.
+ *
+ * @param string view filename
+ * @param array array of view variables
+ * @return string
+ */
+ public function _kohana_load_view($kohana_view_filename, $kohana_input_data)
+ {
+ if ($kohana_view_filename == '')
+ return;
+
+ // Buffering on
+ ob_start();
+
+ // Import the view variables to local namespace
+ extract($kohana_input_data, EXTR_SKIP);
+
+ // Views are straight HTML pages with embedded PHP, so importing them
+ // this way insures that $this can be accessed as if the user was in
+ // the controller, which gives the easiest access to libraries in views
+ include $kohana_view_filename;
+
+ // Fetch the output and close the buffer
+ return ob_get_clean();
+ }
+
+} // End Controller Class \ No newline at end of file
diff --git a/system/libraries/Database.php b/system/libraries/Database.php
new file mode 100644
index 00000000..6267f63a
--- /dev/null
+++ b/system/libraries/Database.php
@@ -0,0 +1,1444 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Provides database access in a platform agnostic way, using simple query building blocks.
+ *
+ * $Id: Database.php 4342 2009-05-08 16:56:01Z jheathco $
+ *
+ * @package Core
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class Database_Core {
+
+ // Database instances
+ public static $instances = array();
+
+ // Global benchmark
+ public static $benchmarks = array();
+
+ // Configuration
+ protected $config = array
+ (
+ 'benchmark' => TRUE,
+ 'persistent' => FALSE,
+ 'connection' => '',
+ 'character_set' => 'utf8',
+ 'table_prefix' => '',
+ 'object' => TRUE,
+ 'cache' => FALSE,
+ 'escape' => TRUE,
+ );
+
+ // Database driver object
+ protected $driver;
+ protected $link;
+
+ // Un-compiled parts of the SQL query
+ protected $select = array();
+ protected $set = array();
+ protected $from = array();
+ protected $join = array();
+ protected $where = array();
+ protected $orderby = array();
+ protected $order = array();
+ protected $groupby = array();
+ protected $having = array();
+ protected $distinct = FALSE;
+ protected $limit = FALSE;
+ protected $offset = FALSE;
+ protected $last_query = '';
+
+ // Stack of queries for push/pop
+ protected $query_history = array();
+
+ /**
+ * Returns a singleton instance of Database.
+ *
+ * @param mixed configuration array or DSN
+ * @return Database_Core
+ */
+ public static function & instance($name = 'default', $config = NULL)
+ {
+ if ( ! isset(Database::$instances[$name]))
+ {
+ // Create a new instance
+ Database::$instances[$name] = new Database($config === NULL ? $name : $config);
+ }
+
+ return Database::$instances[$name];
+ }
+
+ /**
+ * Returns the name of a given database instance.
+ *
+ * @param Database instance of Database
+ * @return string
+ */
+ public static function instance_name(Database $db)
+ {
+ return array_search($db, Database::$instances, TRUE);
+ }
+
+ /**
+ * Sets up the database configuration, loads the Database_Driver.
+ *
+ * @throws Kohana_Database_Exception
+ */
+ public function __construct($config = array())
+ {
+ if (empty($config))
+ {
+ // Load the default group
+ $config = Kohana::config('database.default');
+ }
+ elseif (is_array($config) AND count($config) > 0)
+ {
+ if ( ! array_key_exists('connection', $config))
+ {
+ $config = array('connection' => $config);
+ }
+ }
+ elseif (is_string($config))
+ {
+ // The config is a DSN string
+ if (strpos($config, '://') !== FALSE)
+ {
+ $config = array('connection' => $config);
+ }
+ // The config is a group name
+ else
+ {
+ $name = $config;
+
+ // Test the config group name
+ if (($config = Kohana::config('database.'.$config)) === NULL)
+ throw new Kohana_Database_Exception('database.undefined_group', $name);
+ }
+ }
+
+ // Merge the default config with the passed config
+ $this->config = array_merge($this->config, $config);
+
+ if (is_string($this->config['connection']))
+ {
+ // Make sure the connection is valid
+ if (strpos($this->config['connection'], '://') === FALSE)
+ throw new Kohana_Database_Exception('database.invalid_dsn', $this->config['connection']);
+
+ // Parse the DSN, creating an array to hold the connection parameters
+ $db = array
+ (
+ 'type' => FALSE,
+ 'user' => FALSE,
+ 'pass' => FALSE,
+ 'host' => FALSE,
+ 'port' => FALSE,
+ 'socket' => FALSE,
+ 'database' => FALSE
+ );
+
+ // Get the protocol and arguments
+ list ($db['type'], $connection) = explode('://', $this->config['connection'], 2);
+
+ if (strpos($connection, '@') !== FALSE)
+ {
+ // Get the username and password
+ list ($db['pass'], $connection) = explode('@', $connection, 2);
+ // Check if a password is supplied
+ $logindata = explode(':', $db['pass'], 2);
+ $db['pass'] = (count($logindata) > 1) ? $logindata[1] : '';
+ $db['user'] = $logindata[0];
+
+ // Prepare for finding the database
+ $connection = explode('/', $connection);
+
+ // Find the database name
+ $db['database'] = array_pop($connection);
+
+ // Reset connection string
+ $connection = implode('/', $connection);
+
+ // Find the socket
+ if (preg_match('/^unix\([^)]++\)/', $connection))
+ {
+ // This one is a little hairy: we explode based on the end of
+ // the socket, removing the 'unix(' from the connection string
+ list ($db['socket'], $connection) = explode(')', substr($connection, 5), 2);
+ }
+ elseif (strpos($connection, ':') !== FALSE)
+ {
+ // Fetch the host and port name
+ list ($db['host'], $db['port']) = explode(':', $connection, 2);
+ }
+ else
+ {
+ $db['host'] = $connection;
+ }
+ }
+ else
+ {
+ // File connection
+ $connection = explode('/', $connection);
+
+ // Find database file name
+ $db['database'] = array_pop($connection);
+
+ // Find database directory name
+ $db['socket'] = implode('/', $connection).'/';
+ }
+
+ // Reset the connection array to the database config
+ $this->config['connection'] = $db;
+ }
+ // Set driver name
+ $driver = 'Database_'.ucfirst($this->config['connection']['type']).'_Driver';
+
+ // Load the driver
+ if ( ! Kohana::auto_load($driver))
+ throw new Kohana_Database_Exception('core.driver_not_found', $this->config['connection']['type'], get_class($this));
+
+ // Initialize the driver
+ $this->driver = new $driver($this->config);
+
+ // Validate the driver
+ if ( ! ($this->driver instanceof Database_Driver))
+ throw new Kohana_Database_Exception('core.driver_implements', $this->config['connection']['type'], get_class($this), 'Database_Driver');
+
+ Kohana::log('debug', 'Database Library initialized');
+ }
+
+ /**
+ * Simple connect method to get the database queries up and running.
+ *
+ * @return void
+ */
+ public function connect()
+ {
+ // A link can be a resource or an object
+ if ( ! is_resource($this->link) AND ! is_object($this->link))
+ {
+ $this->link = $this->driver->connect();
+ if ( ! is_resource($this->link) AND ! is_object($this->link))
+ throw new Kohana_Database_Exception('database.connection', $this->driver->show_error());
+
+ // Clear password after successful connect
+ $this->config['connection']['pass'] = NULL;
+ }
+ }
+
+ /**
+ * Runs a query into the driver and returns the result.
+ *
+ * @param string SQL query to execute
+ * @return Database_Result
+ */
+ public function query($sql = '')
+ {
+ if ($sql == '') return FALSE;
+
+ // No link? Connect!
+ $this->link or $this->connect();
+
+ // Start the benchmark
+ $start = microtime(TRUE);
+
+ if (func_num_args() > 1) //if we have more than one argument ($sql)
+ {
+ $argv = func_get_args();
+ $binds = (is_array(next($argv))) ? current($argv) : array_slice($argv, 1);
+ }
+
+ // Compile binds if needed
+ if (isset($binds))
+ {
+ $sql = $this->compile_binds($sql, $binds);
+ }
+
+ // Fetch the result
+ $result = $this->driver->query($this->last_query = $sql);
+
+ // Stop the benchmark
+ $stop = microtime(TRUE);
+
+ if ($this->config['benchmark'] == TRUE)
+ {
+ // Benchmark the query
+ Database::$benchmarks[] = array('query' => $sql, 'time' => $stop - $start, 'rows' => count($result));
+ }
+
+ return $result;
+ }
+
+ /**
+ * Selects the column names for a database query.
+ *
+ * @param string string or array of column names to select
+ * @return Database_Core This Database object.
+ */
+ public function select($sql = '*')
+ {
+ if (func_num_args() > 1)
+ {
+ $sql = func_get_args();
+ }
+ elseif (is_string($sql))
+ {
+ $sql = explode(',', $sql);
+ }
+ else
+ {
+ $sql = (array) $sql;
+ }
+
+ foreach ($sql as $val)
+ {
+ if (($val = trim($val)) === '') continue;
+
+ if (strpos($val, '(') === FALSE AND $val !== '*')
+ {
+ if (preg_match('/^DISTINCT\s++(.+)$/i', $val, $matches))
+ {
+ // Only prepend with table prefix if table name is specified
+ $val = (strpos($matches[1], '.') !== FALSE) ? $this->config['table_prefix'].$matches[1] : $matches[1];
+
+ $this->distinct = TRUE;
+ }
+ else
+ {
+ $val = (strpos($val, '.') !== FALSE) ? $this->config['table_prefix'].$val : $val;
+ }
+
+ $val = $this->driver->escape_column($val);
+ }
+
+ $this->select[] = $val;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Selects the from table(s) for a database query.
+ *
+ * @param string string or array of tables to select
+ * @return Database_Core This Database object.
+ */
+ public function from($sql)
+ {
+ if (func_num_args() > 1)
+ {
+ $sql = func_get_args();
+ }
+ elseif (is_string($sql))
+ {
+ $sql = explode(',', $sql);
+ }
+ else
+ {
+ $sql = array($sql);
+ }
+
+ foreach ($sql as $val)
+ {
+ if (is_string($val))
+ {
+ if (($val = trim($val)) === '') continue;
+
+ // TODO: Temporary solution, this should be moved to database driver (AS is checked for twice)
+ if (stripos($val, ' AS ') !== FALSE)
+ {
+ $val = str_ireplace(' AS ', ' AS ', $val);
+
+ list($table, $alias) = explode(' AS ', $val);
+
+ // Attach prefix to both sides of the AS
+ $val = $this->config['table_prefix'].$table.' AS '.$this->config['table_prefix'].$alias;
+ }
+ else
+ {
+ $val = $this->config['table_prefix'].$val;
+ }
+ }
+
+ $this->from[] = $val;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Generates the JOIN portion of the query.
+ *
+ * @param string table name
+ * @param string|array where key or array of key => value pairs
+ * @param string where value
+ * @param string type of join
+ * @return Database_Core This Database object.
+ */
+ public function join($table, $key, $value = NULL, $type = '')
+ {
+ $join = array();
+
+ if ( ! empty($type))
+ {
+ $type = strtoupper(trim($type));
+
+ if ( ! in_array($type, array('LEFT', 'RIGHT', 'OUTER', 'INNER', 'LEFT OUTER', 'RIGHT OUTER'), TRUE))
+ {
+ $type = '';
+ }
+ else
+ {
+ $type .= ' ';
+ }
+ }
+
+ $cond = array();
+ $keys = is_array($key) ? $key : array($key => $value);
+ foreach ($keys as $key => $value)
+ {
+ $key = (strpos($key, '.') !== FALSE) ? $this->config['table_prefix'].$key : $key;
+
+ if (is_string($value))
+ {
+ // Only escape if it's a string
+ $value = $this->driver->escape_column($this->config['table_prefix'].$value);
+ }
+
+ $cond[] = $this->driver->where($key, $value, 'AND ', count($cond), FALSE);
+ }
+
+ if ( ! is_array($this->join))
+ {
+ $this->join = array();
+ }
+
+ if ( ! is_array($table))
+ {
+ $table = array($table);
+ }
+
+ foreach ($table as $t)
+ {
+ if (is_string($t))
+ {
+ // TODO: Temporary solution, this should be moved to database driver (AS is checked for twice)
+ if (stripos($t, ' AS ') !== FALSE)
+ {
+ $t = str_ireplace(' AS ', ' AS ', $t);
+
+ list($table, $alias) = explode(' AS ', $t);
+
+ // Attach prefix to both sides of the AS
+ $t = $this->config['table_prefix'].$table.' AS '.$this->config['table_prefix'].$alias;
+ }
+ else
+ {
+ $t = $this->config['table_prefix'].$t;
+ }
+ }
+
+ $join['tables'][] = $this->driver->escape_column($t);
+ }
+
+ $join['conditions'] = '('.trim(implode(' ', $cond)).')';
+ $join['type'] = $type;
+
+ $this->join[] = $join;
+
+ return $this;
+ }
+
+
+ /**
+ * Selects the where(s) for a database query.
+ *
+ * @param string|array key name or array of key => value pairs
+ * @param string value to match with key
+ * @param boolean disable quoting of WHERE clause
+ * @return Database_Core This Database object.
+ */
+ public function where($key, $value = NULL, $quote = TRUE)
+ {
+ $quote = (func_num_args() < 2 AND ! is_array($key)) ? -1 : $quote;
+ if (is_object($key))
+ {
+ $keys = array((string) $key => '');
+ }
+ elseif ( ! is_array($key))
+ {
+ $keys = array($key => $value);
+ }
+ else
+ {
+ $keys = $key;
+ }
+
+ foreach ($keys as $key => $value)
+ {
+ $key = (strpos($key, '.') !== FALSE) ? $this->config['table_prefix'].$key : $key;
+ $this->where[] = $this->driver->where($key, $value, 'AND ', count($this->where), $quote);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Selects the or where(s) for a database query.
+ *
+ * @param string|array key name or array of key => value pairs
+ * @param string value to match with key
+ * @param boolean disable quoting of WHERE clause
+ * @return Database_Core This Database object.
+ */
+ public function orwhere($key, $value = NULL, $quote = TRUE)
+ {
+ $quote = (func_num_args() < 2 AND ! is_array($key)) ? -1 : $quote;
+ if (is_object($key))
+ {
+ $keys = array((string) $key => '');
+ }
+ elseif ( ! is_array($key))
+ {
+ $keys = array($key => $value);
+ }
+ else
+ {
+ $keys = $key;
+ }
+
+ foreach ($keys as $key => $value)
+ {
+ $key = (strpos($key, '.') !== FALSE) ? $this->config['table_prefix'].$key : $key;
+ $this->where[] = $this->driver->where($key, $value, 'OR ', count($this->where), $quote);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Selects the like(s) for a database query.
+ *
+ * @param string|array field name or array of field => match pairs
+ * @param string like value to match with field
+ * @param boolean automatically add starting and ending wildcards
+ * @return Database_Core This Database object.
+ */
+ public function like($field, $match = '', $auto = TRUE)
+ {
+ $fields = is_array($field) ? $field : array($field => $match);
+
+ foreach ($fields as $field => $match)
+ {
+ $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
+ $this->where[] = $this->driver->like($field, $match, $auto, 'AND ', count($this->where));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Selects the or like(s) for a database query.
+ *
+ * @param string|array field name or array of field => match pairs
+ * @param string like value to match with field
+ * @param boolean automatically add starting and ending wildcards
+ * @return Database_Core This Database object.
+ */
+ public function orlike($field, $match = '', $auto = TRUE)
+ {
+ $fields = is_array($field) ? $field : array($field => $match);
+
+ foreach ($fields as $field => $match)
+ {
+ $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
+ $this->where[] = $this->driver->like($field, $match, $auto, 'OR ', count($this->where));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Selects the not like(s) for a database query.
+ *
+ * @param string|array field name or array of field => match pairs
+ * @param string like value to match with field
+ * @param boolean automatically add starting and ending wildcards
+ * @return Database_Core This Database object.
+ */
+ public function notlike($field, $match = '', $auto = TRUE)
+ {
+ $fields = is_array($field) ? $field : array($field => $match);
+
+ foreach ($fields as $field => $match)
+ {
+ $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
+ $this->where[] = $this->driver->notlike($field, $match, $auto, 'AND ', count($this->where));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Selects the or not like(s) for a database query.
+ *
+ * @param string|array field name or array of field => match pairs
+ * @param string like value to match with field
+ * @return Database_Core This Database object.
+ */
+ public function ornotlike($field, $match = '', $auto = TRUE)
+ {
+ $fields = is_array($field) ? $field : array($field => $match);
+
+ foreach ($fields as $field => $match)
+ {
+ $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
+ $this->where[] = $this->driver->notlike($field, $match, $auto, 'OR ', count($this->where));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Selects the like(s) for a database query.
+ *
+ * @param string|array field name or array of field => match pairs
+ * @param string like value to match with field
+ * @return Database_Core This Database object.
+ */
+ public function regex($field, $match = '')
+ {
+ $fields = is_array($field) ? $field : array($field => $match);
+
+ foreach ($fields as $field => $match)
+ {
+ $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
+ $this->where[] = $this->driver->regex($field, $match, 'AND ', count($this->where));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Selects the or like(s) for a database query.
+ *
+ * @param string|array field name or array of field => match pairs
+ * @param string like value to match with field
+ * @return Database_Core This Database object.
+ */
+ public function orregex($field, $match = '')
+ {
+ $fields = is_array($field) ? $field : array($field => $match);
+
+ foreach ($fields as $field => $match)
+ {
+ $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
+ $this->where[] = $this->driver->regex($field, $match, 'OR ', count($this->where));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Selects the not regex(s) for a database query.
+ *
+ * @param string|array field name or array of field => match pairs
+ * @param string regex value to match with field
+ * @return Database_Core This Database object.
+ */
+ public function notregex($field, $match = '')
+ {
+ $fields = is_array($field) ? $field : array($field => $match);
+
+ foreach ($fields as $field => $match)
+ {
+ $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
+ $this->where[] = $this->driver->notregex($field, $match, 'AND ', count($this->where));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Selects the or not regex(s) for a database query.
+ *
+ * @param string|array field name or array of field => match pairs
+ * @param string regex value to match with field
+ * @return Database_Core This Database object.
+ */
+ public function ornotregex($field, $match = '')
+ {
+ $fields = is_array($field) ? $field : array($field => $match);
+
+ foreach ($fields as $field => $match)
+ {
+ $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
+ $this->where[] = $this->driver->notregex($field, $match, 'OR ', count($this->where));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Chooses the column to group by in a select query.
+ *
+ * @param string column name to group by
+ * @return Database_Core This Database object.
+ */
+ public function groupby($by)
+ {
+ if ( ! is_array($by))
+ {
+ $by = explode(',', (string) $by);
+ }
+
+ foreach ($by as $val)
+ {
+ $val = trim($val);
+
+ if ($val != '')
+ {
+ // Add the table prefix if we are using table.column names
+ if(strpos($val, '.'))
+ {
+ $val = $this->config['table_prefix'].$val;
+ }
+
+ $this->groupby[] = $this->driver->escape_column($val);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Selects the having(s) for a database query.
+ *
+ * @param string|array key name or array of key => value pairs
+ * @param string value to match with key
+ * @param boolean disable quoting of WHERE clause
+ * @return Database_Core This Database object.
+ */
+ public function having($key, $value = '', $quote = TRUE)
+ {
+ $this->having[] = $this->driver->where($key, $value, 'AND', count($this->having), TRUE);
+ return $this;
+ }
+
+ /**
+ * Selects the or having(s) for a database query.
+ *
+ * @param string|array key name or array of key => value pairs
+ * @param string value to match with key
+ * @param boolean disable quoting of WHERE clause
+ * @return Database_Core This Database object.
+ */
+ public function orhaving($key, $value = '', $quote = TRUE)
+ {
+ $this->having[] = $this->driver->where($key, $value, 'OR', count($this->having), TRUE);
+ return $this;
+ }
+
+ /**
+ * Chooses which column(s) to order the select query by.
+ *
+ * @param string|array column(s) to order on, can be an array, single column, or comma seperated list of columns
+ * @param string direction of the order
+ * @return Database_Core This Database object.
+ */
+ public function orderby($orderby, $direction = NULL)
+ {
+ if ( ! is_array($orderby))
+ {
+ $orderby = array($orderby => $direction);
+ }
+
+ foreach ($orderby as $column => $direction)
+ {
+ $direction = strtoupper(trim($direction));
+
+ // Add a direction if the provided one isn't valid
+ if ( ! in_array($direction, array('ASC', 'DESC', 'RAND()', 'RANDOM()', 'NULL')))
+ {
+ $direction = 'ASC';
+ }
+
+ // Add the table prefix if a table.column was passed
+ if (strpos($column, '.'))
+ {
+ $column = $this->config['table_prefix'].$column;
+ }
+
+ $this->orderby[] = $this->driver->escape_column($column).' '.$direction;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Selects the limit section of a query.
+ *
+ * @param integer number of rows to limit result to
+ * @param integer offset in result to start returning rows from
+ * @return Database_Core This Database object.
+ */
+ public function limit($limit, $offset = NULL)
+ {
+ $this->limit = (int) $limit;
+
+ if ($offset !== NULL OR ! is_int($this->offset))
+ {
+ $this->offset($offset);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets the offset portion of a query.
+ *
+ * @param integer offset value
+ * @return Database_Core This Database object.
+ */
+ public function offset($value)
+ {
+ $this->offset = (int) $value;
+
+ return $this;
+ }
+
+ /**
+ * Allows key/value pairs to be set for inserting or updating.
+ *
+ * @param string|array key name or array of key => value pairs
+ * @param string value to match with key
+ * @return Database_Core This Database object.
+ */
+ public function set($key, $value = '')
+ {
+ if ( ! is_array($key))
+ {
+ $key = array($key => $value);
+ }
+
+ foreach ($key as $k => $v)
+ {
+ // Add a table prefix if the column includes the table.
+ if (strpos($k, '.'))
+ $k = $this->config['table_prefix'].$k;
+
+ $this->set[$k] = $this->driver->escape($v);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Compiles the select statement based on the other functions called and runs the query.
+ *
+ * @param string table name
+ * @param string limit clause
+ * @param string offset clause
+ * @return Database_Result
+ */
+ public function get($table = '', $limit = NULL, $offset = NULL)
+ {
+ if ($table != '')
+ {
+ $this->from($table);
+ }
+
+ if ( ! is_null($limit))
+ {
+ $this->limit($limit, $offset);
+ }
+
+ $sql = $this->driver->compile_select(get_object_vars($this));
+
+ $this->reset_select();
+
+ $result = $this->query($sql);
+
+ $this->last_query = $sql;
+
+ return $result;
+ }
+
+ /**
+ * Compiles the select statement based on the other functions called and runs the query.
+ *
+ * @param string table name
+ * @param array where clause
+ * @param string limit clause
+ * @param string offset clause
+ * @return Database_Core This Database object.
+ */
+ public function getwhere($table = '', $where = NULL, $limit = NULL, $offset = NULL)
+ {
+ if ($table != '')
+ {
+ $this->from($table);
+ }
+
+ if ( ! is_null($where))
+ {
+ $this->where($where);
+ }
+
+ if ( ! is_null($limit))
+ {
+ $this->limit($limit, $offset);
+ }
+
+ $sql = $this->driver->compile_select(get_object_vars($this));
+
+ $this->reset_select();
+
+ $result = $this->query($sql);
+
+ return $result;
+ }
+
+ /**
+ * Compiles the select statement based on the other functions called and returns the query string.
+ *
+ * @param string table name
+ * @param string limit clause
+ * @param string offset clause
+ * @return string sql string
+ */
+ public function compile($table = '', $limit = NULL, $offset = NULL)
+ {
+ if ($table != '')
+ {
+ $this->from($table);
+ }
+
+ if ( ! is_null($limit))
+ {
+ $this->limit($limit, $offset);
+ }
+
+ $sql = $this->driver->compile_select(get_object_vars($this));
+
+ $this->reset_select();
+
+ return $sql;
+ }
+
+ /**
+ * Compiles an insert string and runs the query.
+ *
+ * @param string table name
+ * @param array array of key/value pairs to insert
+ * @return Database_Result Query result
+ */
+ public function insert($table = '', $set = NULL)
+ {
+ if ( ! is_null($set))
+ {
+ $this->set($set);
+ }
+
+ if ($this->set == NULL)
+ throw new Kohana_Database_Exception('database.must_use_set');
+
+ if ($table == '')
+ {
+ if ( ! isset($this->from[0]))
+ throw new Kohana_Database_Exception('database.must_use_table');
+
+ $table = $this->from[0];
+ }
+
+ // If caching is enabled, clear the cache before inserting
+ ($this->config['cache'] === TRUE) and $this->clear_cache();
+
+ $sql = $this->driver->insert($this->config['table_prefix'].$table, array_keys($this->set), array_values($this->set));
+
+ $this->reset_write();
+
+ return $this->query($sql);
+ }
+
+ /**
+ * Adds an "IN" condition to the where clause
+ *
+ * @param string Name of the column being examined
+ * @param mixed An array or string to match against
+ * @param bool Generate a NOT IN clause instead
+ * @return Database_Core This Database object.
+ */
+ public function in($field, $values, $not = FALSE)
+ {
+ if (is_array($values))
+ {
+ $escaped_values = array();
+ foreach ($values as $v)
+ {
+ if (is_numeric($v))
+ {
+ $escaped_values[] = $v;
+ }
+ else
+ {
+ $escaped_values[] = "'".$this->driver->escape_str($v)."'";
+ }
+ }
+ $values = implode(",", $escaped_values);
+ }
+
+ $where = $this->driver->escape_column(((strpos($field,'.') !== FALSE) ? $this->config['table_prefix'] : ''). $field).' '.($not === TRUE ? 'NOT ' : '').'IN ('.$values.')';
+ $this->where[] = $this->driver->where($where, '', 'AND ', count($this->where), -1);
+
+ return $this;
+ }
+
+ /**
+ * Adds a "NOT IN" condition to the where clause
+ *
+ * @param string Name of the column being examined
+ * @param mixed An array or string to match against
+ * @return Database_Core This Database object.
+ */
+ public function notin($field, $values)
+ {
+ return $this->in($field, $values, TRUE);
+ }
+
+ /**
+ * Compiles a merge string and runs the query.
+ *
+ * @param string table name
+ * @param array array of key/value pairs to merge
+ * @return Database_Result Query result
+ */
+ public function merge($table = '', $set = NULL)
+ {
+ if ( ! is_null($set))
+ {
+ $this->set($set);
+ }
+
+ if ($this->set == NULL)
+ throw new Kohana_Database_Exception('database.must_use_set');
+
+ if ($table == '')
+ {
+ if ( ! isset($this->from[0]))
+ throw new Kohana_Database_Exception('database.must_use_table');
+
+ $table = $this->from[0];
+ }
+
+ $sql = $this->driver->merge($this->config['table_prefix'].$table, array_keys($this->set), array_values($this->set));
+
+ $this->reset_write();
+ return $this->query($sql);
+ }
+
+ /**
+ * Compiles an update string and runs the query.
+ *
+ * @param string table name
+ * @param array associative array of update values
+ * @param array where clause
+ * @return Database_Result Query result
+ */
+ public function update($table = '', $set = NULL, $where = NULL)
+ {
+ if ( is_array($set))
+ {
+ $this->set($set);
+ }
+
+ if ( ! is_null($where))
+ {
+ $this->where($where);
+ }
+
+ if ($this->set == FALSE)
+ throw new Kohana_Database_Exception('database.must_use_set');
+
+ if ($table == '')
+ {
+ if ( ! isset($this->from[0]))
+ throw new Kohana_Database_Exception('database.must_use_table');
+
+ $table = $this->from[0];
+ }
+
+ $sql = $this->driver->update($this->config['table_prefix'].$table, $this->set, $this->where);
+
+ $this->reset_write();
+ return $this->query($sql);
+ }
+
+ /**
+ * Compiles a delete string and runs the query.
+ *
+ * @param string table name
+ * @param array where clause
+ * @return Database_Result Query result
+ */
+ public function delete($table = '', $where = NULL)
+ {
+ if ($table == '')
+ {
+ if ( ! isset($this->from[0]))
+ throw new Kohana_Database_Exception('database.must_use_table');
+
+ $table = $this->from[0];
+ }
+ else
+ {
+ $table = $this->config['table_prefix'].$table;
+ }
+
+ if (! is_null($where))
+ {
+ $this->where($where);
+ }
+
+ if (count($this->where) < 1)
+ throw new Kohana_Database_Exception('database.must_use_where');
+
+ $sql = $this->driver->delete($table, $this->where);
+
+ $this->reset_write();
+ return $this->query($sql);
+ }
+
+ /**
+ * Returns the last query run.
+ *
+ * @return string SQL
+ */
+ public function last_query()
+ {
+ return $this->last_query;
+ }
+
+ /**
+ * Count query records.
+ *
+ * @param string table name
+ * @param array where clause
+ * @return integer
+ */
+ public function count_records($table = FALSE, $where = NULL)
+ {
+ if (count($this->from) < 1)
+ {
+ if ($table == FALSE)
+ throw new Kohana_Database_Exception('database.must_use_table');
+
+ $this->from($table);
+ }
+
+ if ($where !== NULL)
+ {
+ $this->where($where);
+ }
+
+ $query = $this->select('COUNT(*) AS '.$this->escape_column('records_found'))->get()->result(TRUE);
+
+ return (int) $query->current()->records_found;
+ }
+
+ /**
+ * Resets all private select variables.
+ *
+ * @return void
+ */
+ protected function reset_select()
+ {
+ $this->select = array();
+ $this->from = array();
+ $this->join = array();
+ $this->where = array();
+ $this->orderby = array();
+ $this->groupby = array();
+ $this->having = array();
+ $this->distinct = FALSE;
+ $this->limit = FALSE;
+ $this->offset = FALSE;
+ }
+
+ /**
+ * Resets all private insert and update variables.
+ *
+ * @return void
+ */
+ protected function reset_write()
+ {
+ $this->set = array();
+ $this->from = array();
+ $this->where = array();
+ }
+
+ /**
+ * Lists all the tables in the current database.
+ *
+ * @return array
+ */
+ public function list_tables()
+ {
+ $this->link or $this->connect();
+
+ return $this->driver->list_tables();
+ }
+
+ /**
+ * See if a table exists in the database.
+ *
+ * @param string table name
+ * @param boolean True to attach table prefix
+ * @return boolean
+ */
+ public function table_exists($table_name, $prefix = TRUE)
+ {
+ if ($prefix)
+ return in_array($this->config['table_prefix'].$table_name, $this->list_tables());
+ else
+ return in_array($table_name, $this->list_tables());
+ }
+
+ /**
+ * Combine a SQL statement with the bind values. Used for safe queries.
+ *
+ * @param string query to bind to the values
+ * @param array array of values to bind to the query
+ * @return string
+ */
+ public function compile_binds($sql, $binds)
+ {
+ foreach ((array) $binds as $val)
+ {
+ // If the SQL contains no more bind marks ("?"), we're done.
+ if (($next_bind_pos = strpos($sql, '?')) === FALSE)
+ break;
+
+ // Properly escape the bind value.
+ $val = $this->driver->escape($val);
+
+ // Temporarily replace possible bind marks ("?"), in the bind value itself, with a placeholder.
+ $val = str_replace('?', '{%B%}', $val);
+
+ // Replace the first bind mark ("?") with its corresponding value.
+ $sql = substr($sql, 0, $next_bind_pos).$val.substr($sql, $next_bind_pos + 1);
+ }
+
+ // Restore placeholders.
+ return str_replace('{%B%}', '?', $sql);
+ }
+
+ /**
+ * Get the field data for a database table, along with the field's attributes.
+ *
+ * @param string table name
+ * @return array
+ */
+ public function field_data($table = '')
+ {
+ $this->link or $this->connect();
+
+ return $this->driver->field_data($this->config['table_prefix'].$table);
+ }
+
+ /**
+ * Get the field data for a database table, along with the field's attributes.
+ *
+ * @param string table name
+ * @return array
+ */
+ public function list_fields($table = '')
+ {
+ $this->link or $this->connect();
+
+ return $this->driver->list_fields($this->config['table_prefix'].$table);
+ }
+
+ /**
+ * Escapes a value for a query.
+ *
+ * @param mixed value to escape
+ * @return string
+ */
+ public function escape($value)
+ {
+ return $this->driver->escape($value);
+ }
+
+ /**
+ * Escapes a string for a query.
+ *
+ * @param string string to escape
+ * @return string
+ */
+ public function escape_str($str)
+ {
+ return $this->driver->escape_str($str);
+ }
+
+ /**
+ * Escapes a table name for a query.
+ *
+ * @param string string to escape
+ * @return string
+ */
+ public function escape_table($table)
+ {
+ return $this->driver->escape_table($table);
+ }
+
+ /**
+ * Escapes a column name for a query.
+ *
+ * @param string string to escape
+ * @return string
+ */
+ public function escape_column($table)
+ {
+ return $this->driver->escape_column($table);
+ }
+
+ /**
+ * Returns table prefix of current configuration.
+ *
+ * @return string
+ */
+ public function table_prefix()
+ {
+ return $this->config['table_prefix'];
+ }
+
+ /**
+ * Clears the query cache.
+ *
+ * @param string|TRUE clear cache by SQL statement or TRUE for last query
+ * @return Database_Core This Database object.
+ */
+ public function clear_cache($sql = NULL)
+ {
+ if ($sql === TRUE)
+ {
+ $this->driver->clear_cache($this->last_query);
+ }
+ elseif (is_string($sql))
+ {
+ $this->driver->clear_cache($sql);
+ }
+ else
+ {
+ $this->driver->clear_cache();
+ }
+
+ return $this;
+ }
+
+ /**
+ * Pushes existing query space onto the query stack. Use push
+ * and pop to prevent queries from clashing before they are
+ * executed
+ *
+ * @return Database_Core This Databaes object
+ */
+ public function push()
+ {
+ array_push($this->query_history, array(
+ $this->select,
+ $this->from,
+ $this->join,
+ $this->where,
+ $this->orderby,
+ $this->order,
+ $this->groupby,
+ $this->having,
+ $this->distinct,
+ $this->limit,
+ $this->offset
+ ));
+
+ $this->reset_select();
+
+ return $this;
+ }
+
+ /**
+ * Pops from query stack into the current query space.
+ *
+ * @return Database_Core This Databaes object
+ */
+ public function pop()
+ {
+ if (count($this->query_history) == 0)
+ {
+ // No history
+ return $this;
+ }
+
+ list(
+ $this->select,
+ $this->from,
+ $this->join,
+ $this->where,
+ $this->orderby,
+ $this->order,
+ $this->groupby,
+ $this->having,
+ $this->distinct,
+ $this->limit,
+ $this->offset
+ ) = array_pop($this->query_history);
+
+ return $this;
+ }
+
+ /**
+ * Count the number of records in the last query, without LIMIT or OFFSET applied.
+ *
+ * @return integer
+ */
+ public function count_last_query()
+ {
+ if ($sql = $this->last_query())
+ {
+ if (stripos($sql, 'LIMIT') !== FALSE)
+ {
+ // Remove LIMIT from the SQL
+ $sql = preg_replace('/\sLIMIT\s+[^a-z]+/i', ' ', $sql);
+ }
+
+ if (stripos($sql, 'OFFSET') !== FALSE)
+ {
+ // Remove OFFSET from the SQL
+ $sql = preg_replace('/\sOFFSET\s+\d+/i', '', $sql);
+ }
+
+ // Get the total rows from the last query executed
+ $result = $this->query
+ (
+ 'SELECT COUNT(*) AS '.$this->escape_column('total_rows').' '.
+ 'FROM ('.trim($sql).') AS '.$this->escape_table('counted_results')
+ );
+
+ // Return the total number of rows from the query
+ return (int) $result->current()->total_rows;
+ }
+
+ return FALSE;
+ }
+
+} // End Database Class
+
+
+/**
+ * Sets the code for a Database exception.
+ */
+class Kohana_Database_Exception extends Kohana_Exception {
+
+ protected $code = E_DATABASE_ERROR;
+
+} // End Kohana Database Exception
diff --git a/system/libraries/Database_Expression.php b/system/libraries/Database_Expression.php
new file mode 100644
index 00000000..940a6363
--- /dev/null
+++ b/system/libraries/Database_Expression.php
@@ -0,0 +1,26 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Database expression class to allow for explicit joins and where expressions.
+ *
+ * $Id: Database_Expression.php 4037 2009-03-04 23:35:53Z jheathco $
+ *
+ * @package Core
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class Database_Expression_Core {
+
+ protected $expression;
+
+ public function __construct($expression)
+ {
+ $this->expression = $expression;
+ }
+
+ public function __toString()
+ {
+ return (string) $this->expression;
+ }
+
+} // End Database Expr Class \ No newline at end of file
diff --git a/system/libraries/Encrypt.php b/system/libraries/Encrypt.php
new file mode 100644
index 00000000..3d564f99
--- /dev/null
+++ b/system/libraries/Encrypt.php
@@ -0,0 +1,164 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * The Encrypt library provides two-way encryption of text and binary strings
+ * using the MCrypt extension.
+ * @see http://php.net/mcrypt
+ *
+ * $Id: Encrypt.php 4072 2009-03-13 17:20:38Z jheathco $
+ *
+ * @package Core
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class Encrypt_Core {
+
+ // OS-dependant RAND type to use
+ protected static $rand;
+
+ // Configuration
+ protected $config;
+
+ /**
+ * Returns a singleton instance of Encrypt.
+ *
+ * @param array configuration options
+ * @return Encrypt_Core
+ */
+ public static function instance($config = NULL)
+ {
+ static $instance;
+
+ // Create the singleton
+ empty($instance) and $instance = new Encrypt((array) $config);
+
+ return $instance;
+ }
+
+ /**
+ * Loads encryption configuration and validates the data.
+ *
+ * @param array|string custom configuration or config group name
+ * @throws Kohana_Exception
+ */
+ public function __construct($config = FALSE)
+ {
+ if ( ! defined('MCRYPT_ENCRYPT'))
+ throw new Kohana_Exception('encrypt.requires_mcrypt');
+
+ if (is_string($config))
+ {
+ $name = $config;
+
+ // Test the config group name
+ if (($config = Kohana::config('encryption.'.$config)) === NULL)
+ throw new Kohana_Exception('encrypt.undefined_group', $name);
+ }
+
+ if (is_array($config))
+ {
+ // Append the default configuration options
+ $config += Kohana::config('encryption.default');
+ }
+ else
+ {
+ // Load the default group
+ $config = Kohana::config('encryption.default');
+ }
+
+ if (empty($config['key']))
+ throw new Kohana_Exception('encrypt.no_encryption_key');
+
+ // Find the max length of the key, based on cipher and mode
+ $size = mcrypt_get_key_size($config['cipher'], $config['mode']);
+
+ if (strlen($config['key']) > $size)
+ {
+ // Shorten the key to the maximum size
+ $config['key'] = substr($config['key'], 0, $size);
+ }
+
+ // Find the initialization vector size
+ $config['iv_size'] = mcrypt_get_iv_size($config['cipher'], $config['mode']);
+
+ // Cache the config in the object
+ $this->config = $config;
+
+ Kohana::log('debug', 'Encrypt Library initialized');
+ }
+
+ /**
+ * Encrypts a string and returns an encrypted string that can be decoded.
+ *
+ * @param string data to be encrypted
+ * @return string encrypted data
+ */
+ public function encode($data)
+ {
+ // Set the rand type if it has not already been set
+ if (Encrypt::$rand === NULL)
+ {
+ if (KOHANA_IS_WIN)
+ {
+ // Windows only supports the system random number generator
+ Encrypt::$rand = MCRYPT_RAND;
+ }
+ else
+ {
+ if (defined('MCRYPT_DEV_URANDOM'))
+ {
+ // Use /dev/urandom
+ Encrypt::$rand = MCRYPT_DEV_URANDOM;
+ }
+ elseif (defined('MCRYPT_DEV_RANDOM'))
+ {
+ // Use /dev/random
+ Encrypt::$rand = MCRYPT_DEV_RANDOM;
+ }
+ else
+ {
+ // Use the system random number generator
+ Encrypt::$rand = MCRYPT_RAND;
+ }
+ }
+ }
+
+ if (Encrypt::$rand === MCRYPT_RAND)
+ {
+ // The system random number generator must always be seeded each
+ // time it is used, or it will not produce true random results
+ mt_srand();
+ }
+
+ // Create a random initialization vector of the proper size for the current cipher
+ $iv = mcrypt_create_iv($this->config['iv_size'], Encrypt::$rand);
+
+ // Encrypt the data using the configured options and generated iv
+ $data = mcrypt_encrypt($this->config['cipher'], $this->config['key'], $data, $this->config['mode'], $iv);
+
+ // Use base64 encoding to convert to a string
+ return base64_encode($iv.$data);
+ }
+
+ /**
+ * Decrypts an encoded string back to its original value.
+ *
+ * @param string encoded string to be decrypted
+ * @return string decrypted data
+ */
+ public function decode($data)
+ {
+ // Convert the data back to binary
+ $data = base64_decode($data);
+
+ // Extract the initialization vector from the data
+ $iv = substr($data, 0, $this->config['iv_size']);
+
+ // Remove the iv from the data
+ $data = substr($data, $this->config['iv_size']);
+
+ // Return the decrypted data, trimming the \0 padding bytes from the end of the data
+ return rtrim(mcrypt_decrypt($this->config['cipher'], $this->config['key'], $data, $this->config['mode'], $iv), "\0");
+ }
+
+} // End Encrypt
diff --git a/system/libraries/Event_Observer.php b/system/libraries/Event_Observer.php
new file mode 100644
index 00000000..086c8a23
--- /dev/null
+++ b/system/libraries/Event_Observer.php
@@ -0,0 +1,70 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Kohana event observer. Uses the SPL observer pattern.
+ *
+ * $Id: Event_Observer.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
+ */
+abstract class Event_Observer implements SplObserver {
+
+ // Calling object
+ protected $caller;
+
+ /**
+ * Initializes a new observer and attaches the subject as the caller.
+ *
+ * @param object Event_Subject
+ * @return void
+ */
+ public function __construct(SplSubject $caller)
+ {
+ // Update the caller
+ $this->update($caller);
+ }
+
+ /**
+ * Updates the observer subject with a new caller.
+ *
+ * @chainable
+ * @param object Event_Subject
+ * @return object
+ */
+ public function update(SplSubject $caller)
+ {
+ if ( ! ($caller instanceof Event_Subject))
+ throw new Kohana_Exception('event.invalid_subject', get_class($caller), get_class($this));
+
+ // Update the caller
+ $this->caller = $caller;
+
+ return $this;
+ }
+
+ /**
+ * Detaches this observer from the subject.
+ *
+ * @chainable
+ * @return object
+ */
+ public function remove()
+ {
+ // Detach this observer from the caller
+ $this->caller->detach($this);
+
+ return $this;
+ }
+
+ /**
+ * Notify the observer of a new message. This function must be defined in
+ * all observers and must take exactly one parameter of any type.
+ *
+ * @param mixed message string, object, or array
+ * @return void
+ */
+ abstract public function notify($message);
+
+} // End Event Observer \ No newline at end of file
diff --git a/system/libraries/Event_Subject.php b/system/libraries/Event_Subject.php
new file mode 100644
index 00000000..d1ccc544
--- /dev/null
+++ b/system/libraries/Event_Subject.php
@@ -0,0 +1,67 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Kohana event subject. Uses the SPL observer pattern.
+ *
+ * $Id: Event_Subject.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
+ */
+abstract class Event_Subject implements SplSubject {
+
+ // Attached subject listeners
+ protected $listeners = array();
+
+ /**
+ * Attach an observer to the object.
+ *
+ * @chainable
+ * @param object Event_Observer
+ * @return object
+ */
+ public function attach(SplObserver $obj)
+ {
+ if ( ! ($obj instanceof Event_Observer))
+ throw new Kohana_Exception('eventable.invalid_observer', get_class($obj), get_class($this));
+
+ // Add a new listener
+ $this->listeners[spl_object_hash($obj)] = $obj;
+
+ return $this;
+ }
+
+ /**
+ * Detach an observer from the object.
+ *
+ * @chainable
+ * @param object Event_Observer
+ * @return object
+ */
+ public function detach(SplObserver $obj)
+ {
+ // Remove the listener
+ unset($this->listeners[spl_object_hash($obj)]);
+
+ return $this;
+ }
+
+ /**
+ * Notify all attached observers of a new message.
+ *
+ * @chainable
+ * @param mixed message string, object, or array
+ * @return object
+ */
+ public function notify($message)
+ {
+ foreach ($this->listeners as $obj)
+ {
+ $obj->notify($message);
+ }
+
+ return $this;
+ }
+
+} // End Event Subject \ No newline at end of file
diff --git a/system/libraries/Image.php b/system/libraries/Image.php
new file mode 100644
index 00000000..08c2957c
--- /dev/null
+++ b/system/libraries/Image.php
@@ -0,0 +1,460 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Manipulate images using standard methods such as resize, crop, rotate, etc.
+ * This class must be re-initialized for every image you wish to manipulate.
+ *
+ * $Id: Image.php 4072 2009-03-13 17:20:38Z jheathco $
+ *
+ * @package Image
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class Image_Core {
+
+ // Master Dimension
+ const NONE = 1;
+ const AUTO = 2;
+ const HEIGHT = 3;
+ const WIDTH = 4;
+ // Flip Directions
+ const HORIZONTAL = 5;
+ const VERTICAL = 6;
+
+ // Allowed image types
+ public static $allowed_types = array
+ (
+ IMAGETYPE_GIF => 'gif',
+ IMAGETYPE_JPEG => 'jpg',
+ IMAGETYPE_PNG => 'png',
+ IMAGETYPE_TIFF_II => 'tiff',
+ IMAGETYPE_TIFF_MM => 'tiff',
+ );
+
+ // Driver instance
+ protected $driver;
+
+ // Driver actions
+ protected $actions = array();
+
+ // Reference to the current image filename
+ protected $image = '';
+
+ /**
+ * Creates a new Image instance and returns it.
+ *
+ * @param string filename of image
+ * @param array non-default configurations
+ * @return object
+ */
+ public static function factory($image, $config = NULL)
+ {
+ return new Image($image, $config);
+ }
+
+ /**
+ * Creates a new image editor instance.
+ *
+ * @throws Kohana_Exception
+ * @param string filename of image
+ * @param array non-default configurations
+ * @return void
+ */
+ public function __construct($image, $config = NULL)
+ {
+ static $check;
+
+ // Make the check exactly once
+ ($check === NULL) and $check = function_exists('getimagesize');
+
+ if ($check === FALSE)
+ throw new Kohana_Exception('image.getimagesize_missing');
+
+ // Check to make sure the image exists
+ if ( ! is_file($image))
+ throw new Kohana_Exception('image.file_not_found', $image);
+
+ // Disable error reporting, to prevent PHP warnings
+ $ER = error_reporting(0);
+
+ // Fetch the image size and mime type
+ $image_info = getimagesize($image);
+
+ // Turn on error reporting again
+ error_reporting($ER);
+
+ // Make sure that the image is readable and valid
+ if ( ! is_array($image_info) OR count($image_info) < 3)
+ throw new Kohana_Exception('image.file_unreadable', $image);
+
+ // Check to make sure the image type is allowed
+ if ( ! isset(Image::$allowed_types[$image_info[2]]))
+ throw new Kohana_Exception('image.type_not_allowed', $image);
+
+ // Image has been validated, load it
+ $this->image = array
+ (
+ 'file' => str_replace('\\', '/', realpath($image)),
+ 'width' => $image_info[0],
+ 'height' => $image_info[1],
+ 'type' => $image_info[2],
+ 'ext' => Image::$allowed_types[$image_info[2]],
+ 'mime' => $image_info['mime']
+ );
+
+ // Load configuration
+ $this->config = (array) $config + Kohana::config('image');
+
+ // Set driver class name
+ $driver = 'Image_'.ucfirst($this->config['driver']).'_Driver';
+
+ // Load the driver
+ if ( ! Kohana::auto_load($driver))
+ throw new Kohana_Exception('core.driver_not_found', $this->config['driver'], get_class($this));
+
+ // Initialize the driver
+ $this->driver = new $driver($this->config['params']);
+
+ // Validate the driver
+ if ( ! ($this->driver instanceof Image_Driver))
+ throw new Kohana_Exception('core.driver_implements', $this->config['driver'], get_class($this), 'Image_Driver');
+ }
+
+ /**
+ * Handles retrieval of pre-save image properties
+ *
+ * @param string property name
+ * @return mixed
+ */
+ public function __get($property)
+ {
+ if (isset($this->image[$property]))
+ {
+ return $this->image[$property];
+ }
+ else
+ {
+ throw new Kohana_Exception('core.invalid_property', $property, get_class($this));
+ }
+ }
+
+ /**
+ * Resize an image to a specific width and height. By default, Kohana will
+ * maintain the aspect ratio using the width as the master dimension. If you
+ * wish to use height as master dim, set $image->master_dim = Image::HEIGHT
+ * This method is chainable.
+ *
+ * @throws Kohana_Exception
+ * @param integer width
+ * @param integer height
+ * @param integer one of: Image::NONE, Image::AUTO, Image::WIDTH, Image::HEIGHT
+ * @return object
+ */
+ public function resize($width, $height, $master = NULL)
+ {
+ if ( ! $this->valid_size('width', $width))
+ throw new Kohana_Exception('image.invalid_width', $width);
+
+ if ( ! $this->valid_size('height', $height))
+ throw new Kohana_Exception('image.invalid_height', $height);
+
+ if (empty($width) AND empty($height))
+ throw new Kohana_Exception('image.invalid_dimensions', __FUNCTION__);
+
+ if ($master === NULL)
+ {
+ // Maintain the aspect ratio by default
+ $master = Image::AUTO;
+ }
+ elseif ( ! $this->valid_size('master', $master))
+ throw new Kohana_Exception('image.invalid_master');
+
+ $this->actions['resize'] = array
+ (
+ 'width' => $width,
+ 'height' => $height,
+ 'master' => $master,
+ );
+
+ return $this;
+ }
+
+ /**
+ * Crop an image to a specific width and height. You may also set the top
+ * and left offset.
+ * This method is chainable.
+ *
+ * @throws Kohana_Exception
+ * @param integer width
+ * @param integer height
+ * @param integer top offset, pixel value or one of: top, center, bottom
+ * @param integer left offset, pixel value or one of: left, center, right
+ * @return object
+ */
+ public function crop($width, $height, $top = 'center', $left = 'center')
+ {
+ if ( ! $this->valid_size('width', $width))
+ throw new Kohana_Exception('image.invalid_width', $width);
+
+ if ( ! $this->valid_size('height', $height))
+ throw new Kohana_Exception('image.invalid_height', $height);
+
+ if ( ! $this->valid_size('top', $top))
+ throw new Kohana_Exception('image.invalid_top', $top);
+
+ if ( ! $this->valid_size('left', $left))
+ throw new Kohana_Exception('image.invalid_left', $left);
+
+ if (empty($width) AND empty($height))
+ throw new Kohana_Exception('image.invalid_dimensions', __FUNCTION__);
+
+ $this->actions['crop'] = array
+ (
+ 'width' => $width,
+ 'height' => $height,
+ 'top' => $top,
+ 'left' => $left,
+ );
+
+ return $this;
+ }
+
+ /**
+ * Allows rotation of an image by 180 degrees clockwise or counter clockwise.
+ *
+ * @param integer degrees
+ * @return object
+ */
+ public function rotate($degrees)
+ {
+ $degrees = (int) $degrees;
+
+ if ($degrees > 180)
+ {
+ do
+ {
+ // Keep subtracting full circles until the degrees have normalized
+ $degrees -= 360;
+ }
+ while($degrees > 180);
+ }
+
+ if ($degrees < -180)
+ {
+ do
+ {
+ // Keep adding full circles until the degrees have normalized
+ $degrees += 360;
+ }
+ while($degrees < -180);
+ }
+
+ $this->actions['rotate'] = $degrees;
+
+ return $this;
+ }
+
+ /**
+ * Overlay a second image on top of this one.
+ *
+ * @throws Kohana_Exception
+ * @param string $overlay_file path to an image file
+ * @param integer $x x offset for the overlay
+ * @param integer $y y offset for the overlay
+ * @param integer $transparency transparency percent
+ */
+ public function composite($overlay_file, $x, $y, $transparency)
+ {
+ $image_info = getimagesize($overlay_file);
+
+ // Check to make sure the image type is allowed
+ if ( ! isset(Image::$allowed_types[$image_info[2]]))
+ throw new Kohana_Exception('image.type_not_allowed', $overlay_file);
+
+ $this->actions['composite'] = array
+ (
+ 'overlay_file' => $overlay_file,
+ 'mime' => $image_info['mime'],
+ 'x' => $x,
+ 'y' => $y,
+ 'transparency' => $transparency
+ );
+
+ return $this;
+ }
+
+ /**
+ * Flip an image horizontally or vertically.
+ *
+ * @throws Kohana_Exception
+ * @param integer direction
+ * @return object
+ */
+ public function flip($direction)
+ {
+ if ($direction !== Image::HORIZONTAL AND $direction !== Image::VERTICAL)
+ throw new Kohana_Exception('image.invalid_flip');
+
+ $this->actions['flip'] = $direction;
+
+ return $this;
+ }
+
+ /**
+ * Change the quality of an image.
+ *
+ * @param integer quality as a percentage
+ * @return object
+ */
+ public function quality($amount)
+ {
+ $this->actions['quality'] = max(1, min($amount, 100));
+
+ return $this;
+ }
+
+ /**
+ * Sharpen an image.
+ *
+ * @param integer amount to sharpen, usually ~20 is ideal
+ * @return object
+ */
+ public function sharpen($amount)
+ {
+ $this->actions['sharpen'] = max(1, min($amount, 100));
+
+ return $this;
+ }
+
+ /**
+ * Save the image to a new image or overwrite this image.
+ *
+ * @throws Kohana_Exception
+ * @param string new image filename
+ * @param integer permissions for new image
+ * @param boolean keep or discard image process actions
+ * @return object
+ */
+ public function save($new_image = FALSE, $chmod = 0644, $keep_actions = FALSE)
+ {
+ // If no new image is defined, use the current image
+ empty($new_image) and $new_image = $this->image['file'];
+
+ // Separate the directory and filename
+ $dir = pathinfo($new_image, PATHINFO_DIRNAME);
+ $file = pathinfo($new_image, PATHINFO_BASENAME);
+
+ // Normalize the path
+ $dir = str_replace('\\', '/', realpath($dir)).'/';
+
+ if ( ! is_writable($dir))
+ throw new Kohana_Exception('image.directory_unwritable', $dir);
+
+ if ($status = $this->driver->process($this->image, $this->actions, $dir, $file))
+ {
+ if ($chmod !== FALSE)
+ {
+ // Set permissions
+ chmod($new_image, $chmod);
+ }
+ }
+
+ // Reset actions. Subsequent save() or render() will not apply previous actions.
+ if ($keep_actions === FALSE)
+ $this->actions = array();
+
+ return $status;
+ }
+
+ /**
+ * Output the image to the browser.
+ *
+ * @param boolean keep or discard image process actions
+ * @return object
+ */
+ public function render($keep_actions = FALSE)
+ {
+ $new_image = $this->image['file'];
+
+ // Separate the directory and filename
+ $dir = pathinfo($new_image, PATHINFO_DIRNAME);
+ $file = pathinfo($new_image, PATHINFO_BASENAME);
+
+ // Normalize the path
+ $dir = str_replace('\\', '/', realpath($dir)).'/';
+
+ // Process the image with the driver
+ $status = $this->driver->process($this->image, $this->actions, $dir, $file, $render = TRUE);
+
+ // Reset actions. Subsequent save() or render() will not apply previous actions.
+ if ($keep_actions === FALSE)
+ $this->actions = array();
+
+ return $status;
+ }
+
+ /**
+ * Sanitize a given value type.
+ *
+ * @param string type of property
+ * @param mixed property value
+ * @return boolean
+ */
+ protected function valid_size($type, & $value)
+ {
+ if (is_null($value))
+ return TRUE;
+
+ if ( ! is_scalar($value))
+ return FALSE;
+
+ switch ($type)
+ {
+ case 'width':
+ case 'height':
+ if (is_string($value) AND ! ctype_digit($value))
+ {
+ // Only numbers and percent signs
+ if ( ! preg_match('/^[0-9]++%$/D', $value))
+ return FALSE;
+ }
+ else
+ {
+ $value = (int) $value;
+ }
+ break;
+ case 'top':
+ if (is_string($value) AND ! ctype_digit($value))
+ {
+ if ( ! in_array($value, array('top', 'bottom', 'center')))
+ return FALSE;
+ }
+ else
+ {
+ $value = (int) $value;
+ }
+ break;
+ case 'left':
+ if (is_string($value) AND ! ctype_digit($value))
+ {
+ if ( ! in_array($value, array('left', 'right', 'center')))
+ return FALSE;
+ }
+ else
+ {
+ $value = (int) $value;
+ }
+ break;
+ case 'master':
+ if ($value !== Image::NONE AND
+ $value !== Image::AUTO AND
+ $value !== Image::WIDTH AND
+ $value !== Image::HEIGHT)
+ return FALSE;
+ break;
+ }
+
+ return TRUE;
+ }
+
+} // End Image \ No newline at end of file
diff --git a/system/libraries/Input.php b/system/libraries/Input.php
new file mode 100644
index 00000000..0e23c800
--- /dev/null
+++ b/system/libraries/Input.php
@@ -0,0 +1,452 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Input library.
+ *
+ * $Id: Input.php 4346 2009-05-11 17:08:15Z zombor $
+ *
+ * @package Core
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class Input_Core {
+
+ // Enable or disable automatic XSS cleaning
+ protected $use_xss_clean = FALSE;
+
+ // Are magic quotes enabled?
+ protected $magic_quotes_gpc = FALSE;
+
+ // IP address of current user
+ public $ip_address;
+
+ // Input singleton
+ protected static $instance;
+
+ /**
+ * Retrieve a singleton instance of Input. This will always be the first
+ * created instance of this class.
+ *
+ * @return object
+ */
+ public static function instance()
+ {
+ if (Input::$instance === NULL)
+ {
+ // Create a new instance
+ return new Input;
+ }
+
+ return Input::$instance;
+ }
+
+ /**
+ * Sanitizes global GET, POST and COOKIE data. Also takes care of
+ * magic_quotes and register_globals, if they have been enabled.
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ // Use XSS clean?
+ $this->use_xss_clean = (bool) Kohana::config('core.global_xss_filtering');
+
+ if (Input::$instance === NULL)
+ {
+ // magic_quotes_runtime is enabled
+ if (get_magic_quotes_runtime())
+ {
+ set_magic_quotes_runtime(0);
+ Kohana::log('debug', 'Disable magic_quotes_runtime! It is evil and deprecated: http://php.net/magic_quotes');
+ }
+
+ // magic_quotes_gpc is enabled
+ if (get_magic_quotes_gpc())
+ {
+ $this->magic_quotes_gpc = TRUE;
+ Kohana::log('debug', 'Disable magic_quotes_gpc! It is evil and deprecated: http://php.net/magic_quotes');
+ }
+
+ // register_globals is enabled
+ if (ini_get('register_globals'))
+ {
+ if (isset($_REQUEST['GLOBALS']))
+ {
+ // Prevent GLOBALS override attacks
+ exit('Global variable overload attack.');
+ }
+
+ // Destroy the REQUEST global
+ $_REQUEST = array();
+
+ // These globals are standard and should not be removed
+ $preserve = array('GLOBALS', '_REQUEST', '_GET', '_POST', '_FILES', '_COOKIE', '_SERVER', '_ENV', '_SESSION');
+
+ // This loop has the same effect as disabling register_globals
+ foreach (array_diff(array_keys($GLOBALS), $preserve) as $key)
+ {
+ global $$key;
+ $$key = NULL;
+
+ // Unset the global variable
+ unset($GLOBALS[$key], $$key);
+ }
+
+ // Warn the developer about register globals
+ Kohana::log('debug', 'Disable register_globals! It is evil and deprecated: http://php.net/register_globals');
+ }
+
+ if (is_array($_GET))
+ {
+ foreach ($_GET as $key => $val)
+ {
+ // Sanitize $_GET
+ $_GET[$this->clean_input_keys($key)] = $this->clean_input_data($val);
+ }
+ }
+ else
+ {
+ $_GET = array();
+ }
+
+ if (is_array($_POST))
+ {
+ foreach ($_POST as $key => $val)
+ {
+ // Sanitize $_POST
+ $_POST[$this->clean_input_keys($key)] = $this->clean_input_data($val);
+ }
+ }
+ else
+ {
+ $_POST = array();
+ }
+
+ if (is_array($_COOKIE))
+ {
+ foreach ($_COOKIE as $key => $val)
+ {
+ // Ignore special attributes in RFC2109 compliant cookies
+ if ($key == '$Version' OR $key == '$Path' OR $key == '$Domain')
+ continue;
+
+ // Sanitize $_COOKIE
+ $_COOKIE[$this->clean_input_keys($key)] = $this->clean_input_data($val);
+ }
+ }
+ else
+ {
+ $_COOKIE = array();
+ }
+
+ // Create a singleton
+ Input::$instance = $this;
+
+ Kohana::log('debug', 'Global GET, POST and COOKIE data sanitized');
+ }
+ }
+
+ /**
+ * Fetch an item from the $_GET array.
+ *
+ * @param string key to find
+ * @param mixed default value
+ * @param boolean XSS clean the value
+ * @return mixed
+ */
+ public function get($key = array(), $default = NULL, $xss_clean = FALSE)
+ {
+ return $this->search_array($_GET, $key, $default, $xss_clean);
+ }
+
+ /**
+ * Fetch an item from the $_POST array.
+ *
+ * @param string key to find
+ * @param mixed default value
+ * @param boolean XSS clean the value
+ * @return mixed
+ */
+ public function post($key = array(), $default = NULL, $xss_clean = FALSE)
+ {
+ return $this->search_array($_POST, $key, $default, $xss_clean);
+ }
+
+ /**
+ * Fetch an item from the $_COOKIE array.
+ *
+ * @param string key to find
+ * @param mixed default value
+ * @param boolean XSS clean the value
+ * @return mixed
+ */
+ public function cookie($key = array(), $default = NULL, $xss_clean = FALSE)
+ {
+ return $this->search_array($_COOKIE, $key, $default, $xss_clean);
+ }
+
+ /**
+ * Fetch an item from the $_SERVER array.
+ *
+ * @param string key to find
+ * @param mixed default value
+ * @param boolean XSS clean the value
+ * @return mixed
+ */
+ public function server($key = array(), $default = NULL, $xss_clean = FALSE)
+ {
+ return $this->search_array($_SERVER, $key, $default, $xss_clean);
+ }
+
+ /**
+ * Fetch an item from a global array.
+ *
+ * @param array array to search
+ * @param string key to find
+ * @param mixed default value
+ * @param boolean XSS clean the value
+ * @return mixed
+ */
+ protected function search_array($array, $key, $default = NULL, $xss_clean = FALSE)
+ {
+ if ($key === array())
+ return $array;
+
+ if ( ! isset($array[$key]))
+ return $default;
+
+ // Get the value
+ $value = $array[$key];
+
+ if ($this->use_xss_clean === FALSE AND $xss_clean === TRUE)
+ {
+ // XSS clean the value
+ $value = $this->xss_clean($value);
+ }
+
+ return $value;
+ }
+
+ /**
+ * Fetch the IP Address.
+ *
+ * @return string
+ */
+ public function ip_address()
+ {
+ if ($this->ip_address !== NULL)
+ return $this->ip_address;
+
+ // Server keys that could contain the client IP address
+ $keys = array('HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'REMOTE_ADDR');
+
+ foreach ($keys as $key)
+ {
+ if ($ip = $this->server($key))
+ {
+ $this->ip_address = $ip;
+
+ // An IP address has been found
+ break;
+ }
+ }
+
+ if ($comma = strrpos($this->ip_address, ',') !== FALSE)
+ {
+ $this->ip_address = substr($this->ip_address, $comma + 1);
+ }
+
+ if ( ! valid::ip($this->ip_address))
+ {
+ // Use an empty IP
+ $this->ip_address = '0.0.0.0';
+ }
+
+ return $this->ip_address;
+ }
+
+ /**
+ * Clean cross site scripting exploits from string.
+ * HTMLPurifier may be used if installed, otherwise defaults to built in method.
+ * Note - This function should only be used to deal with data upon submission.
+ * It's not something that should be used for general runtime processing
+ * since it requires a fair amount of processing overhead.
+ *
+ * @param string data to clean
+ * @param string xss_clean method to use ('htmlpurifier' or defaults to built-in method)
+ * @return string
+ */
+ public function xss_clean($data, $tool = NULL)
+ {
+ if ($tool === NULL)
+ {
+ // Use the default tool
+ $tool = Kohana::config('core.global_xss_filtering');
+ }
+
+ if (is_array($data))
+ {
+ foreach ($data as $key => $val)
+ {
+ $data[$key] = $this->xss_clean($val, $tool);
+ }
+
+ return $data;
+ }
+
+ // Do not clean empty strings
+ if (trim($data) === '')
+ return $data;
+
+ if ($tool === TRUE)
+ {
+ // NOTE: This is necessary because switch is NOT type-sensative!
+ $tool = 'default';
+ }
+
+ switch ($tool)
+ {
+ case 'htmlpurifier':
+ /**
+ * @todo License should go here, http://htmlpurifier.org/
+ */
+ if ( ! class_exists('HTMLPurifier_Config', FALSE))
+ {
+ // Load HTMLPurifier
+ require Kohana::find_file('vendor', 'htmlpurifier/HTMLPurifier.auto', TRUE);
+ require 'HTMLPurifier.func.php';
+ }
+
+ // Set configuration
+ $config = HTMLPurifier_Config::createDefault();
+ $config->set('HTML', 'TidyLevel', 'none'); // Only XSS cleaning now
+
+ // Run HTMLPurifier
+ $data = HTMLPurifier($data, $config);
+ break;
+ default:
+ // http://svn.bitflux.ch/repos/public/popoon/trunk/classes/externalinput.php
+ // +----------------------------------------------------------------------+
+ // | Copyright (c) 2001-2006 Bitflux GmbH |
+ // +----------------------------------------------------------------------+
+ // | Licensed under the Apache License, Version 2.0 (the "License"); |
+ // | you may not use this file except in compliance with the License. |
+ // | You may obtain a copy of the License at |
+ // | http://www.apache.org/licenses/LICENSE-2.0 |
+ // | Unless required by applicable law or agreed to in writing, software |
+ // | distributed under the License is distributed on an "AS IS" BASIS, |
+ // | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
+ // | implied. See the License for the specific language governing |
+ // | permissions and limitations under the License. |
+ // +----------------------------------------------------------------------+
+ // | Author: Christian Stocker <chregu@bitflux.ch> |
+ // +----------------------------------------------------------------------+
+ //
+ // Kohana Modifications:
+ // * Changed double quotes to single quotes, changed indenting and spacing
+ // * Removed magic_quotes stuff
+ // * Increased regex readability:
+ // * Used delimeters that aren't found in the pattern
+ // * Removed all unneeded escapes
+ // * Deleted U modifiers and swapped greediness where needed
+ // * Increased regex speed:
+ // * Made capturing parentheses non-capturing where possible
+ // * Removed parentheses where possible
+ // * Split up alternation alternatives
+ // * Made some quantifiers possessive
+
+ // Fix &entity\n;
+ $data = str_replace(array('&amp;','&lt;','&gt;'), array('&amp;amp;','&amp;lt;','&amp;gt;'), $data);
+ $data = preg_replace('/(&#*\w+)[\x00-\x20]+;/u', '$1;', $data);
+ $data = preg_replace('/(&#x*[0-9A-F]+);*/iu', '$1;', $data);
+ $data = html_entity_decode($data, ENT_COMPAT, 'UTF-8');
+
+ // Remove any attribute starting with "on" or xmlns
+ $data = preg_replace('#(<[^>]+?[\x00-\x20"\'])(?:on|xmlns)[^>]*+>#iu', '$1>', $data);
+
+ // Remove javascript: and vbscript: protocols
+ $data = preg_replace('#([a-z]*)[\x00-\x20]*=[\x00-\x20]*([`\'"]*)[\x00-\x20]*j[\x00-\x20]*a[\x00-\x20]*v[\x00-\x20]*a[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2nojavascript...', $data);
+ $data = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*v[\x00-\x20]*b[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2novbscript...', $data);
+ $data = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*-moz-binding[\x00-\x20]*:#u', '$1=$2nomozbinding...', $data);
+
+ // Only works in IE: <span style="width: expression(alert('Ping!'));"></span>
+ $data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?expression[\x00-\x20]*\([^>]*+>#i', '$1>', $data);
+ $data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?behaviour[\x00-\x20]*\([^>]*+>#i', '$1>', $data);
+ $data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:*[^>]*+>#iu', '$1>', $data);
+
+ // Remove namespaced elements (we do not need them)
+ $data = preg_replace('#</*\w+:\w[^>]*+>#i', '', $data);
+
+ do
+ {
+ // Remove really unwanted tags
+ $old_data = $data;
+ $data = preg_replace('#</*(?:applet|b(?:ase|gsound|link)|embed|frame(?:set)?|i(?:frame|layer)|l(?:ayer|ink)|meta|object|s(?:cript|tyle)|title|xml)[^>]*+>#i', '', $data);
+ }
+ while ($old_data !== $data);
+ break;
+ }
+
+ return $data;
+ }
+
+ /**
+ * This is a helper method. It enforces W3C specifications for allowed
+ * key name strings, to prevent malicious exploitation.
+ *
+ * @param string string to clean
+ * @return string
+ */
+ public function clean_input_keys($str)
+ {
+ $chars = PCRE_UNICODE_PROPERTIES ? '\pL' : 'a-zA-Z';
+
+ if ( ! preg_match('#^['.$chars.'0-9:_.-]++$#uD', $str))
+ {
+ exit('Disallowed key characters in global data.');
+ }
+
+ return $str;
+ }
+
+ /**
+ * This is a helper method. It escapes data and forces all newline
+ * characters to "\n".
+ *
+ * @param unknown_type string to clean
+ * @return string
+ */
+ public function clean_input_data($str)
+ {
+ if (is_array($str))
+ {
+ $new_array = array();
+ foreach ($str as $key => $val)
+ {
+ // Recursion!
+ $new_array[$this->clean_input_keys($key)] = $this->clean_input_data($val);
+ }
+ return $new_array;
+ }
+
+ if ($this->magic_quotes_gpc === TRUE)
+ {
+ // Remove annoying magic quotes
+ $str = stripslashes($str);
+ }
+
+ if ($this->use_xss_clean === TRUE)
+ {
+ $str = $this->xss_clean($str);
+ }
+
+ if (strpos($str, "\r") !== FALSE)
+ {
+ // Standardize newlines
+ $str = str_replace(array("\r\n", "\r"), "\n", $str);
+ }
+
+ return $str;
+ }
+
+} // End Input Class
diff --git a/system/libraries/Model.php b/system/libraries/Model.php
new file mode 100644
index 00000000..0c9fd8d6
--- /dev/null
+++ b/system/libraries/Model.php
@@ -0,0 +1,31 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Model base class.
+ *
+ * $Id: Model.php 4007 2009-02-20 01:54:00Z jheathco $
+ *
+ * @package Core
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class Model_Core {
+
+ // Database object
+ protected $db = 'default';
+
+ /**
+ * Loads the database instance, if the database is not already loaded.
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ if ( ! is_object($this->db))
+ {
+ // Load the default database
+ $this->db = Database::instance($this->db);
+ }
+ }
+
+} // End Model \ No newline at end of file
diff --git a/system/libraries/ORM.php b/system/libraries/ORM.php
new file mode 100644
index 00000000..c1048604
--- /dev/null
+++ b/system/libraries/ORM.php
@@ -0,0 +1,1431 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * [Object Relational Mapping][ref-orm] (ORM) is a method of abstracting database
+ * access to standard PHP calls. All table rows are represented as model objects,
+ * with object properties representing row data. ORM in Kohana generally follows
+ * the [Active Record][ref-act] pattern.
+ *
+ * [ref-orm]: http://wikipedia.org/wiki/Object-relational_mapping
+ * [ref-act]: http://wikipedia.org/wiki/Active_record
+ *
+ * $Id: ORM.php 4354 2009-05-15 16:51:37Z kiall $
+ *
+ * @package ORM
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class ORM_Core {
+
+ // Current relationships
+ protected $has_one = array();
+ protected $belongs_to = array();
+ protected $has_many = array();
+ protected $has_and_belongs_to_many = array();
+
+ // Relationships that should always be joined
+ protected $load_with = array();
+
+ // Current object
+ protected $object = array();
+ protected $changed = array();
+ protected $related = array();
+ protected $loaded = FALSE;
+ protected $saved = FALSE;
+ protected $sorting;
+
+ // Related objects
+ protected $object_relations = array();
+ protected $changed_relations = array();
+
+ // Model table information
+ protected $object_name;
+ protected $object_plural;
+ protected $table_name;
+ protected $table_columns;
+ protected $ignored_columns;
+
+ // Table primary key and value
+ protected $primary_key = 'id';
+ protected $primary_val = 'name';
+
+ // Array of foreign key name overloads
+ protected $foreign_key = array();
+
+ // Model configuration
+ protected $table_names_plural = TRUE;
+ protected $reload_on_wakeup = TRUE;
+
+ // Database configuration
+ protected $db = 'default';
+ protected $db_applied = array();
+
+ // With calls already applied
+ protected $with_applied = array();
+
+ // Stores column information for ORM models
+ protected static $column_cache = array();
+
+ /**
+ * Creates and returns a new model.
+ *
+ * @chainable
+ * @param string model name
+ * @param mixed parameter for find()
+ * @return ORM
+ */
+ public static function factory($model, $id = NULL)
+ {
+ // Set class name
+ $model = ucfirst($model).'_Model';
+
+ return new $model($id);
+ }
+
+ /**
+ * Prepares the model database connection and loads the object.
+ *
+ * @param mixed parameter for find or object to load
+ * @return void
+ */
+ public function __construct($id = NULL)
+ {
+ // Set the object name and plural name
+ $this->object_name = strtolower(substr(get_class($this), 0, -6));
+ $this->object_plural = inflector::plural($this->object_name);
+
+ if (!isset($this->sorting))
+ {
+ // Default sorting
+ $this->sorting = array($this->primary_key => 'asc');
+ }
+
+ // Initialize database
+ $this->__initialize();
+
+ // Clear the object
+ $this->clear();
+
+ if (is_object($id))
+ {
+ // Load an object
+ $this->load_values((array) $id);
+ }
+ elseif (!empty($id))
+ {
+ // Find an object
+ $this->find($id);
+ }
+ }
+
+ /**
+ * Prepares the model database connection, determines the table name,
+ * and loads column information.
+ *
+ * @return void
+ */
+ public function __initialize()
+ {
+ if ( ! is_object($this->db))
+ {
+ // Get database instance
+ $this->db = Database::instance($this->db);
+ }
+
+ if (empty($this->table_name))
+ {
+ // Table name is the same as the object name
+ $this->table_name = $this->object_name;
+
+ if ($this->table_names_plural === TRUE)
+ {
+ // Make the table name plural
+ $this->table_name = inflector::plural($this->table_name);
+ }
+ }
+
+ if (is_array($this->ignored_columns))
+ {
+ // Make the ignored columns mirrored = mirrored
+ $this->ignored_columns = array_combine($this->ignored_columns, $this->ignored_columns);
+ }
+
+ // Load column information
+ $this->reload_columns();
+ }
+
+ /**
+ * Allows serialization of only the object data and state, to prevent
+ * "stale" objects being unserialized, which also requires less memory.
+ *
+ * @return array
+ */
+ public function __sleep()
+ {
+ // Store only information about the object
+ return array('object_name', 'object', 'changed', 'loaded', 'saved', 'sorting');
+ }
+
+ /**
+ * Prepares the database connection and reloads the object.
+ *
+ * @return void
+ */
+ public function __wakeup()
+ {
+ // Initialize database
+ $this->__initialize();
+
+ if ($this->reload_on_wakeup === TRUE)
+ {
+ // Reload the object
+ $this->reload();
+ }
+ }
+
+ /**
+ * Handles pass-through to database methods. Calls to query methods
+ * (query, get, insert, update) are not allowed. Query builder methods
+ * are chainable.
+ *
+ * @param string method name
+ * @param array method arguments
+ * @return mixed
+ */
+ public function __call($method, array $args)
+ {
+ if (method_exists($this->db, $method))
+ {
+ if (in_array($method, array('query', 'get', 'insert', 'update', 'delete')))
+ throw new Kohana_Exception('orm.query_methods_not_allowed');
+
+ // Method has been applied to the database
+ $this->db_applied[$method] = $method;
+
+ // Number of arguments passed
+ $num_args = count($args);
+
+ if ($method === 'select' AND $num_args > 3)
+ {
+ // Call select() manually to avoid call_user_func_array
+ $this->db->select($args);
+ }
+ else
+ {
+ // We use switch here to manually call the database methods. This is
+ // done for speed: call_user_func_array can take over 300% longer to
+ // make calls. Most database methods are 4 arguments or less, so this
+ // avoids almost any calls to call_user_func_array.
+
+ switch ($num_args)
+ {
+ case 0:
+ if (in_array($method, array('open_paren', 'close_paren', 'enable_cache', 'disable_cache')))
+ {
+ // Should return ORM, not Database
+ $this->db->$method();
+ }
+ else
+ {
+ // Support for things like reset_select, reset_write, list_tables
+ return $this->db->$method();
+ }
+ break;
+ case 1:
+ $this->db->$method($args[0]);
+ break;
+ case 2:
+ $this->db->$method($args[0], $args[1]);
+ break;
+ case 3:
+ $this->db->$method($args[0], $args[1], $args[2]);
+ break;
+ case 4:
+ $this->db->$method($args[0], $args[1], $args[2], $args[3]);
+ break;
+ default:
+ // Here comes the snail...
+ call_user_func_array(array($this->db, $method), $args);
+ break;
+ }
+ }
+
+ return $this;
+ }
+ else
+ {
+ throw new Kohana_Exception('core.invalid_method', $method, get_class($this));
+ }
+ }
+
+ /**
+ * Handles retrieval of all model values, relationships, and metadata.
+ *
+ * @param string column name
+ * @return mixed
+ */
+ public function __get($column)
+ {
+ if (array_key_exists($column, $this->object))
+ {
+ return $this->object[$column];
+ }
+ elseif (isset($this->related[$column]))
+ {
+ return $this->related[$column];
+ }
+ elseif ($column === 'primary_key_value')
+ {
+ return $this->object[$this->primary_key];
+ }
+ elseif ($model = $this->related_object($column))
+ {
+ // This handles the has_one and belongs_to relationships
+
+ if (in_array($model->object_name, $this->belongs_to) OR ! array_key_exists($this->foreign_key($column), $model->object))
+ {
+ // Foreign key lies in this table (this model belongs_to target model) OR an invalid has_one relationship
+ $where = array($model->table_name.'.'.$model->primary_key => $this->object[$this->foreign_key($column)]);
+ }
+ else
+ {
+ // Foreign key lies in the target table (this model has_one target model)
+ $where = array($this->foreign_key($column, $model->table_name) => $this->primary_key_value);
+ }
+
+ // one<>alias:one relationship
+ return $this->related[$column] = $model->find($where);
+ }
+ elseif (isset($this->has_many[$column]))
+ {
+ // Load the "middle" model
+ $through = ORM::factory(inflector::singular($this->has_many[$column]));
+
+ // Load the "end" model
+ $model = ORM::factory(inflector::singular($column));
+
+ // Join ON target model's primary key set to 'through' model's foreign key
+ // User-defined foreign keys must be defined in the 'through' model
+ $join_table = $through->table_name;
+ $join_col1 = $through->foreign_key($model->object_name, $join_table);
+ $join_col2 = $model->table_name.'.'.$model->primary_key;
+
+ // one<>alias:many relationship
+ return $this->related[$column] = $model
+ ->join($join_table, $join_col1, $join_col2)
+ ->where($through->foreign_key($this->object_name, $join_table), $this->object[$this->primary_key])
+ ->find_all();
+ }
+ elseif (in_array($column, $this->has_many))
+ {
+ // one<>many relationship
+ $model = ORM::factory(inflector::singular($column));
+ return $this->related[$column] = $model
+ ->where($this->foreign_key($column, $model->table_name), $this->object[$this->primary_key])
+ ->find_all();
+ }
+ elseif (in_array($column, $this->has_and_belongs_to_many))
+ {
+ // Load the remote model, always singular
+ $model = ORM::factory(inflector::singular($column));
+
+ if ($this->has($model, TRUE))
+ {
+ // many<>many relationship
+ return $this->related[$column] = $model
+ ->in($model->table_name.'.'.$model->primary_key, $this->changed_relations[$column])
+ ->find_all();
+ }
+ else
+ {
+ // empty many<>many relationship
+ return $this->related[$column] = $model
+ ->where($model->table_name.'.'.$model->primary_key, NULL)
+ ->find_all();
+ }
+ }
+ elseif (isset($this->ignored_columns[$column]))
+ {
+ return NULL;
+ }
+ elseif (in_array($column, array
+ (
+ 'object_name', 'object_plural', // Object
+ 'primary_key', 'primary_val', 'table_name', 'table_columns', // Table
+ 'loaded', 'saved', // Status
+ 'has_one', 'belongs_to', 'has_many', 'has_and_belongs_to_many', 'load_with' // Relationships
+ )))
+ {
+ // Model meta information
+ return $this->$column;
+ }
+ else
+ {
+ throw new Kohana_Exception('core.invalid_property', $column, get_class($this));
+ }
+ }
+
+ /**
+ * Handles setting of all model values, and tracks changes between values.
+ *
+ * @param string column name
+ * @param mixed column value
+ * @return void
+ */
+ public function __set($column, $value)
+ {
+ if (isset($this->ignored_columns[$column]))
+ {
+ return NULL;
+ }
+ elseif (isset($this->object[$column]) OR array_key_exists($column, $this->object))
+ {
+ if (isset($this->table_columns[$column]))
+ {
+ // Data has changed
+ $this->changed[$column] = $column;
+
+ // Object is no longer saved
+ $this->saved = FALSE;
+ }
+
+ $this->object[$column] = $this->load_type($column, $value);
+ }
+ elseif (in_array($column, $this->has_and_belongs_to_many) AND is_array($value))
+ {
+ // Load relations
+ $model = ORM::factory(inflector::singular($column));
+
+ if ( ! isset($this->object_relations[$column]))
+ {
+ // Load relations
+ $this->has($model);
+ }
+
+ // Change the relationships
+ $this->changed_relations[$column] = $value;
+
+ if (isset($this->related[$column]))
+ {
+ // Force a reload of the relationships
+ unset($this->related[$column]);
+ }
+ }
+ else
+ {
+ throw new Kohana_Exception('core.invalid_property', $column, get_class($this));
+ }
+ }
+
+ /**
+ * Checks if object data is set.
+ *
+ * @param string column name
+ * @return boolean
+ */
+ public function __isset($column)
+ {
+ return (isset($this->object[$column]) OR isset($this->related[$column]));
+ }
+
+ /**
+ * Unsets object data.
+ *
+ * @param string column name
+ * @return void
+ */
+ public function __unset($column)
+ {
+ unset($this->object[$column], $this->changed[$column], $this->related[$column]);
+ }
+
+ /**
+ * Displays the primary key of a model when it is converted to a string.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return (string) $this->object[$this->primary_key];
+ }
+
+ /**
+ * Returns the values of this object as an array.
+ *
+ * @return array
+ */
+ public function as_array()
+ {
+ $object = array();
+
+ foreach ($this->object as $key => $val)
+ {
+ // Reconstruct the array (calls __get)
+ $object[$key] = $this->$key;
+ }
+
+ return $object;
+ }
+
+ /**
+ * Binds another one-to-one object to this model. One-to-one objects
+ * can be nested using 'object1:object2' syntax
+ *
+ * @param string $target_path
+ * @return void
+ */
+ public function with($target_path)
+ {
+ if (isset($this->with_applied[$target_path]))
+ {
+ // Don't join anything already joined
+ return $this;
+ }
+
+ // Split object parts
+ $objects = explode(':', $target_path);
+ $target = $this;
+ foreach ($objects as $object)
+ {
+ // Go down the line of objects to find the given target
+ $parent = $target;
+ $target = $parent->related_object($object);
+
+ if ( ! $target)
+ {
+ // Can't find related object
+ return $this;
+ }
+ }
+
+ $target_name = $object;
+
+ // Pop-off top object to get the parent object (user:photo:tag becomes user:photo - the parent table prefix)
+ array_pop($objects);
+ $parent_path = implode(':', $objects);
+
+ if (empty($parent_path))
+ {
+ // Use this table name itself for the parent object
+ $parent_path = $this->table_name;
+ }
+ else
+ {
+ if( ! isset($this->with_applied[$parent_path]))
+ {
+ // If the parent object hasn't been joined yet, do it first (otherwise LEFT JOINs fail)
+ $this->with($parent_path);
+ }
+ }
+
+ // Add to with_applied to prevent duplicate joins
+ $this->with_applied[$target_path] = TRUE;
+
+ // Use the keys of the empty object to determine the columns
+ $select = array_keys($target->object);
+ foreach ($select as $i => $column)
+ {
+ // Add the prefix so that load_result can determine the relationship
+ $select[$i] = $target_path.'.'.$column.' AS '.$target_path.':'.$column;
+ }
+
+
+ // Select all of the prefixed keys in the object
+ $this->db->select($select);
+
+ if (in_array($target->object_name, $parent->belongs_to) OR ! isset($target->object[$parent->foreign_key($target_name)]))
+ {
+ // Parent belongs_to target, use target's primary key as join column
+ $join_col1 = $target->foreign_key(TRUE, $target_path);
+ $join_col2 = $parent->foreign_key($target_name, $parent_path);
+ }
+ else
+ {
+ // Parent has_one target, use parent's primary key as join column
+ $join_col2 = $parent->foreign_key(TRUE, $parent_path);
+ $join_col1 = $parent->foreign_key($target_name, $target_path);
+ }
+
+ // This allows for models to use different table prefixes (sharing the same database)
+ $join_table = new Database_Expression($target->db->table_prefix().$target->table_name.' AS '.$this->db->table_prefix().$target_path);
+
+ // Join the related object into the result
+ $this->db->join($join_table, $join_col1, $join_col2, 'LEFT');
+
+ return $this;
+ }
+
+ /**
+ * Finds and loads a single database row into the object.
+ *
+ * @chainable
+ * @param mixed primary key or an array of clauses
+ * @return ORM
+ */
+ public function find($id = NULL)
+ {
+ if ($id !== NULL)
+ {
+ if (is_array($id))
+ {
+ // Search for all clauses
+ $this->db->where($id);
+ }
+ else
+ {
+ // Search for a specific column
+ $this->db->where($this->table_name.'.'.$this->unique_key($id), $id);
+ }
+ }
+
+ return $this->load_result();
+ }
+
+ /**
+ * Finds multiple database rows and returns an iterator of the rows found.
+ *
+ * @chainable
+ * @param integer SQL limit
+ * @param integer SQL offset
+ * @return ORM_Iterator
+ */
+ public function find_all($limit = NULL, $offset = NULL)
+ {
+ if ($limit !== NULL AND ! isset($this->db_applied['limit']))
+ {
+ // Set limit
+ $this->limit($limit);
+ }
+
+ if ($offset !== NULL AND ! isset($this->db_applied['offset']))
+ {
+ // Set offset
+ $this->offset($offset);
+ }
+
+ return $this->load_result(TRUE);
+ }
+
+ /**
+ * Creates a key/value array from all of the objects available. Uses find_all
+ * to find the objects.
+ *
+ * @param string key column
+ * @param string value column
+ * @return array
+ */
+ public function select_list($key = NULL, $val = NULL)
+ {
+ if ($key === NULL)
+ {
+ $key = $this->primary_key;
+ }
+
+ if ($val === NULL)
+ {
+ $val = $this->primary_val;
+ }
+
+ // Return a select list from the results
+ return $this->select($key, $val)->find_all()->select_list($key, $val);
+ }
+
+ /**
+ * Validates the current object. This method should generally be called
+ * via the model, after the $_POST Validation object has been created.
+ *
+ * @param object Validation array
+ * @return boolean
+ */
+ public function validate(Validation $array, $save = FALSE)
+ {
+ $safe_array = $array->safe_array();
+
+ if ( ! $array->submitted())
+ {
+ foreach ($safe_array as $key => $value)
+ {
+ // Get the value from this object
+ $value = $this->$key;
+
+ if (is_object($value) AND $value instanceof ORM_Iterator)
+ {
+ // Convert the value to an array of primary keys
+ $value = $value->primary_key_array();
+ }
+
+ // Pre-fill data
+ $array[$key] = $value;
+ }
+ }
+
+ // Validate the array
+ if ($status = $array->validate())
+ {
+ // Grab only set fields (excludes missing data, unlike safe_array)
+ $fields = $array->as_array();
+
+ foreach ($fields as $key => $value)
+ {
+ if (isset($safe_array[$key]))
+ {
+ // Set new data, ignoring any missing fields or fields without rules
+ $this->$key = $value;
+ }
+ }
+
+ if ($save === TRUE OR is_string($save))
+ {
+ // Save this object
+ $this->save();
+
+ if (is_string($save))
+ {
+ // Redirect to the saved page
+ url::redirect($save);
+ }
+ }
+ }
+
+ // Return validation status
+ return $status;
+ }
+
+ /**
+ * Saves the current object.
+ *
+ * @chainable
+ * @return ORM
+ */
+ public function save()
+ {
+ if ( ! empty($this->changed))
+ {
+ $data = array();
+ foreach ($this->changed as $column)
+ {
+ // Compile changed data
+ $data[$column] = $this->object[$column];
+ }
+
+ if ($this->loaded === TRUE)
+ {
+ $query = $this->db
+ ->where($this->primary_key, $this->object[$this->primary_key])
+ ->update($this->table_name, $data);
+
+ // Object has been saved
+ $this->saved = TRUE;
+ }
+ else
+ {
+ $query = $this->db
+ ->insert($this->table_name, $data);
+
+ if ($query->count() > 0)
+ {
+ if (empty($this->object[$this->primary_key]))
+ {
+ // Load the insert id as the primary key
+ $this->object[$this->primary_key] = $query->insert_id();
+ }
+
+ // Object is now loaded and saved
+ $this->loaded = $this->saved = TRUE;
+ }
+ }
+
+ if ($this->saved === TRUE)
+ {
+ // All changes have been saved
+ $this->changed = array();
+ }
+ }
+
+ if ($this->saved === TRUE AND ! empty($this->changed_relations))
+ {
+ foreach ($this->changed_relations as $column => $values)
+ {
+ // All values that were added
+ $added = array_diff($values, $this->object_relations[$column]);
+
+ // All values that were saved
+ $removed = array_diff($this->object_relations[$column], $values);
+
+ if (empty($added) AND empty($removed))
+ {
+ // No need to bother
+ continue;
+ }
+
+ // Clear related columns
+ unset($this->related[$column]);
+
+ // Load the model
+ $model = ORM::factory(inflector::singular($column));
+
+ if (($join_table = array_search($column, $this->has_and_belongs_to_many)) === FALSE)
+ continue;
+
+ if (is_int($join_table))
+ {
+ // No "through" table, load the default JOIN table
+ $join_table = $model->join_table($this->table_name);
+ }
+
+ // Foreign keys for the join table
+ $object_fk = $this->foreign_key(NULL);
+ $related_fk = $model->foreign_key(NULL);
+
+ if ( ! empty($added))
+ {
+ foreach ($added as $id)
+ {
+ // Insert the new relationship
+ $this->db->insert($join_table, array
+ (
+ $object_fk => $this->object[$this->primary_key],
+ $related_fk => $id,
+ ));
+ }
+ }
+
+ if ( ! empty($removed))
+ {
+ $this->db
+ ->where($object_fk, $this->object[$this->primary_key])
+ ->in($related_fk, $removed)
+ ->delete($join_table);
+ }
+
+ // Clear all relations for this column
+ unset($this->object_relations[$column], $this->changed_relations[$column]);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Deletes the current object from the database. This does NOT destroy
+ * relationships that have been created with other objects.
+ *
+ * @chainable
+ * @return ORM
+ */
+ public function delete($id = NULL)
+ {
+ if ($id === NULL AND $this->loaded)
+ {
+ // Use the the primary key value
+ $id = $this->object[$this->primary_key];
+ }
+
+ // Delete this object
+ $this->db->where($this->primary_key, $id)->delete($this->table_name);
+
+ return $this->clear();
+ }
+
+ /**
+ * Delete all objects in the associated table. This does NOT destroy
+ * relationships that have been created with other objects.
+ *
+ * @chainable
+ * @param array ids to delete
+ * @return ORM
+ */
+ public function delete_all($ids = NULL)
+ {
+ if (is_array($ids))
+ {
+ // Delete only given ids
+ $this->db->in($this->primary_key, $ids);
+ }
+ elseif (is_null($ids))
+ {
+ // Delete all records
+ $this->db->where('1=1');
+ }
+ else
+ {
+ // Do nothing - safeguard
+ return $this;
+ }
+
+ // Delete all objects
+ $this->db->delete($this->table_name);
+
+ return $this->clear();
+ }
+
+ /**
+ * Unloads the current object and clears the status.
+ *
+ * @chainable
+ * @return ORM
+ */
+ public function clear()
+ {
+ // Create an array with all the columns set to NULL
+ $columns = array_keys($this->table_columns);
+ $values = array_combine($columns, array_fill(0, count($columns), NULL));
+
+ // Replace the current object with an empty one
+ $this->load_values($values);
+
+ return $this;
+ }
+
+ /**
+ * Reloads the current object from the database.
+ *
+ * @chainable
+ * @return ORM
+ */
+ public function reload()
+ {
+ return $this->find($this->object[$this->primary_key]);
+ }
+
+ /**
+ * Reload column definitions.
+ *
+ * @chainable
+ * @param boolean force reloading
+ * @return ORM
+ */
+ public function reload_columns($force = FALSE)
+ {
+ if ($force === TRUE OR empty($this->table_columns))
+ {
+ if (isset(ORM::$column_cache[$this->object_name]))
+ {
+ // Use cached column information
+ $this->table_columns = ORM::$column_cache[$this->object_name];
+ }
+ else
+ {
+ // Load table columns
+ ORM::$column_cache[$this->object_name] = $this->table_columns = $this->list_fields();
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Tests if this object has a relationship to a different model.
+ *
+ * @param object related ORM model
+ * @param boolean check for any relations to given model
+ * @return boolean
+ */
+ public function has(ORM $model, $any = FALSE)
+ {
+ // Determine plural or singular relation name
+ $related = ($model->table_names_plural === TRUE) ? $model->object_plural : $model->object_name;
+
+ if (($join_table = array_search($related, $this->has_and_belongs_to_many)) === FALSE)
+ return FALSE;
+
+ if (is_int($join_table))
+ {
+ // No "through" table, load the default JOIN table
+ $join_table = $model->join_table($this->table_name);
+ }
+
+ if ( ! isset($this->object_relations[$related]))
+ {
+ // Load the object relationships
+ $this->changed_relations[$related] = $this->object_relations[$related] = $this->load_relations($join_table, $model);
+ }
+
+ if ( ! $model->empty_primary_key())
+ {
+ // Check if a specific object exists
+ return in_array($model->primary_key_value, $this->changed_relations[$related]);
+ }
+ elseif ($any)
+ {
+ // Check if any relations to given model exist
+ return ! empty($this->changed_relations[$related]);
+ }
+ else
+ {
+ return FALSE;
+ }
+ }
+
+ /**
+ * Adds a new relationship to between this model and another.
+ *
+ * @param object related ORM model
+ * @return boolean
+ */
+ public function add(ORM $model)
+ {
+ if ($this->has($model))
+ return TRUE;
+
+ // Get the faked column name
+ $column = $model->object_plural;
+
+ // Add the new relation to the update
+ $this->changed_relations[$column][] = $model->primary_key_value;
+
+ if (isset($this->related[$column]))
+ {
+ // Force a reload of the relationships
+ unset($this->related[$column]);
+ }
+
+ return TRUE;
+ }
+
+ /**
+ * Adds a new relationship to between this model and another.
+ *
+ * @param object related ORM model
+ * @return boolean
+ */
+ public function remove(ORM $model)
+ {
+ if ( ! $this->has($model))
+ return FALSE;
+
+ // Get the faked column name
+ $column = $model->object_plural;
+
+ if (($key = array_search($model->primary_key_value, $this->changed_relations[$column])) === FALSE)
+ return FALSE;
+
+ // Remove the relationship
+ unset($this->changed_relations[$column][$key]);
+
+ if (isset($this->related[$column]))
+ {
+ // Force a reload of the relationships
+ unset($this->related[$column]);
+ }
+
+ return TRUE;
+ }
+
+ /**
+ * Count the number of records in the table.
+ *
+ * @return integer
+ */
+ public function count_all()
+ {
+ // Return the total number of records in a table
+ return $this->db->count_records($this->table_name);
+ }
+
+ /**
+ * Proxy method to Database list_fields.
+ *
+ * @param string table name or NULL to use this table
+ * @return array
+ */
+ public function list_fields($table = NULL)
+ {
+ if ($table === NULL)
+ {
+ $table = $this->table_name;
+ }
+
+ // Proxy to database
+ return $this->db->list_fields($table);
+ }
+
+ /**
+ * Proxy method to Database field_data.
+ *
+ * @param string table name
+ * @return array
+ */
+ public function field_data($table)
+ {
+ // Proxy to database
+ return $this->db->field_data($table);
+ }
+
+ /**
+ * Proxy method to Database field_data.
+ *
+ * @chainable
+ * @param string SQL query to clear
+ * @return ORM
+ */
+ public function clear_cache($sql = NULL)
+ {
+ // Proxy to database
+ $this->db->clear_cache($sql);
+
+ ORM::$column_cache = array();
+
+ return $this;
+ }
+
+ /**
+ * Returns the unique key for a specific value. This method is expected
+ * to be overloaded in models if the model has other unique columns.
+ *
+ * @param mixed unique value
+ * @return string
+ */
+ public function unique_key($id)
+ {
+ return $this->primary_key;
+ }
+
+ /**
+ * Determines the name of a foreign key for a specific table.
+ *
+ * @param string related table name
+ * @param string prefix table name (used for JOINs)
+ * @return string
+ */
+ public function foreign_key($table = NULL, $prefix_table = NULL)
+ {
+ if ($table === TRUE)
+ {
+ if (is_string($prefix_table))
+ {
+ // Use prefix table name and this table's PK
+ return $prefix_table.'.'.$this->primary_key;
+ }
+ else
+ {
+ // Return the name of this table's PK
+ return $this->table_name.'.'.$this->primary_key;
+ }
+ }
+
+ if (is_string($prefix_table))
+ {
+ // Add a period for prefix_table.column support
+ $prefix_table .= '.';
+ }
+
+ if (isset($this->foreign_key[$table]))
+ {
+ // Use the defined foreign key name, no magic here!
+ $foreign_key = $this->foreign_key[$table];
+ }
+ else
+ {
+ if ( ! is_string($table) OR ! array_key_exists($table.'_'.$this->primary_key, $this->object))
+ {
+ // Use this table
+ $table = $this->table_name;
+
+ if (strpos($table, '.') !== FALSE)
+ {
+ // Hack around support for PostgreSQL schemas
+ list ($schema, $table) = explode('.', $table, 2);
+ }
+
+ if ($this->table_names_plural === TRUE)
+ {
+ // Make the key name singular
+ $table = inflector::singular($table);
+ }
+ }
+
+ $foreign_key = $table.'_'.$this->primary_key;
+ }
+
+ return $prefix_table.$foreign_key;
+ }
+
+ /**
+ * This uses alphabetical comparison to choose the name of the table.
+ *
+ * Example: The joining table of users and roles would be roles_users,
+ * because "r" comes before "u". Joining products and categories would
+ * result in categories_products, because "c" comes before "p".
+ *
+ * Example: zoo > zebra > robber > ocean > angel > aardvark
+ *
+ * @param string table name
+ * @return string
+ */
+ public function join_table($table)
+ {
+ if ($this->table_name > $table)
+ {
+ $table = $table.'_'.$this->table_name;
+ }
+ else
+ {
+ $table = $this->table_name.'_'.$table;
+ }
+
+ return $table;
+ }
+
+ /**
+ * Returns an ORM model for the given object name;
+ *
+ * @param string object name
+ * @return ORM
+ */
+ protected function related_object($object)
+ {
+ if (isset($this->has_one[$object]))
+ {
+ $object = ORM::factory($this->has_one[$object]);
+ }
+ elseif (isset($this->belongs_to[$object]))
+ {
+ $object = ORM::factory($this->belongs_to[$object]);
+ }
+ elseif (in_array($object, $this->has_one) OR in_array($object, $this->belongs_to))
+ {
+ $object = ORM::factory($object);
+ }
+ else
+ {
+ return FALSE;
+ }
+
+ return $object;
+ }
+
+ /**
+ * Loads an array of values into into the current object.
+ *
+ * @chainable
+ * @param array values to load
+ * @return ORM
+ */
+ public function load_values(array $values)
+ {
+ if (array_key_exists($this->primary_key, $values))
+ {
+ // Replace the object and reset the object status
+ $this->object = $this->changed = $this->related = array();
+
+ // Set the loaded and saved object status based on the primary key
+ $this->loaded = $this->saved = ($values[$this->primary_key] !== NULL);
+ }
+
+ // Related objects
+ $related = array();
+
+ foreach ($values as $column => $value)
+ {
+ if (strpos($column, ':') === FALSE)
+ {
+ if (isset($this->table_columns[$column]))
+ {
+ // The type of the value can be determined, convert the value
+ $value = $this->load_type($column, $value);
+ }
+
+ $this->object[$column] = $value;
+ }
+ else
+ {
+ list ($prefix, $column) = explode(':', $column, 2);
+
+ $related[$prefix][$column] = $value;
+ }
+ }
+
+ if ( ! empty($related))
+ {
+ foreach ($related as $object => $values)
+ {
+ // Load the related objects with the values in the result
+ $this->related[$object] = $this->related_object($object)->load_values($values);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Loads a value according to the types defined by the column metadata.
+ *
+ * @param string column name
+ * @param mixed value to load
+ * @return mixed
+ */
+ protected function load_type($column, $value)
+ {
+ $type = gettype($value);
+ if ($type == 'object' OR $type == 'array' OR ! isset($this->table_columns[$column]))
+ return $value;
+
+ // Load column data
+ $column = $this->table_columns[$column];
+
+ if ($value === NULL AND ! empty($column['null']))
+ return $value;
+
+ if ( ! empty($column['binary']) AND ! empty($column['exact']) AND (int) $column['length'] === 1)
+ {
+ // Use boolean for BINARY(1) fields
+ $column['type'] = 'boolean';
+ }
+
+ switch ($column['type'])
+ {
+ case 'int':
+ if ($value === '' AND ! empty($column['null']))
+ {
+ // Forms will only submit strings, so empty integer values must be null
+ $value = NULL;
+ }
+ elseif ((float) $value > PHP_INT_MAX)
+ {
+ // This number cannot be represented by a PHP integer, so we convert it to a string
+ $value = (string) $value;
+ }
+ else
+ {
+ $value = (int) $value;
+ }
+ break;
+ case 'float':
+ $value = (float) $value;
+ break;
+ case 'boolean':
+ $value = (bool) $value;
+ break;
+ case 'string':
+ $value = (string) $value;
+ break;
+ }
+
+ return $value;
+ }
+
+ /**
+ * Loads a database result, either as a new object for this model, or as
+ * an iterator for multiple rows.
+ *
+ * @chainable
+ * @param boolean return an iterator or load a single row
+ * @return ORM for single rows
+ * @return ORM_Iterator for multiple rows
+ */
+ protected function load_result($array = FALSE)
+ {
+ if ($array === FALSE)
+ {
+ // Only fetch 1 record
+ $this->db->limit(1);
+ }
+
+ if ( ! isset($this->db_applied['select']))
+ {
+ // Select all columns by default
+ $this->db->select($this->table_name.'.*');
+ }
+
+ if ( ! empty($this->load_with))
+ {
+ foreach ($this->load_with as $alias => $object)
+ {
+ // Join each object into the results
+ if (is_string($alias))
+ {
+ // Use alias
+ $this->with($alias);
+ }
+ else
+ {
+ // Use object
+ $this->with($object);
+ }
+ }
+ }
+
+ if ( ! isset($this->db_applied['orderby']) AND ! empty($this->sorting))
+ {
+ $sorting = array();
+ foreach ($this->sorting as $column => $direction)
+ {
+ if (strpos($column, '.') === FALSE)
+ {
+ // Keeps sorting working properly when using JOINs on
+ // tables with columns of the same name
+ $column = $this->table_name.'.'.$column;
+ }
+
+ $sorting[$column] = $direction;
+ }
+
+ // Apply the user-defined sorting
+ $this->db->orderby($sorting);
+ }
+
+ // Load the result
+ $result = $this->db->get($this->table_name);
+
+ if ($array === TRUE)
+ {
+ // Return an iterated result
+ return new ORM_Iterator($this, $result);
+ }
+
+ if ($result->count() === 1)
+ {
+ // Load object values
+ $this->load_values($result->result(FALSE)->current());
+ }
+ else
+ {
+ // Clear the object, nothing was found
+ $this->clear();
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return an array of all the primary keys of the related table.
+ *
+ * @param string table name
+ * @param object ORM model to find relations of
+ * @return array
+ */
+ protected function load_relations($table, ORM $model)
+ {
+ // Save the current query chain (otherwise the next call will clash)
+ $this->db->push();
+
+ $query = $this->db
+ ->select($model->foreign_key(NULL).' AS id')
+ ->from($table)
+ ->where($this->foreign_key(NULL, $table), $this->object[$this->primary_key])
+ ->get()
+ ->result(TRUE);
+
+ $this->db->pop();
+
+ $relations = array();
+ foreach ($query as $row)
+ {
+ $relations[] = $row->id;
+ }
+
+ return $relations;
+ }
+
+ /**
+ * Returns whether or not primary key is empty
+ *
+ * @return bool
+ */
+ protected function empty_primary_key()
+ {
+ return (empty($this->object[$this->primary_key]) AND $this->object[$this->primary_key] !== '0');
+ }
+
+} // End ORM
diff --git a/system/libraries/ORM_Iterator.php b/system/libraries/ORM_Iterator.php
new file mode 100644
index 00000000..41aa8065
--- /dev/null
+++ b/system/libraries/ORM_Iterator.php
@@ -0,0 +1,228 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+* Object Relational Mapping (ORM) result iterator.
+*
+* $Id: ORM_Iterator.php 3769 2008-12-15 00:48:56Z zombor $
+*
+* @package ORM
+* @author Kohana Team
+* @copyright (c) 2007-2008 Kohana Team
+* @license http://kohanaphp.com/license.html
+*/
+class ORM_Iterator_Core implements Iterator, ArrayAccess, Countable {
+
+ // Class attributes
+ protected $class_name;
+ protected $primary_key;
+ protected $primary_val;
+
+ // Database result object
+ protected $result;
+
+ public function __construct(ORM $model, Database_Result $result)
+ {
+ // Class attributes
+ $this->class_name = get_class($model);
+ $this->primary_key = $model->primary_key;
+ $this->primary_val = $model->primary_val;
+
+ // Database result
+ $this->result = $result->result(TRUE);
+ }
+
+ /**
+ * Returns an array of the results as ORM objects.
+ *
+ * @return array
+ */
+ public function as_array()
+ {
+ $array = array();
+
+ if ($results = $this->result->result_array())
+ {
+ // Import class name
+ $class = $this->class_name;
+
+ foreach ($results as $obj)
+ {
+ $array[] = new $class($obj);
+ }
+ }
+
+ return $array;
+ }
+
+ /**
+ * Return an array of all of the primary keys for this object.
+ *
+ * @return array
+ */
+ public function primary_key_array()
+ {
+ $ids = array();
+ foreach ($this->result as $row)
+ {
+ $ids[] = $row->{$this->primary_key};
+ }
+ return $ids;
+ }
+
+ /**
+ * Create a key/value array from the results.
+ *
+ * @param string key column
+ * @param string value column
+ * @return array
+ */
+ public function select_list($key = NULL, $val = NULL)
+ {
+ if ($key === NULL)
+ {
+ // Use the default key
+ $key = $this->primary_key;
+ }
+
+ if ($val === NULL)
+ {
+ // Use the default value
+ $val = $this->primary_val;
+ }
+
+ $array = array();
+ foreach ($this->result->result_array() as $row)
+ {
+ $array[$row->$key] = $row->$val;
+ }
+ return $array;
+ }
+
+ /**
+ * Return a range of offsets.
+ *
+ * @param integer start
+ * @param integer end
+ * @return array
+ */
+ public function range($start, $end)
+ {
+ // Array of objects
+ $array = array();
+
+ if ($this->result->offsetExists($start))
+ {
+ // Import the class name
+ $class = $this->class_name;
+
+ // Set the end offset
+ $end = $this->result->offsetExists($end) ? $end : $this->count();
+
+ for ($i = $start; $i < $end; $i++)
+ {
+ // Insert each object in the range
+ $array[] = new $class($this->result->offsetGet($i));
+ }
+ }
+
+ return $array;
+ }
+
+ /**
+ * Countable: count
+ */
+ public function count()
+ {
+ return $this->result->count();
+ }
+
+ /**
+ * Iterator: current
+ */
+ public function current()
+ {
+ if ($row = $this->result->current())
+ {
+ // Import class name
+ $class = $this->class_name;
+
+ $row = new $class($row);
+ }
+
+ return $row;
+ }
+
+ /**
+ * Iterator: key
+ */
+ public function key()
+ {
+ return $this->result->key();
+ }
+
+ /**
+ * Iterator: next
+ */
+ public function next()
+ {
+ return $this->result->next();
+ }
+
+ /**
+ * Iterator: rewind
+ */
+ public function rewind()
+ {
+ $this->result->rewind();
+ }
+
+ /**
+ * Iterator: valid
+ */
+ public function valid()
+ {
+ return $this->result->valid();
+ }
+
+ /**
+ * ArrayAccess: offsetExists
+ */
+ public function offsetExists($offset)
+ {
+ return $this->result->offsetExists($offset);
+ }
+
+ /**
+ * ArrayAccess: offsetGet
+ */
+ public function offsetGet($offset)
+ {
+ if ($this->result->offsetExists($offset))
+ {
+ // Import class name
+ $class = $this->class_name;
+
+ return new $class($this->result->offsetGet($offset));
+ }
+ }
+
+ /**
+ * ArrayAccess: offsetSet
+ *
+ * @throws Kohana_Database_Exception
+ */
+ public function offsetSet($offset, $value)
+ {
+ throw new Kohana_Database_Exception('database.result_read_only');
+ }
+
+ /**
+ * ArrayAccess: offsetUnset
+ *
+ * @throws Kohana_Database_Exception
+ */
+ public function offsetUnset($offset)
+ {
+ throw new Kohana_Database_Exception('database.result_read_only');
+ }
+
+} // End ORM Iterator \ No newline at end of file
diff --git a/system/libraries/ORM_Tree.php b/system/libraries/ORM_Tree.php
new file mode 100644
index 00000000..cdb09fd0
--- /dev/null
+++ b/system/libraries/ORM_Tree.php
@@ -0,0 +1,76 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Object Relational Mapping (ORM) "tree" extension. Allows ORM objects to act
+ * as trees, with parents and children.
+ *
+ * $Id: ORM_Tree.php 3923 2009-01-22 15:37:04Z samsoir $
+ *
+ * @package ORM
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class ORM_Tree_Core extends ORM {
+
+ // Name of the child
+ protected $ORM_Tree_children;
+
+ // Parent keyword name
+ protected $ORM_Tree_parent_key = 'parent_id';
+
+ /**
+ * Overload ORM::__get to support "parent" and "children" properties.
+ *
+ * @param string column name
+ * @return mixed
+ */
+ public function __get($column)
+ {
+ if ($column === 'parent')
+ {
+ if (empty($this->related[$column]))
+ {
+ // Load child model
+ $model = ORM::factory(inflector::singular($this->ORM_Tree_children));
+
+ if (array_key_exists($this->ORM_Tree_parent_key, $this->object))
+ {
+ // Find children of this parent
+ $model->where($model->primary_key, $this->object[$this->ORM_Tree_parent_key])->find();
+ }
+
+ $this->related[$column] = $model;
+ }
+
+ return $this->related[$column];
+ }
+ elseif ($column === 'children')
+ {
+ if (empty($this->related[$column]))
+ {
+ $model = ORM::factory(inflector::singular($this->ORM_Tree_children));
+
+ if ($this->ORM_Tree_children === $this->table_name)
+ {
+ // Load children within this table
+ $this->related[$column] = $model
+ ->where($this->ORM_Tree_parent_key, $this->object[$this->primary_key])
+ ->find_all();
+ }
+ else
+ {
+ // Find first selection of children
+ $this->related[$column] = $model
+ ->where($this->foreign_key(), $this->object[$this->primary_key])
+ ->where($this->ORM_Tree_parent_key, NULL)
+ ->find_all();
+ }
+ }
+
+ return $this->related[$column];
+ }
+
+ return parent::__get($column);
+ }
+
+} // End ORM Tree \ No newline at end of file
diff --git a/system/libraries/ORM_Versioned.php b/system/libraries/ORM_Versioned.php
new file mode 100644
index 00000000..078fe16a
--- /dev/null
+++ b/system/libraries/ORM_Versioned.php
@@ -0,0 +1,143 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Object Relational Mapping (ORM) "versioned" extension. Allows ORM objects to
+ * be revisioned instead of updated.
+ *
+ * $Id$
+ *
+ * @package ORM
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class ORM_Versioned_Core extends ORM {
+
+ protected $last_version = NULL;
+
+ /**
+ * Overload ORM::save() to support versioned data
+ *
+ * @chainable
+ * @return ORM
+ */
+ public function save()
+ {
+ $this->last_version = 1 + ($this->last_version === NULL ? $this->object['version'] : $this->last_version);
+ $this->__set('version', $this->last_version);
+
+ parent::save();
+
+ if ($this->saved)
+ {
+ $data = array();
+ foreach ($this->object as $key => $value)
+ {
+ if ($key === 'id')
+ continue;
+
+ $data[$key] = $value;
+ }
+ $data[$this->foreign_key()] = $this->id;
+
+ $this->db->insert($this->table_name.'_versions', $data);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Loads previous version from current object
+ *
+ * @chainable
+ * @return ORM
+ */
+ public function previous()
+ {
+ if ( ! $this->loaded)
+ return $this;
+
+ $this->last_version = ($this->last_version === NULL) ? $this->object['version'] : $this->last_version;
+ $version = $this->last_version - 1;
+
+ $query = $this->db
+ ->where($this->foreign_key(), $this->object[$this->primary_key])
+ ->where('version', $version)
+ ->limit(1)
+ ->get($this->table_name.'_versions');
+
+ if ($query->count())
+ {
+ $this->load_values($query->result(FALSE)->current());
+ }
+
+ return $this;
+ }
+
+ /**
+ * Restores the object with data from stored version
+ *
+ * @param integer version number you want to restore
+ * @return ORM
+ */
+ public function restore($version)
+ {
+ if ( ! $this->loaded)
+ return $this;
+
+ $query = $this->db
+ ->where($this->foreign_key(), $this->object[$this->primary_key])
+ ->where('version', $version)
+ ->limit(1)
+ ->get($this->table_name.'_versions');
+
+ if ($query->count())
+ {
+ $row = $query->result(FALSE)->current();
+
+ foreach ($row as $key => $value)
+ {
+ if ($key === $this->primary_key OR $key === $this->foreign_key())
+ {
+ // Do not overwrite the primary key
+ continue;
+ }
+
+ if ($key === 'version')
+ {
+ // Always use the current version
+ $value = $this->version;
+ }
+
+ $this->__set($key, $value);
+ }
+
+ $this->save();
+ }
+
+ return $this;
+ }
+
+ /**
+ * Overloads ORM::delete() to delete all versioned entries of current object
+ * and the object itself
+ *
+ * @param integer id of the object you want to delete
+ * @return ORM
+ */
+ public function delete($id = NULL)
+ {
+ if ($id === NULL)
+ {
+ // Use the current object id
+ $id = $this->object[$this->primary_key];
+ }
+
+ if ($status = parent::delete($id))
+ {
+ $this->db->where($this->foreign_key(), $id)->delete($this->table_name.'_versions');
+ }
+
+ return $status;
+ }
+
+} \ No newline at end of file
diff --git a/system/libraries/Pagination.php b/system/libraries/Pagination.php
new file mode 100644
index 00000000..a8f7bb19
--- /dev/null
+++ b/system/libraries/Pagination.php
@@ -0,0 +1,236 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Pagination library.
+ *
+ * $Id: Pagination.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 Pagination_Core {
+
+ // Config values
+ protected $base_url = '';
+ protected $directory = 'pagination';
+ protected $style = 'classic';
+ protected $uri_segment = 3;
+ protected $query_string = '';
+ protected $items_per_page = 20;
+ protected $total_items = 0;
+ protected $auto_hide = FALSE;
+
+ // Autogenerated values
+ protected $url;
+ protected $current_page;
+ protected $total_pages;
+ protected $current_first_item;
+ protected $current_last_item;
+ protected $first_page;
+ protected $last_page;
+ protected $previous_page;
+ protected $next_page;
+ protected $sql_offset;
+ protected $sql_limit;
+
+ /**
+ * Constructs and returns a new Pagination object.
+ *
+ * @param array configuration settings
+ * @return object
+ */
+ public function factory($config = array())
+ {
+ return new Pagination($config);
+ }
+
+ /**
+ * Constructs a new Pagination object.
+ *
+ * @param array configuration settings
+ * @return void
+ */
+ public function __construct($config = array())
+ {
+ // No custom group name given
+ if ( ! isset($config['group']))
+ {
+ $config['group'] = 'default';
+ }
+
+ // Pagination setup
+ $this->initialize($config);
+
+ Kohana::log('debug', 'Pagination Library initialized');
+ }
+
+ /**
+ * Sets config values.
+ *
+ * @throws Kohana_Exception
+ * @param array configuration settings
+ * @return void
+ */
+ public function initialize($config = array())
+ {
+ // Load config group
+ if (isset($config['group']))
+ {
+ // Load and validate config group
+ if ( ! is_array($group_config = Kohana::config('pagination.'.$config['group'])))
+ throw new Kohana_Exception('pagination.undefined_group', $config['group']);
+
+ // All pagination config groups inherit default config group
+ if ($config['group'] !== 'default')
+ {
+ // Load and validate default config group
+ if ( ! is_array($default_config = Kohana::config('pagination.default')))
+ throw new Kohana_Exception('pagination.undefined_group', 'default');
+
+ // Merge config group with default config group
+ $group_config += $default_config;
+ }
+
+ // Merge custom config items with config group
+ $config += $group_config;
+ }
+
+ // Assign config values to the object
+ foreach ($config as $key => $value)
+ {
+ if (property_exists($this, $key))
+ {
+ $this->$key = $value;
+ }
+ }
+
+ // Clean view directory
+ $this->directory = trim($this->directory, '/').'/';
+
+ // Build generic URL with page in query string
+ if ($this->query_string !== '')
+ {
+ // Extract current page
+ $this->current_page = isset($_GET[$this->query_string]) ? (int) $_GET[$this->query_string] : 1;
+
+ // Insert {page} placeholder
+ $_GET[$this->query_string] = '{page}';
+
+ // Create full URL
+ $base_url = ($this->base_url === '') ? Router::$current_uri : $this->base_url;
+ $this->url = url::site($base_url).'?'.str_replace('%7Bpage%7D', '{page}', http_build_query($_GET));
+
+ // Reset page number
+ $_GET[$this->query_string] = $this->current_page;
+ }
+
+ // Build generic URL with page as URI segment
+ else
+ {
+ // Use current URI if no base_url set
+ $this->url = ($this->base_url === '') ? Router::$segments : explode('/', trim($this->base_url, '/'));
+
+ // Convert uri 'label' to corresponding integer if needed
+ if (is_string($this->uri_segment))
+ {
+ if (($key = array_search($this->uri_segment, $this->url)) === FALSE)
+ {
+ // If uri 'label' is not found, auto add it to base_url
+ $this->url[] = $this->uri_segment;
+ $this->uri_segment = count($this->url) + 1;
+ }
+ else
+ {
+ $this->uri_segment = $key + 2;
+ }
+ }
+
+ // Insert {page} placeholder
+ $this->url[$this->uri_segment - 1] = '{page}';
+
+ // Create full URL
+ $this->url = url::site(implode('/', $this->url)).Router::$query_string;
+
+ // Extract current page
+ $this->current_page = URI::instance()->segment($this->uri_segment);
+ }
+
+ // Core pagination values
+ $this->total_items = (int) max(0, $this->total_items);
+ $this->items_per_page = (int) max(1, $this->items_per_page);
+ $this->total_pages = (int) ceil($this->total_items / $this->items_per_page);
+ $this->current_page = (int) min(max(1, $this->current_page), max(1, $this->total_pages));
+ $this->current_first_item = (int) min((($this->current_page - 1) * $this->items_per_page) + 1, $this->total_items);
+ $this->current_last_item = (int) min($this->current_first_item + $this->items_per_page - 1, $this->total_items);
+
+ // If there is no first/last/previous/next page, relative to the
+ // current page, value is set to FALSE. Valid page number otherwise.
+ $this->first_page = ($this->current_page === 1) ? FALSE : 1;
+ $this->last_page = ($this->current_page >= $this->total_pages) ? FALSE : $this->total_pages;
+ $this->previous_page = ($this->current_page > 1) ? $this->current_page - 1 : FALSE;
+ $this->next_page = ($this->current_page < $this->total_pages) ? $this->current_page + 1 : FALSE;
+
+ // SQL values
+ $this->sql_offset = (int) ($this->current_page - 1) * $this->items_per_page;
+ $this->sql_limit = sprintf(' LIMIT %d OFFSET %d ', $this->items_per_page, $this->sql_offset);
+ }
+
+ /**
+ * Generates the HTML for the chosen pagination style.
+ *
+ * @param string pagination style
+ * @return string pagination html
+ */
+ public function render($style = NULL)
+ {
+ // Hide single page pagination
+ if ($this->auto_hide === TRUE AND $this->total_pages <= 1)
+ return '';
+
+ if ($style === NULL)
+ {
+ // Use default style
+ $style = $this->style;
+ }
+
+ // Return rendered pagination view
+ return View::factory($this->directory.$style, get_object_vars($this))->render();
+ }
+
+ /**
+ * Magically converts Pagination object to string.
+ *
+ * @return string pagination html
+ */
+ public function __toString()
+ {
+ return $this->render();
+ }
+
+ /**
+ * Magically gets a pagination variable.
+ *
+ * @param string variable key
+ * @return mixed variable value if the key is found
+ * @return void if the key is not found
+ */
+ public function __get($key)
+ {
+ if (isset($this->$key))
+ return $this->$key;
+ }
+
+ /**
+ * Adds a secondary interface for accessing properties, e.g. $pagination->total_pages().
+ * Note that $pagination->total_pages is the recommended way to access properties.
+ *
+ * @param string function name
+ * @return string
+ */
+ public function __call($func, $args = NULL)
+ {
+ return $this->__get($func);
+ }
+
+} // End Pagination Class \ No newline at end of file
diff --git a/system/libraries/Profiler.php b/system/libraries/Profiler.php
new file mode 100644
index 00000000..47d82ace
--- /dev/null
+++ b/system/libraries/Profiler.php
@@ -0,0 +1,271 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Adds useful information to the bottom of the current page for debugging and optimization purposes.
+ *
+ * Benchmarks - The times and memory usage of benchmarks run by the Benchmark library.
+ * Database - The raw SQL and number of affected rows of Database queries.
+ * Session Data - Data stored in the current session if using the Session library.
+ * POST Data - The name and values of any POST data submitted to the current page.
+ * Cookie Data - All cookies sent for the current request.
+ *
+ * $Id: Profiler.php 4090 2009-03-19 01:27:45Z bharat $
+ *
+ * @package Profiler
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class Profiler_Core {
+
+ protected $profiles = array();
+ protected $show;
+
+ public function __construct()
+ {
+ // Add all built in profiles to event
+ Event::add('profiler.run', array($this, 'benchmarks'));
+ Event::add('profiler.run', array($this, 'database'));
+ Event::add('profiler.run', array($this, 'session'));
+ Event::add('profiler.run', array($this, 'post'));
+ Event::add('profiler.run', array($this, 'cookies'));
+
+ // Add profiler to page output automatically
+ Event::add('system.display', array($this, 'render'));
+
+ Kohana::log('debug', 'Profiler Library initialized');
+ }
+
+ /**
+ * Magic __call method. Creates a new profiler section object.
+ *
+ * @param string input type
+ * @param string input name
+ * @return object
+ */
+ public function __call($method, $args)
+ {
+ if ( ! $this->show OR (is_array($this->show) AND ! in_array($args[0], $this->show)))
+ return FALSE;
+
+ // Class name
+ $class = 'Profiler_'.ucfirst($method);
+
+ $class = new $class();
+
+ $this->profiles[$args[0]] = $class;
+
+ return $class;
+ }
+
+ /**
+ * Disables the profiler for this page only.
+ * Best used when profiler is autoloaded.
+ *
+ * @return void
+ */
+ public function disable()
+ {
+ // Removes itself from the event queue
+ Event::clear('system.display', array($this, 'render'));
+ }
+
+ /**
+ * Render the profiler. Output is added to the bottom of the page by default.
+ *
+ * @param boolean return the output if TRUE
+ * @return void|string
+ */
+ public function render($return = FALSE)
+ {
+ $start = microtime(TRUE);
+
+ $get = isset($_GET['profiler']) ? explode(',', $_GET['profiler']) : array();
+ $this->show = empty($get) ? Kohana::config('profiler.show') : $get;
+
+ Event::run('profiler.run', $this);
+
+ $styles = '';
+ foreach ($this->profiles as $profile)
+ {
+ $styles .= $profile->styles();
+ }
+
+ // Don't display if there's no profiles
+ if (empty($this->profiles))
+ return;
+
+ // Load the profiler view
+ $data = array
+ (
+ 'profiles' => $this->profiles,
+ 'styles' => $styles,
+ 'execution_time' => microtime(TRUE) - $start
+ );
+ $view = new View('kohana_profiler', $data);
+
+ // Return rendered view if $return is TRUE
+ if ($return == TRUE)
+ return $view->render();
+
+ // Add profiler data to the output
+ if (stripos(Kohana::$output, '</body>') !== FALSE)
+ {
+ // Closing body tag was found, insert the profiler data before it
+ Kohana::$output = str_ireplace('</body>', $view->render().'</body>', Kohana::$output);
+ }
+ else
+ {
+ // Append the profiler data to the output
+ Kohana::$output .= $view->render();
+ }
+ }
+
+ /**
+ * Benchmark times and memory usage from the Benchmark library.
+ *
+ * @return void
+ */
+ public function benchmarks()
+ {
+ if ( ! $table = $this->table('benchmarks'))
+ return;
+
+ $table->add_column();
+ $table->add_column('kp-column kp-data');
+ $table->add_column('kp-column kp-data');
+ $table->add_column('kp-column kp-data');
+ $table->add_row(array('Benchmarks', 'Time', 'Count', 'Memory'), 'kp-title', 'background-color: #FFE0E0');
+
+ $benchmarks = Benchmark::get(TRUE);
+
+ // Moves the first benchmark (total execution time) to the end of the array
+ $benchmarks = array_slice($benchmarks, 1) + array_slice($benchmarks, 0, 1);
+
+ text::alternate();
+ foreach ($benchmarks as $name => $benchmark)
+ {
+ // Clean unique id from system benchmark names
+ $name = ucwords(str_replace(array('_', '-'), ' ', str_replace(SYSTEM_BENCHMARK.'_', '', $name)));
+
+ $data = array($name, number_format($benchmark['time'], 3), $benchmark['count'], number_format($benchmark['memory'] / 1024 / 1024, 2).'MB');
+ $class = text::alternate('', 'kp-altrow');
+
+ if ($name == 'Total Execution')
+ $class = 'kp-totalrow';
+
+ $table->add_row($data, $class);
+ }
+ }
+
+ /**
+ * Database query benchmarks.
+ *
+ * @return void
+ */
+ public function database()
+ {
+ if ( ! $table = $this->table('database'))
+ return;
+
+ $table->add_column();
+ $table->add_column('kp-column kp-data');
+ $table->add_column('kp-column kp-data');
+ $table->add_row(array('Queries', 'Time', 'Rows'), 'kp-title', 'background-color: #E0FFE0');
+
+ $queries = Database::$benchmarks;
+
+ text::alternate();
+ $total_time = $total_rows = 0;
+ foreach ($queries as $query)
+ {
+ $data = array($query['query'], number_format($query['time'], 3), $query['rows']);
+ $class = text::alternate('', 'kp-altrow');
+ $table->add_row($data, $class);
+ $total_time += $query['time'];
+ $total_rows += $query['rows'];
+ }
+
+ $data = array('Total: ' . count($queries), number_format($total_time, 3), $total_rows);
+ $table->add_row($data, 'kp-totalrow');
+ }
+
+ /**
+ * Session data.
+ *
+ * @return void
+ */
+ public function session()
+ {
+ if (empty($_SESSION)) return;
+
+ if ( ! $table = $this->table('session'))
+ return;
+
+ $table->add_column('kp-name');
+ $table->add_column();
+ $table->add_row(array('Session', 'Value'), 'kp-title', 'background-color: #CCE8FB');
+
+ text::alternate();
+ foreach($_SESSION as $name => $value)
+ {
+ if (is_object($value))
+ {
+ $value = get_class($value).' [object]';
+ }
+
+ $data = array($name, $value);
+ $class = text::alternate('', 'kp-altrow');
+ $table->add_row($data, $class);
+ }
+ }
+
+ /**
+ * POST data.
+ *
+ * @return void
+ */
+ public function post()
+ {
+ if (empty($_POST)) return;
+
+ if ( ! $table = $this->table('post'))
+ return;
+
+ $table->add_column('kp-name');
+ $table->add_column();
+ $table->add_row(array('POST', 'Value'), 'kp-title', 'background-color: #E0E0FF');
+
+ text::alternate();
+ foreach($_POST as $name => $value)
+ {
+ $data = array($name, $value);
+ $class = text::alternate('', 'kp-altrow');
+ $table->add_row($data, $class);
+ }
+ }
+
+ /**
+ * Cookie data.
+ *
+ * @return void
+ */
+ public function cookies()
+ {
+ if (empty($_COOKIE)) return;
+
+ if ( ! $table = $this->table('cookies'))
+ return;
+
+ $table->add_column('kp-name');
+ $table->add_column();
+ $table->add_row(array('Cookies', 'Value'), 'kp-title', 'background-color: #FFF4D7');
+
+ text::alternate();
+ foreach($_COOKIE as $name => $value)
+ {
+ $data = array($name, $value);
+ $class = text::alternate('', 'kp-altrow');
+ $table->add_row($data, $class);
+ }
+ }
+} \ No newline at end of file
diff --git a/system/libraries/Profiler_Table.php b/system/libraries/Profiler_Table.php
new file mode 100644
index 00000000..a0058a58
--- /dev/null
+++ b/system/libraries/Profiler_Table.php
@@ -0,0 +1,69 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Provides a table layout for sections in the Profiler library.
+ *
+ * $Id$
+ *
+ * @package Profiler
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class Profiler_Table_Core {
+
+ protected $columns = array();
+ protected $rows = array();
+
+ /**
+ * Get styles for table.
+ *
+ * @return string
+ */
+ public function styles()
+ {
+ static $styles_output;
+
+ if ( ! $styles_output)
+ {
+ $styles_output = TRUE;
+ return file_get_contents(Kohana::find_file('views', 'kohana_profiler_table', FALSE, 'css'));
+ }
+
+ return '';
+ }
+
+ /**
+ * Add column to table.
+ *
+ * @param string CSS class
+ * @param string CSS style
+ */
+ public function add_column($class = '', $style = '')
+ {
+ $this->columns[] = array('class' => $class, 'style' => $style);
+ }
+
+ /**
+ * Add row to table.
+ *
+ * @param array data to go in table cells
+ * @param string CSS class
+ * @param string CSS style
+ */
+ public function add_row($data, $class = '', $style = '')
+ {
+ $this->rows[] = array('data' => $data, 'class' => $class, 'style' => $style);
+ }
+
+ /**
+ * Render table.
+ *
+ * @return string
+ */
+ public function render()
+ {
+ $data['rows'] = $this->rows;
+ $data['columns'] = $this->columns;
+ return View::factory('kohana_profiler_table', $data)->render();
+ }
+} \ No newline at end of file
diff --git a/system/libraries/Router.php b/system/libraries/Router.php
new file mode 100644
index 00000000..6dc9b10c
--- /dev/null
+++ b/system/libraries/Router.php
@@ -0,0 +1,304 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Router
+ *
+ * $Id: Router.php 4350 2009-05-14 18:58:18Z zombor $
+ *
+ * @package Core
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class Router_Core {
+
+ protected static $routes;
+
+ public static $current_uri = '';
+ public static $query_string = '';
+ public static $complete_uri = '';
+ public static $routed_uri = '';
+ public static $url_suffix = '';
+
+ public static $segments;
+ public static $rsegments;
+
+ public static $controller;
+ public static $controller_path;
+
+ public static $method = 'index';
+ public static $arguments = array();
+
+ /**
+ * Router setup routine. Automatically called during Kohana setup process.
+ *
+ * @return void
+ */
+ public static function setup()
+ {
+ if ( ! empty($_SERVER['QUERY_STRING']))
+ {
+ // Set the query string to the current query string
+ Router::$query_string = '?'.trim($_SERVER['QUERY_STRING'], '&/');
+ }
+
+ if (Router::$routes === NULL)
+ {
+ // Load routes
+ Router::$routes = Kohana::config('routes');
+ }
+
+ // Default route status
+ $default_route = FALSE;
+
+ if (Router::$current_uri === '')
+ {
+ // Make sure the default route is set
+ if ( ! isset(Router::$routes['_default']))
+ throw new Kohana_Exception('core.no_default_route');
+
+ // Use the default route when no segments exist
+ Router::$current_uri = Router::$routes['_default'];
+
+ // Default route is in use
+ $default_route = TRUE;
+ }
+
+ // Make sure the URL is not tainted with HTML characters
+ Router::$current_uri = html::specialchars(Router::$current_uri, FALSE);
+
+ // Remove all dot-paths from the URI, they are not valid
+ Router::$current_uri = preg_replace('#\.[\s./]*/#', '', Router::$current_uri);
+
+ // At this point segments, rsegments, and current URI are all the same
+ Router::$segments = Router::$rsegments = Router::$current_uri = trim(Router::$current_uri, '/');
+
+ // Set the complete URI
+ Router::$complete_uri = Router::$current_uri.Router::$query_string;
+
+ // Explode the segments by slashes
+ Router::$segments = ($default_route === TRUE OR Router::$segments === '') ? array() : explode('/', Router::$segments);
+
+ if ($default_route === FALSE AND count(Router::$routes) > 1)
+ {
+ // Custom routing
+ Router::$rsegments = Router::routed_uri(Router::$current_uri);
+ }
+
+ // The routed URI is now complete
+ Router::$routed_uri = Router::$rsegments;
+
+ // Routed segments will never be empty
+ Router::$rsegments = explode('/', Router::$rsegments);
+
+ // Prepare to find the controller
+ $controller_path = '';
+ $method_segment = NULL;
+
+ // Paths to search
+ $paths = Kohana::include_paths();
+
+ foreach (Router::$rsegments as $key => $segment)
+ {
+ // Add the segment to the search path
+ $controller_path .= $segment;
+
+ $found = FALSE;
+ foreach ($paths as $dir)
+ {
+ // Search within controllers only
+ $dir .= 'controllers/';
+
+ if (is_dir($dir.$controller_path) OR is_file($dir.$controller_path.EXT))
+ {
+ // Valid path
+ $found = TRUE;
+
+ // The controller must be a file that exists with the search path
+ if ($c = str_replace('\\', '/', realpath($dir.$controller_path.EXT))
+ AND is_file($c) AND strpos($c, $dir) === 0)
+ {
+ // Set controller name
+ Router::$controller = $segment;
+
+ // Change controller path
+ Router::$controller_path = $c;
+
+ // Set the method segment
+ $method_segment = $key + 1;
+
+ // Stop searching
+ break;
+ }
+ }
+ }
+
+ if ($found === FALSE)
+ {
+ // Maximum depth has been reached, stop searching
+ break;
+ }
+
+ // Add another slash
+ $controller_path .= '/';
+ }
+
+ if ($method_segment !== NULL AND isset(Router::$rsegments[$method_segment]))
+ {
+ // Set method
+ Router::$method = Router::$rsegments[$method_segment];
+
+ if (isset(Router::$rsegments[$method_segment + 1]))
+ {
+ // Set arguments
+ Router::$arguments = array_slice(Router::$rsegments, $method_segment + 1);
+ }
+ }
+
+ // Last chance to set routing before a 404 is triggered
+ Event::run('system.post_routing');
+
+ if (Router::$controller === NULL)
+ {
+ // No controller was found, so no page can be rendered
+ Event::run('system.404');
+ }
+ }
+
+ /**
+ * Attempts to determine the current URI using CLI, GET, PATH_INFO, ORIG_PATH_INFO, or PHP_SELF.
+ *
+ * @return void
+ */
+ public static function find_uri()
+ {
+ if (PHP_SAPI === 'cli')
+ {
+ // Command line requires a bit of hacking
+ if (isset($_SERVER['argv'][1]))
+ {
+ Router::$current_uri = $_SERVER['argv'][1];
+
+ // Remove GET string from segments
+ if (($query = strpos(Router::$current_uri, '?')) !== FALSE)
+ {
+ list (Router::$current_uri, $query) = explode('?', Router::$current_uri, 2);
+
+ // Parse the query string into $_GET
+ parse_str($query, $_GET);
+
+ // Convert $_GET to UTF-8
+ $_GET = utf8::clean($_GET);
+ }
+ }
+ }
+ elseif (isset($_GET['kohana_uri']))
+ {
+ // Use the URI defined in the query string
+ Router::$current_uri = $_GET['kohana_uri'];
+
+ // Remove the URI from $_GET
+ unset($_GET['kohana_uri']);
+
+ // Remove the URI from $_SERVER['QUERY_STRING']
+ $_SERVER['QUERY_STRING'] = preg_replace('~\bkohana_uri\b[^&]*+&?~', '', $_SERVER['QUERY_STRING']);
+ }
+ elseif (isset($_SERVER['PATH_INFO']) AND $_SERVER['PATH_INFO'])
+ {
+ Router::$current_uri = $_SERVER['PATH_INFO'];
+ }
+ elseif (isset($_SERVER['ORIG_PATH_INFO']) AND $_SERVER['ORIG_PATH_INFO'])
+ {
+ Router::$current_uri = $_SERVER['ORIG_PATH_INFO'];
+ }
+ elseif (isset($_SERVER['PHP_SELF']) AND $_SERVER['PHP_SELF'])
+ {
+ Router::$current_uri = $_SERVER['PHP_SELF'];
+
+ if (($strpos_fc = strpos(Router::$current_uri, KOHANA)) !== FALSE)
+ {
+ // Remove the front controller from the current uri
+ Router::$current_uri = substr(Router::$current_uri, $strpos_fc + strlen(KOHANA));
+ }
+ }
+
+ // Remove slashes from the start and end of the URI
+ Router::$current_uri = trim(Router::$current_uri, '/');
+
+ if (Router::$current_uri !== '')
+ {
+ if ($suffix = Kohana::config('core.url_suffix') AND strpos(Router::$current_uri, $suffix) !== FALSE)
+ {
+ // Remove the URL suffix
+ Router::$current_uri = preg_replace('#'.preg_quote($suffix).'$#u', '', Router::$current_uri);
+
+ // Set the URL suffix
+ Router::$url_suffix = $suffix;
+ }
+
+ // Reduce multiple slashes into single slashes
+ Router::$current_uri = preg_replace('#//+#', '/', Router::$current_uri);
+ }
+ }
+
+ /**
+ * Generates routed URI from given URI.
+ *
+ * @param string URI to convert
+ * @return string Routed uri
+ */
+ public static function routed_uri($uri)
+ {
+ if (Router::$routes === NULL)
+ {
+ // Load routes
+ Router::$routes = Kohana::config('routes');
+ }
+
+ // Prepare variables
+ $routed_uri = $uri = trim($uri, '/');
+
+ if (isset(Router::$routes[$uri]))
+ {
+ // Literal match, no need for regex
+ $routed_uri = Router::$routes[$uri];
+ }
+ else
+ {
+ // Loop through the routes and see if anything matches
+ foreach (Router::$routes as $key => $val)
+ {
+ if ($key === '_default') continue;
+
+ // Trim slashes
+ $key = trim($key, '/');
+ $val = trim($val, '/');
+
+ if (preg_match('#^'.$key.'$#u', $uri))
+ {
+ if (strpos($val, '$') !== FALSE)
+ {
+ // Use regex routing
+ $routed_uri = preg_replace('#^'.$key.'$#u', $val, $uri);
+ }
+ else
+ {
+ // Standard routing
+ $routed_uri = $val;
+ }
+
+ // A valid route has been found
+ break;
+ }
+ }
+ }
+
+ if (isset(Router::$routes[$routed_uri]))
+ {
+ // Check for double routing (without regex)
+ $routed_uri = Router::$routes[$routed_uri];
+ }
+
+ return trim($routed_uri, '/');
+ }
+
+} // End Router \ No newline at end of file
diff --git a/system/libraries/Session.php b/system/libraries/Session.php
new file mode 100644
index 00000000..e03f5dff
--- /dev/null
+++ b/system/libraries/Session.php
@@ -0,0 +1,458 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Session library.
+ *
+ * $Id: Session.php 4073 2009-03-13 17:53:58Z Shadowhand $
+ *
+ * @package Core
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class Session_Core {
+
+ // Session singleton
+ protected static $instance;
+
+ // Protected key names (cannot be set by the user)
+ protected static $protect = array('session_id', 'user_agent', 'last_activity', 'ip_address', 'total_hits', '_kf_flash_');
+
+ // Configuration and driver
+ protected static $config;
+ protected static $driver;
+
+ // Flash variables
+ protected static $flash;
+
+ // Input library
+ protected $input;
+
+ /**
+ * Singleton instance of Session.
+ */
+ public static function instance()
+ {
+ if (Session::$instance == NULL)
+ {
+ // Create a new instance
+ new Session;
+ }
+
+ return Session::$instance;
+ }
+
+ /**
+ * On first session instance creation, sets up the driver and creates session.
+ */
+ public function __construct()
+ {
+ $this->input = Input::instance();
+
+ // This part only needs to be run once
+ if (Session::$instance === NULL)
+ {
+ // Load config
+ Session::$config = Kohana::config('session');
+
+ // Makes a mirrored array, eg: foo=foo
+ Session::$protect = array_combine(Session::$protect, Session::$protect);
+
+ // Configure garbage collection
+ ini_set('session.gc_probability', (int) Session::$config['gc_probability']);
+ ini_set('session.gc_divisor', 100);
+ ini_set('session.gc_maxlifetime', (Session::$config['expiration'] == 0) ? 86400 : Session::$config['expiration']);
+
+ // Create a new session
+ $this->create();
+
+ if (Session::$config['regenerate'] > 0 AND ($_SESSION['total_hits'] % Session::$config['regenerate']) === 0)
+ {
+ // Regenerate session id and update session cookie
+ $this->regenerate();
+ }
+ else
+ {
+ // Always update session cookie to keep the session alive
+ cookie::set(Session::$config['name'], $_SESSION['session_id'], Session::$config['expiration']);
+ }
+
+ // Close the session just before sending the headers, so that
+ // the session cookie(s) can be written.
+ Event::add('system.send_headers', array($this, 'write_close'));
+
+ // Make sure that sessions are closed before exiting
+ register_shutdown_function(array($this, 'write_close'));
+
+ // Singleton instance
+ Session::$instance = $this;
+ }
+
+ Kohana::log('debug', 'Session Library initialized');
+ }
+
+ /**
+ * Get the session id.
+ *
+ * @return string
+ */
+ public function id()
+ {
+ return $_SESSION['session_id'];
+ }
+
+ /**
+ * Create a new session.
+ *
+ * @param array variables to set after creation
+ * @return void
+ */
+ public function create($vars = NULL)
+ {
+ // Destroy any current sessions
+ $this->destroy();
+
+ if (Session::$config['driver'] !== 'native')
+ {
+ // Set driver name
+ $driver = 'Session_'.ucfirst(Session::$config['driver']).'_Driver';
+
+ // Load the driver
+ if ( ! Kohana::auto_load($driver))
+ throw new Kohana_Exception('core.driver_not_found', Session::$config['driver'], get_class($this));
+
+ // Initialize the driver
+ Session::$driver = new $driver();
+
+ // Validate the driver
+ if ( ! (Session::$driver instanceof Session_Driver))
+ throw new Kohana_Exception('core.driver_implements', Session::$config['driver'], get_class($this), 'Session_Driver');
+
+ // Register non-native driver as the session handler
+ session_set_save_handler
+ (
+ array(Session::$driver, 'open'),
+ array(Session::$driver, 'close'),
+ array(Session::$driver, 'read'),
+ array(Session::$driver, 'write'),
+ array(Session::$driver, 'destroy'),
+ array(Session::$driver, 'gc')
+ );
+ }
+
+ // Validate the session name
+ if ( ! preg_match('~^(?=.*[a-z])[a-z0-9_]++$~iD', Session::$config['name']))
+ throw new Kohana_Exception('session.invalid_session_name', Session::$config['name']);
+
+ // Name the session, this will also be the name of the cookie
+ session_name(Session::$config['name']);
+
+ // Set the session cookie parameters
+ session_set_cookie_params
+ (
+ Session::$config['expiration'],
+ Kohana::config('cookie.path'),
+ Kohana::config('cookie.domain'),
+ Kohana::config('cookie.secure'),
+ Kohana::config('cookie.httponly')
+ );
+
+ // Start the session!
+ session_start();
+
+ // Put session_id in the session variable
+ $_SESSION['session_id'] = session_id();
+
+ // Set defaults
+ if ( ! isset($_SESSION['_kf_flash_']))
+ {
+ $_SESSION['total_hits'] = 0;
+ $_SESSION['_kf_flash_'] = array();
+
+ $_SESSION['user_agent'] = Kohana::$user_agent;
+ $_SESSION['ip_address'] = $this->input->ip_address();
+ }
+
+ // Set up flash variables
+ Session::$flash =& $_SESSION['_kf_flash_'];
+
+ // Increase total hits
+ $_SESSION['total_hits'] += 1;
+
+ // Validate data only on hits after one
+ if ($_SESSION['total_hits'] > 1)
+ {
+ // Validate the session
+ foreach (Session::$config['validate'] as $valid)
+ {
+ switch ($valid)
+ {
+ // Check user agent for consistency
+ case 'user_agent':
+ if ($_SESSION[$valid] !== Kohana::$user_agent)
+ return $this->create();
+ break;
+
+ // Check ip address for consistency
+ case 'ip_address':
+ if ($_SESSION[$valid] !== $this->input->$valid())
+ return $this->create();
+ break;
+
+ // Check expiration time to prevent users from manually modifying it
+ case 'expiration':
+ if (time() - $_SESSION['last_activity'] > ini_get('session.gc_maxlifetime'))
+ return $this->create();
+ break;
+ }
+ }
+ }
+
+ // Expire flash keys
+ $this->expire_flash();
+
+ // Update last activity
+ $_SESSION['last_activity'] = time();
+
+ // Set the new data
+ Session::set($vars);
+ }
+
+ /**
+ * Regenerates the global session id.
+ *
+ * @return void
+ */
+ public function regenerate()
+ {
+ if (Session::$config['driver'] === 'native')
+ {
+ // Generate a new session id
+ // Note: also sets a new session cookie with the updated id
+ session_regenerate_id(TRUE);
+
+ // Update session with new id
+ $_SESSION['session_id'] = session_id();
+ }
+ else
+ {
+ // Pass the regenerating off to the driver in case it wants to do anything special
+ $_SESSION['session_id'] = Session::$driver->regenerate();
+ }
+
+ // Get the session name
+ $name = session_name();
+
+ if (isset($_COOKIE[$name]))
+ {
+ // Change the cookie value to match the new session id to prevent "lag"
+ $_COOKIE[$name] = $_SESSION['session_id'];
+ }
+ }
+
+ /**
+ * Destroys the current session.
+ *
+ * @return void
+ */
+ public function destroy()
+ {
+ if (session_id() !== '')
+ {
+ // Get the session name
+ $name = session_name();
+
+ // Destroy the session
+ session_destroy();
+
+ // Re-initialize the array
+ $_SESSION = array();
+
+ // Delete the session cookie
+ cookie::delete($name);
+ }
+ }
+
+ /**
+ * Runs the system.session_write event, then calls session_write_close.
+ *
+ * @return void
+ */
+ public function write_close()
+ {
+ static $run;
+
+ if ($run === NULL)
+ {
+ $run = TRUE;
+
+ // Run the events that depend on the session being open
+ Event::run('system.session_write');
+
+ // Expire flash keys
+ $this->expire_flash();
+
+ // Close the session
+ session_write_close();
+ }
+ }
+
+ /**
+ * Set a session variable.
+ *
+ * @param string|array key, or array of values
+ * @param mixed value (if keys is not an array)
+ * @return void
+ */
+ public function set($keys, $val = FALSE)
+ {
+ if (empty($keys))
+ return FALSE;
+
+ if ( ! is_array($keys))
+ {
+ $keys = array($keys => $val);
+ }
+
+ foreach ($keys as $key => $val)
+ {
+ if (isset(Session::$protect[$key]))
+ continue;
+
+ // Set the key
+ $_SESSION[$key] = $val;
+ }
+ }
+
+ /**
+ * Set a flash variable.
+ *
+ * @param string|array key, or array of values
+ * @param mixed value (if keys is not an array)
+ * @return void
+ */
+ public function set_flash($keys, $val = FALSE)
+ {
+ if (empty($keys))
+ return FALSE;
+
+ if ( ! is_array($keys))
+ {
+ $keys = array($keys => $val);
+ }
+
+ foreach ($keys as $key => $val)
+ {
+ if ($key == FALSE)
+ continue;
+
+ Session::$flash[$key] = 'new';
+ Session::set($key, $val);
+ }
+ }
+
+ /**
+ * Freshen one, multiple or all flash variables.
+ *
+ * @param string variable key(s)
+ * @return void
+ */
+ public function keep_flash($keys = NULL)
+ {
+ $keys = ($keys === NULL) ? array_keys(Session::$flash) : func_get_args();
+
+ foreach ($keys as $key)
+ {
+ if (isset(Session::$flash[$key]))
+ {
+ Session::$flash[$key] = 'new';
+ }
+ }
+ }
+
+ /**
+ * Expires old flash data and removes it from the session.
+ *
+ * @return void
+ */
+ public function expire_flash()
+ {
+ static $run;
+
+ // Method can only be run once
+ if ($run === TRUE)
+ return;
+
+ if ( ! empty(Session::$flash))
+ {
+ foreach (Session::$flash as $key => $state)
+ {
+ if ($state === 'old')
+ {
+ // Flash has expired
+ unset(Session::$flash[$key], $_SESSION[$key]);
+ }
+ else
+ {
+ // Flash will expire
+ Session::$flash[$key] = 'old';
+ }
+ }
+ }
+
+ // Method has been run
+ $run = TRUE;
+ }
+
+ /**
+ * Get a variable. Access to sub-arrays is supported with key.subkey.
+ *
+ * @param string variable key
+ * @param mixed default value returned if variable does not exist
+ * @return mixed Variable data if key specified, otherwise array containing all session data.
+ */
+ public function get($key = FALSE, $default = FALSE)
+ {
+ if (empty($key))
+ return $_SESSION;
+
+ $result = isset($_SESSION[$key]) ? $_SESSION[$key] : Kohana::key_string($_SESSION, $key);
+
+ return ($result === NULL) ? $default : $result;
+ }
+
+ /**
+ * Get a variable, and delete it.
+ *
+ * @param string variable key
+ * @param mixed default value returned if variable does not exist
+ * @return mixed
+ */
+ public function get_once($key, $default = FALSE)
+ {
+ $return = Session::get($key, $default);
+ Session::delete($key);
+
+ return $return;
+ }
+
+ /**
+ * Delete one or more variables.
+ *
+ * @param string variable key(s)
+ * @return void
+ */
+ public function delete($keys)
+ {
+ $args = func_get_args();
+
+ foreach ($args as $key)
+ {
+ if (isset(Session::$protect[$key]))
+ continue;
+
+ // Unset the key
+ unset($_SESSION[$key]);
+ }
+ }
+
+} // End Session Class
diff --git a/system/libraries/URI.php b/system/libraries/URI.php
new file mode 100644
index 00000000..d9ccdcf7
--- /dev/null
+++ b/system/libraries/URI.php
@@ -0,0 +1,279 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * URI library.
+ *
+ * $Id: URI.php 4072 2009-03-13 17:20:38Z jheathco $
+ *
+ * @package Core
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class URI_Core extends Router {
+
+ /**
+ * Returns a singleton instance of URI.
+ *
+ * @return object
+ */
+ public static function instance()
+ {
+ static $instance;
+
+ if ($instance == NULL)
+ {
+ // Initialize the URI instance
+ $instance = new URI;
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Retrieve a specific URI segment.
+ *
+ * @param integer|string segment number or label
+ * @param mixed default value returned if segment does not exist
+ * @return string
+ */
+ public function segment($index = 1, $default = FALSE)
+ {
+ if (is_string($index))
+ {
+ if (($key = array_search($index, URI::$segments)) === FALSE)
+ return $default;
+
+ $index = $key + 2;
+ }
+
+ $index = (int) $index - 1;
+
+ return isset(URI::$segments[$index]) ? URI::$segments[$index] : $default;
+ }
+
+ /**
+ * Retrieve a specific routed URI segment.
+ *
+ * @param integer|string rsegment number or label
+ * @param mixed default value returned if segment does not exist
+ * @return string
+ */
+ public function rsegment($index = 1, $default = FALSE)
+ {
+ if (is_string($index))
+ {
+ if (($key = array_search($index, URI::$rsegments)) === FALSE)
+ return $default;
+
+ $index = $key + 2;
+ }
+
+ $index = (int) $index - 1;
+
+ return isset(URI::$rsegments[$index]) ? URI::$rsegments[$index] : $default;
+ }
+
+ /**
+ * Retrieve a specific URI argument.
+ * This is the part of the segments that does not indicate controller or method
+ *
+ * @param integer|string argument number or label
+ * @param mixed default value returned if segment does not exist
+ * @return string
+ */
+ public function argument($index = 1, $default = FALSE)
+ {
+ if (is_string($index))
+ {
+ if (($key = array_search($index, URI::$arguments)) === FALSE)
+ return $default;
+
+ $index = $key + 2;
+ }
+
+ $index = (int) $index - 1;
+
+ return isset(URI::$arguments[$index]) ? URI::$arguments[$index] : $default;
+ }
+
+ /**
+ * Returns an array containing all the URI segments.
+ *
+ * @param integer segment offset
+ * @param boolean return an associative array
+ * @return array
+ */
+ public function segment_array($offset = 0, $associative = FALSE)
+ {
+ return $this->build_array(URI::$segments, $offset, $associative);
+ }
+
+ /**
+ * Returns an array containing all the re-routed URI segments.
+ *
+ * @param integer rsegment offset
+ * @param boolean return an associative array
+ * @return array
+ */
+ public function rsegment_array($offset = 0, $associative = FALSE)
+ {
+ return $this->build_array(URI::$rsegments, $offset, $associative);
+ }
+
+ /**
+ * Returns an array containing all the URI arguments.
+ *
+ * @param integer segment offset
+ * @param boolean return an associative array
+ * @return array
+ */
+ public function argument_array($offset = 0, $associative = FALSE)
+ {
+ return $this->build_array(URI::$arguments, $offset, $associative);
+ }
+
+ /**
+ * Creates a simple or associative array from an array and an offset.
+ * Used as a helper for (r)segment_array and argument_array.
+ *
+ * @param array array to rebuild
+ * @param integer offset to start from
+ * @param boolean create an associative array
+ * @return array
+ */
+ public function build_array($array, $offset = 0, $associative = FALSE)
+ {
+ // Prevent the keys from being improperly indexed
+ array_unshift($array, 0);
+
+ // Slice the array, preserving the keys
+ $array = array_slice($array, $offset + 1, count($array) - 1, TRUE);
+
+ if ($associative === FALSE)
+ return $array;
+
+ $associative = array();
+ $pairs = array_chunk($array, 2);
+
+ foreach ($pairs as $pair)
+ {
+ // Add the key/value pair to the associative array
+ $associative[$pair[0]] = isset($pair[1]) ? $pair[1] : '';
+ }
+
+ return $associative;
+ }
+
+ /**
+ * Returns the complete URI as a string.
+ *
+ * @return string
+ */
+ public function string()
+ {
+ return URI::$current_uri;
+ }
+
+ /**
+ * Magic method for converting an object to a string.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return URI::$current_uri;
+ }
+
+ /**
+ * Returns the total number of URI segments.
+ *
+ * @return integer
+ */
+ public function total_segments()
+ {
+ return count(URI::$segments);
+ }
+
+ /**
+ * Returns the total number of re-routed URI segments.
+ *
+ * @return integer
+ */
+ public function total_rsegments()
+ {
+ return count(URI::$rsegments);
+ }
+
+ /**
+ * Returns the total number of URI arguments.
+ *
+ * @return integer
+ */
+ public function total_arguments()
+ {
+ return count(URI::$arguments);
+ }
+
+ /**
+ * Returns the last URI segment.
+ *
+ * @param mixed default value returned if segment does not exist
+ * @return string
+ */
+ public function last_segment($default = FALSE)
+ {
+ if (($end = $this->total_segments()) < 1)
+ return $default;
+
+ return URI::$segments[$end - 1];
+ }
+
+ /**
+ * Returns the last re-routed URI segment.
+ *
+ * @param mixed default value returned if segment does not exist
+ * @return string
+ */
+ public function last_rsegment($default = FALSE)
+ {
+ if (($end = $this->total_segments()) < 1)
+ return $default;
+
+ return URI::$rsegments[$end - 1];
+ }
+
+ /**
+ * Returns the path to the current controller (not including the actual
+ * controller), as a web path.
+ *
+ * @param boolean return a full url, or only the path specifically
+ * @return string
+ */
+ public function controller_path($full = TRUE)
+ {
+ return ($full) ? url::site(URI::$controller_path) : URI::$controller_path;
+ }
+
+ /**
+ * Returns the current controller, as a web path.
+ *
+ * @param boolean return a full url, or only the controller specifically
+ * @return string
+ */
+ public function controller($full = TRUE)
+ {
+ return ($full) ? url::site(URI::$controller_path.URI::$controller) : URI::$controller;
+ }
+
+ /**
+ * Returns the current method, as a web path.
+ *
+ * @param boolean return a full url, or only the method specifically
+ * @return string
+ */
+ public function method($full = TRUE)
+ {
+ return ($full) ? url::site(URI::$controller_path.URI::$controller.'/'.URI::$method) : URI::$method;
+ }
+
+} // End URI Class
diff --git a/system/libraries/Validation.php b/system/libraries/Validation.php
new file mode 100644
index 00000000..5a48bfc5
--- /dev/null
+++ b/system/libraries/Validation.php
@@ -0,0 +1,826 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Validation library.
+ *
+ * $Id: Validation.php 4120 2009-03-25 19:22:31Z jheathco $
+ *
+ * @package Validation
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class Validation_Core extends ArrayObject {
+
+ // Filters
+ protected $pre_filters = array();
+ protected $post_filters = array();
+
+ // Rules and callbacks
+ protected $rules = array();
+ protected $callbacks = array();
+
+ // Rules that are allowed to run on empty fields
+ protected $empty_rules = array('required', 'matches');
+
+ // Errors
+ protected $errors = array();
+ protected $messages = array();
+
+ // Fields that are expected to be arrays
+ protected $array_fields = array();
+
+ // Checks if there is data to validate.
+ protected $submitted;
+
+ /**
+ * Creates a new Validation instance.
+ *
+ * @param array array to use for validation
+ * @return object
+ */
+ public static function factory(array $array)
+ {
+ return new Validation($array);
+ }
+
+ /**
+ * Sets the unique "any field" key and creates an ArrayObject from the
+ * passed array.
+ *
+ * @param array array to validate
+ * @return void
+ */
+ public function __construct(array $array)
+ {
+ // The array is submitted if the array is not empty
+ $this->submitted = ! empty($array);
+
+ parent::__construct($array, ArrayObject::ARRAY_AS_PROPS | ArrayObject::STD_PROP_LIST);
+ }
+
+ /**
+ * Magic clone method, clears errors and messages.
+ *
+ * @return void
+ */
+ public function __clone()
+ {
+ $this->errors = array();
+ $this->messages = array();
+ }
+
+ /**
+ * Create a copy of the current validation rules and change the array.
+ *
+ * @chainable
+ * @param array new array to validate
+ * @return Validation
+ */
+ public function copy(array $array)
+ {
+ $copy = clone $this;
+
+ $copy->exchangeArray($array);
+
+ return $copy;
+ }
+
+ /**
+ * Test if the data has been submitted.
+ *
+ * @return boolean
+ */
+ public function submitted($value = NULL)
+ {
+ if (is_bool($value))
+ {
+ $this->submitted = $value;
+ }
+
+ return $this->submitted;
+ }
+
+ /**
+ * Returns an array of all the field names that have filters, rules, or callbacks.
+ *
+ * @return array
+ */
+ public function field_names()
+ {
+ // All the fields that are being validated
+ $fields = array_keys(array_merge
+ (
+ $this->pre_filters,
+ $this->rules,
+ $this->callbacks,
+ $this->post_filters
+ ));
+
+ // Remove wildcard fields
+ $fields = array_diff($fields, array('*'));
+
+ return $fields;
+ }
+
+ /**
+ * Returns the array values of the current object.
+ *
+ * @return array
+ */
+ public function as_array()
+ {
+ return $this->getArrayCopy();
+ }
+
+ /**
+ * Returns the ArrayObject values, removing all inputs without rules.
+ * To choose specific inputs, list the field name as arguments.
+ *
+ * @param boolean return only fields with filters, rules, and callbacks
+ * @return array
+ */
+ public function safe_array()
+ {
+ // Load choices
+ $choices = func_get_args();
+ $choices = empty($choices) ? NULL : array_combine($choices, $choices);
+
+ // Get field names
+ $fields = $this->field_names();
+
+ $safe = array();
+ foreach ($fields as $field)
+ {
+ if ($choices === NULL OR isset($choices[$field]))
+ {
+ if (isset($this[$field]))
+ {
+ $value = $this[$field];
+
+ if (is_object($value))
+ {
+ // Convert the value back into an array
+ $value = $value->getArrayCopy();
+ }
+ }
+ else
+ {
+ // Even if the field is not in this array, it must be set
+ $value = NULL;
+ }
+
+ // Add the field to the array
+ $safe[$field] = $value;
+ }
+ }
+
+ return $safe;
+ }
+
+ /**
+ * Add additional rules that will forced, even for empty fields. All arguments
+ * passed will be appended to the list.
+ *
+ * @chainable
+ * @param string rule name
+ * @return object
+ */
+ public function allow_empty_rules($rules)
+ {
+ // Any number of args are supported
+ $rules = func_get_args();
+
+ // Merge the allowed rules
+ $this->empty_rules = array_merge($this->empty_rules, $rules);
+
+ return $this;
+ }
+
+ /**
+ * Converts a filter, rule, or callback into a fully-qualified callback array.
+ *
+ * @return mixed
+ */
+ protected function callback($callback)
+ {
+ if (is_string($callback))
+ {
+ if (strpos($callback, '::') !== FALSE)
+ {
+ $callback = explode('::', $callback);
+ }
+ elseif (function_exists($callback))
+ {
+ // No need to check if the callback is a method
+ $callback = $callback;
+ }
+ elseif (method_exists($this, $callback))
+ {
+ // The callback exists in Validation
+ $callback = array($this, $callback);
+ }
+ elseif (method_exists('valid', $callback))
+ {
+ // The callback exists in valid::
+ $callback = array('valid', $callback);
+ }
+ }
+
+ if ( ! is_callable($callback, FALSE))
+ {
+ if (is_array($callback))
+ {
+ if (is_object($callback[0]))
+ {
+ // Object instance syntax
+ $name = get_class($callback[0]).'->'.$callback[1];
+ }
+ else
+ {
+ // Static class syntax
+ $name = $callback[0].'::'.$callback[1];
+ }
+ }
+ else
+ {
+ // Function syntax
+ $name = $callback;
+ }
+
+ throw new Kohana_Exception('validation.not_callable', $name);
+ }
+
+ return $callback;
+ }
+
+ /**
+ * Add a pre-filter to one or more inputs. Pre-filters are applied before
+ * rules or callbacks are executed.
+ *
+ * @chainable
+ * @param callback filter
+ * @param string fields to apply filter to, use TRUE for all fields
+ * @return object
+ */
+ public function pre_filter($filter, $field = TRUE)
+ {
+ if ($field === TRUE OR $field === '*')
+ {
+ // Use wildcard
+ $fields = array('*');
+ }
+ else
+ {
+ // Add the filter to specific inputs
+ $fields = func_get_args();
+ $fields = array_slice($fields, 1);
+ }
+
+ // Convert to a proper callback
+ $filter = $this->callback($filter);
+
+ foreach ($fields as $field)
+ {
+ // Add the filter to specified field
+ $this->pre_filters[$field][] = $filter;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add a post-filter to one or more inputs. Post-filters are applied after
+ * rules and callbacks have been executed.
+ *
+ * @chainable
+ * @param callback filter
+ * @param string fields to apply filter to, use TRUE for all fields
+ * @return object
+ */
+ public function post_filter($filter, $field = TRUE)
+ {
+ if ($field === TRUE)
+ {
+ // Use wildcard
+ $fields = array('*');
+ }
+ else
+ {
+ // Add the filter to specific inputs
+ $fields = func_get_args();
+ $fields = array_slice($fields, 1);
+ }
+
+ // Convert to a proper callback
+ $filter = $this->callback($filter);
+
+ foreach ($fields as $field)
+ {
+ // Add the filter to specified field
+ $this->post_filters[$field][] = $filter;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add rules to a field. Validation rules may only return TRUE or FALSE and
+ * can not manipulate the value of a field.
+ *
+ * @chainable
+ * @param string field name
+ * @param callback rules (one or more arguments)
+ * @return object
+ */
+ public function add_rules($field, $rules)
+ {
+ // Get the rules
+ $rules = func_get_args();
+ $rules = array_slice($rules, 1);
+
+ if ($field === TRUE)
+ {
+ // Use wildcard
+ $field = '*';
+ }
+
+ foreach ($rules as $rule)
+ {
+ // Arguments for rule
+ $args = NULL;
+
+ if (is_string($rule))
+ {
+ if (preg_match('/^([^\[]++)\[(.+)\]$/', $rule, $matches))
+ {
+ // Split the rule into the function and args
+ $rule = $matches[1];
+ $args = preg_split('/(?<!\\\\),\s*/', $matches[2]);
+
+ // Replace escaped comma with comma
+ $args = str_replace('\,', ',', $args);
+ }
+ }
+
+ if ($rule === 'is_array')
+ {
+ // This field is expected to be an array
+ $this->array_fields[$field] = $field;
+ }
+
+ // Convert to a proper callback
+ $rule = $this->callback($rule);
+
+ // Add the rule, with args, to the field
+ $this->rules[$field][] = array($rule, $args);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add callbacks to a field. Callbacks must accept the Validation object
+ * and the input name. Callback returns are not processed.
+ *
+ * @chainable
+ * @param string field name
+ * @param callbacks callbacks (unlimited number)
+ * @return object
+ */
+ public function add_callbacks($field, $callbacks)
+ {
+ // Get all callbacks as an array
+ $callbacks = func_get_args();
+ $callbacks = array_slice($callbacks, 1);
+
+ if ($field === TRUE)
+ {
+ // Use wildcard
+ $field = '*';
+ }
+
+ foreach ($callbacks as $callback)
+ {
+ // Convert to a proper callback
+ $callback = $this->callback($callback);
+
+ // Add the callback to specified field
+ $this->callbacks[$field][] = $callback;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Validate by processing pre-filters, rules, callbacks, and post-filters.
+ * All fields that have filters, rules, or callbacks will be initialized if
+ * they are undefined. Validation will only be run if there is data already
+ * in the array.
+ *
+ * @param object Validation object, used only for recursion
+ * @param object name of field for errors
+ * @return bool
+ */
+ public function validate($object = NULL, $field_name = NULL)
+ {
+ if ($object === NULL)
+ {
+ // Use the current object
+ $object = $this;
+ }
+
+ // Get all field names
+ $fields = $this->field_names();
+
+ // Copy the array from the object, to optimize multiple sets
+ $array = $this->getArrayCopy();
+
+ foreach ($fields as $field)
+ {
+ if ($field === '*')
+ {
+ // Ignore wildcard
+ continue;
+ }
+
+ if ( ! isset($array[$field]))
+ {
+ if (isset($this->array_fields[$field]))
+ {
+ // This field must be an array
+ $array[$field] = array();
+ }
+ else
+ {
+ $array[$field] = NULL;
+ }
+ }
+ }
+
+ // Swap the array back into the object
+ $this->exchangeArray($array);
+
+ // Get all defined field names
+ $fields = array_keys($array);
+
+ foreach ($this->pre_filters as $field => $callbacks)
+ {
+ foreach ($callbacks as $callback)
+ {
+ if ($field === '*')
+ {
+ foreach ($fields as $f)
+ {
+ $this[$f] = is_array($this[$f]) ? array_map($callback, $this[$f]) : call_user_func($callback, $this[$f]);
+ }
+ }
+ else
+ {
+ $this[$field] = is_array($this[$field]) ? array_map($callback, $this[$field]) : call_user_func($callback, $this[$field]);
+ }
+ }
+ }
+
+ if ($this->submitted === FALSE)
+ return FALSE;
+
+ foreach ($this->rules as $field => $callbacks)
+ {
+ foreach ($callbacks as $callback)
+ {
+ // Separate the callback and arguments
+ list ($callback, $args) = $callback;
+
+ // Function or method name of the rule
+ $rule = is_array($callback) ? $callback[1] : $callback;
+
+ if ($field === '*')
+ {
+ foreach ($fields as $f)
+ {
+ // Note that continue, instead of break, is used when
+ // applying rules using a wildcard, so that all fields
+ // will be validated.
+
+ if (isset($this->errors[$f]))
+ {
+ // Prevent other rules from being evaluated if an error has occurred
+ continue;
+ }
+
+ if (empty($this[$f]) AND ! in_array($rule, $this->empty_rules))
+ {
+ // This rule does not need to be processed on empty fields
+ continue;
+ }
+
+ if ($args === NULL)
+ {
+ if ( ! call_user_func($callback, $this[$f]))
+ {
+ $this->errors[$f] = $rule;
+
+ // Stop validating this field when an error is found
+ continue;
+ }
+ }
+ else
+ {
+ if ( ! call_user_func($callback, $this[$f], $args))
+ {
+ $this->errors[$f] = $rule;
+
+ // Stop validating this field when an error is found
+ continue;
+ }
+ }
+ }
+ }
+ else
+ {
+ if (isset($this->errors[$field]))
+ {
+ // Prevent other rules from being evaluated if an error has occurred
+ break;
+ }
+
+ if ( ! in_array($rule, $this->empty_rules) AND ! $this->required($this[$field]))
+ {
+ // This rule does not need to be processed on empty fields
+ continue;
+ }
+
+ if ($args === NULL)
+ {
+ if ( ! call_user_func($callback, $this[$field]))
+ {
+ $this->errors[$field] = $rule;
+
+ // Stop validating this field when an error is found
+ break;
+ }
+ }
+ else
+ {
+ if ( ! call_user_func($callback, $this[$field], $args))
+ {
+ $this->errors[$field] = $rule;
+
+ // Stop validating this field when an error is found
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ foreach ($this->callbacks as $field => $callbacks)
+ {
+ foreach ($callbacks as $callback)
+ {
+ if ($field === '*')
+ {
+ foreach ($fields as $f)
+ {
+ // Note that continue, instead of break, is used when
+ // applying rules using a wildcard, so that all fields
+ // will be validated.
+
+ if (isset($this->errors[$f]))
+ {
+ // Stop validating this field when an error is found
+ continue;
+ }
+
+ call_user_func($callback, $this, $f);
+ }
+ }
+ else
+ {
+ if (isset($this->errors[$field]))
+ {
+ // Stop validating this field when an error is found
+ break;
+ }
+
+ call_user_func($callback, $this, $field);
+ }
+ }
+ }
+
+ foreach ($this->post_filters as $field => $callbacks)
+ {
+ foreach ($callbacks as $callback)
+ {
+ if ($field === '*')
+ {
+ foreach ($fields as $f)
+ {
+ $this[$f] = is_array($this[$f]) ? array_map($callback, $this[$f]) : call_user_func($callback, $this[$f]);
+ }
+ }
+ else
+ {
+ $this[$field] = is_array($this[$field]) ? array_map($callback, $this[$field]) : call_user_func($callback, $this[$field]);
+ }
+ }
+ }
+
+ // Return TRUE if there are no errors
+ return $this->errors === array();
+ }
+
+ /**
+ * Add an error to an input.
+ *
+ * @chainable
+ * @param string input name
+ * @param string unique error name
+ * @return object
+ */
+ public function add_error($field, $name)
+ {
+ $this->errors[$field] = $name;
+
+ return $this;
+ }
+
+ /**
+ * Sets or returns the message for an input.
+ *
+ * @chainable
+ * @param string input key
+ * @param string message to set
+ * @return string|object
+ */
+ public function message($input = NULL, $message = NULL)
+ {
+ if ($message === NULL)
+ {
+ if ($input === NULL)
+ {
+ $messages = array();
+ $keys = array_keys($this->messages);
+
+ foreach ($keys as $input)
+ {
+ $messages[] = $this->message($input);
+ }
+
+ return implode("\n", $messages);
+ }
+
+ // Return nothing if no message exists
+ if (empty($this->messages[$input]))
+ return '';
+
+ // Return the HTML message string
+ return $this->messages[$input];
+ }
+ else
+ {
+ $this->messages[$input] = $message;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return the errors array.
+ *
+ * @param boolean load errors from a lang file
+ * @return array
+ */
+ public function errors($file = NULL)
+ {
+ if ($file === NULL)
+ {
+ return $this->errors;
+ }
+ else
+ {
+
+ $errors = array();
+ foreach ($this->errors as $input => $error)
+ {
+ // Key for this input error
+ $key = "$file.$input.$error";
+
+ if (($errors[$input] = Kohana::lang($key)) === $key)
+ {
+ // Get the default error message
+ $errors[$input] = Kohana::lang("$file.$input.default");
+ }
+ }
+
+ return $errors;
+ }
+ }
+
+ /**
+ * Rule: required. Generates an error if the field has an empty value.
+ *
+ * @param mixed input value
+ * @return bool
+ */
+ public function required($str)
+ {
+ if (is_object($str) AND $str instanceof ArrayObject)
+ {
+ // Get the array from the ArrayObject
+ $str = $str->getArrayCopy();
+ }
+
+ if (is_array($str))
+ {
+ return ! empty($str);
+ }
+ else
+ {
+ return ! ($str === '' OR $str === NULL OR $str === FALSE);
+ }
+ }
+
+ /**
+ * Rule: matches. Generates an error if the field does not match one or more
+ * other fields.
+ *
+ * @param mixed input value
+ * @param array input names to match against
+ * @return bool
+ */
+ public function matches($str, array $inputs)
+ {
+ foreach ($inputs as $key)
+ {
+ if ($str !== (isset($this[$key]) ? $this[$key] : NULL))
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ /**
+ * Rule: length. Generates an error if the field is too long or too short.
+ *
+ * @param mixed input value
+ * @param array minimum, maximum, or exact length to match
+ * @return bool
+ */
+ public function length($str, array $length)
+ {
+ if ( ! is_string($str))
+ return FALSE;
+
+ $size = utf8::strlen($str);
+ $status = FALSE;
+
+ if (count($length) > 1)
+ {
+ list ($min, $max) = $length;
+
+ if ($size >= $min AND $size <= $max)
+ {
+ $status = TRUE;
+ }
+ }
+ else
+ {
+ $status = ($size === (int) $length[0]);
+ }
+
+ return $status;
+ }
+
+ /**
+ * Rule: depends_on. Generates an error if the field does not depend on one
+ * or more other fields.
+ *
+ * @param mixed field name
+ * @param array field names to check dependency
+ * @return bool
+ */
+ public function depends_on($field, array $fields)
+ {
+ foreach ($fields as $depends_on)
+ {
+ if ( ! isset($this[$depends_on]) OR $this[$depends_on] == NULL)
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ /**
+ * Rule: chars. Generates an error if the field contains characters outside of the list.
+ *
+ * @param string field value
+ * @param array allowed characters
+ * @return bool
+ */
+ public function chars($value, array $chars)
+ {
+ return ! preg_match('![^'.implode('', $chars).']!u', $value);
+ }
+
+} // End Validation
diff --git a/system/libraries/View.php b/system/libraries/View.php
new file mode 100644
index 00000000..2b8471c6
--- /dev/null
+++ b/system/libraries/View.php
@@ -0,0 +1,309 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Loads and displays Kohana view files. Can also handle output of some binary
+ * files, such as image, Javascript, and CSS files.
+ *
+ * $Id: View.php 4072 2009-03-13 17:20:38Z jheathco $
+ *
+ * @package Core
+ * @author Kohana Team
+ * @copyright (c) 2007-2008 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class View_Core {
+
+ // The view file name and type
+ protected $kohana_filename = FALSE;
+ protected $kohana_filetype = FALSE;
+
+ // View variable storage
+ protected $kohana_local_data = array();
+ protected static $kohana_global_data = array();
+
+ /**
+ * Creates a new View using the given parameters.
+ *
+ * @param string view name
+ * @param array pre-load data
+ * @param string type of file: html, css, js, etc.
+ * @return object
+ */
+ public static function factory($name = NULL, $data = NULL, $type = NULL)
+ {
+ return new View($name, $data, $type);
+ }
+
+ /**
+ * Attempts to load a view and pre-load view data.
+ *
+ * @throws Kohana_Exception if the requested view cannot be found
+ * @param string view name
+ * @param array pre-load data
+ * @param string type of file: html, css, js, etc.
+ * @return void
+ */
+ public function __construct($name = NULL, $data = NULL, $type = NULL)
+ {
+ if (is_string($name) AND $name !== '')
+ {
+ // Set the filename
+ $this->set_filename($name, $type);
+ }
+
+ if (is_array($data) AND ! empty($data))
+ {
+ // Preload data using array_merge, to allow user extensions
+ $this->kohana_local_data = array_merge($this->kohana_local_data, $data);
+ }
+ }
+
+ /**
+ * Magic method access to test for view property
+ *
+ * @param string View property to test for
+ * @return boolean
+ */
+ public function __isset($key = NULL)
+ {
+ return $this->is_set($key);
+ }
+
+ /**
+ * Sets the view filename.
+ *
+ * @chainable
+ * @param string view filename
+ * @param string view file type
+ * @return object
+ */
+ public function set_filename($name, $type = NULL)
+ {
+ if ($type == NULL)
+ {
+ // Load the filename and set the content type
+ $this->kohana_filename = Kohana::find_file('views', $name, TRUE);
+ $this->kohana_filetype = EXT;
+ }
+ else
+ {
+ // Check if the filetype is allowed by the configuration
+ if ( ! in_array($type, Kohana::config('view.allowed_filetypes')))
+ throw new Kohana_Exception('core.invalid_filetype', $type);
+
+ // Load the filename and set the content type
+ $this->kohana_filename = Kohana::find_file('views', $name, TRUE, $type);
+ $this->kohana_filetype = Kohana::config('mimes.'.$type);
+
+ if ($this->kohana_filetype == NULL)
+ {
+ // Use the specified type
+ $this->kohana_filetype = $type;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets a view variable.
+ *
+ * @param string|array name of variable or an array of variables
+ * @param mixed value when using a named variable
+ * @return object
+ */
+ public function set($name, $value = NULL)
+ {
+ if (is_array($name))
+ {
+ foreach ($name as $key => $value)
+ {
+ $this->__set($key, $value);
+ }
+ }
+ else
+ {
+ $this->__set($name, $value);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Checks for a property existence in the view locally or globally. Unlike the built in __isset(),
+ * this method can take an array of properties to test simultaneously.
+ *
+ * @param string $key property name to test for
+ * @param array $key array of property names to test for
+ * @return boolean property test result
+ * @return array associative array of keys and boolean test result
+ */
+ public function is_set( $key = FALSE )
+ {
+ // Setup result;
+ $result = FALSE;
+
+ // If key is an array
+ if (is_array($key))
+ {
+ // Set the result to an array
+ $result = array();
+
+ // Foreach key
+ foreach ($key as $property)
+ {
+ // Set the result to an associative array
+ $result[$property] = (array_key_exists($property, $this->kohana_local_data) OR array_key_exists($property, View::$kohana_global_data)) ? TRUE : FALSE;
+ }
+ }
+ else
+ {
+ // Otherwise just check one property
+ $result = (array_key_exists($key, $this->kohana_local_data) OR array_key_exists($key, View::$kohana_global_data)) ? TRUE : FALSE;
+ }
+
+ // Return the result
+ return $result;
+ }
+
+ /**
+ * Sets a bound variable by reference.
+ *
+ * @param string name of variable
+ * @param mixed variable to assign by reference
+ * @return object
+ */
+ public function bind($name, & $var)
+ {
+ $this->kohana_local_data[$name] =& $var;
+
+ return $this;
+ }
+
+ /**
+ * Sets a view global variable.
+ *
+ * @param string|array name of variable or an array of variables
+ * @param mixed value when using a named variable
+ * @return void
+ */
+ public static function set_global($name, $value = NULL)
+ {
+ if (is_array($name))
+ {
+ foreach ($name as $key => $value)
+ {
+ View::$kohana_global_data[$key] = $value;
+ }
+ }
+ else
+ {
+ View::$kohana_global_data[$name] = $value;
+ }
+ }
+
+ /**
+ * Magically sets a view variable.
+ *
+ * @param string variable key
+ * @param string variable value
+ * @return void
+ */
+ public function __set($key, $value)
+ {
+ $this->kohana_local_data[$key] = $value;
+ }
+
+ /**
+ * Magically gets a view variable.
+ *
+ * @param string variable key
+ * @return mixed variable value if the key is found
+ * @return void if the key is not found
+ */
+ public function &__get($key)
+ {
+ if (isset($this->kohana_local_data[$key]))
+ return $this->kohana_local_data[$key];
+
+ if (isset(View::$kohana_global_data[$key]))
+ return View::$kohana_global_data[$key];
+
+ if (isset($this->$key))
+ return $this->$key;
+ }
+
+ /**
+ * Magically converts view object to string.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ try
+ {
+ return $this->render();
+ }
+ catch (Exception $e)
+ {
+ // Display the exception using its internal __toString method
+ return (string) $e;
+ }
+ }
+
+ /**
+ * Renders a view.
+ *
+ * @param boolean set to TRUE to echo the output instead of returning it
+ * @param callback special renderer to pass the output through
+ * @return string if print is FALSE
+ * @return void if print is TRUE
+ */
+ public function render($print = FALSE, $renderer = FALSE)
+ {
+ if (empty($this->kohana_filename))
+ throw new Kohana_Exception('core.view_set_filename');
+
+ if (is_string($this->kohana_filetype))
+ {
+ // Merge global and local data, local overrides global with the same name
+ $data = array_merge(View::$kohana_global_data, $this->kohana_local_data);
+
+ // Load the view in the controller for access to $this
+ $output = Kohana::$instance->_kohana_load_view($this->kohana_filename, $data);
+
+ if ($renderer !== FALSE AND is_callable($renderer, TRUE))
+ {
+ // Pass the output through the user defined renderer
+ $output = call_user_func($renderer, $output);
+ }
+
+ if ($print === TRUE)
+ {
+ // Display the output
+ echo $output;
+ return;
+ }
+ }
+ else
+ {
+ // Set the content type and size
+ header('Content-Type: '.$this->kohana_filetype[0]);
+
+ if ($print === TRUE)
+ {
+ if ($file = fopen($this->kohana_filename, 'rb'))
+ {
+ // Display the output
+ fpassthru($file);
+ fclose($file);
+ }
+ return;
+ }
+
+ // Fetch the file contents
+ $output = file_get_contents($this->kohana_filename);
+ }
+
+ return $output;
+ }
+} // End View \ No newline at end of file
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