summaryrefslogtreecommitdiff
path: root/system/libraries
diff options
context:
space:
mode:
Diffstat (limited to 'system/libraries')
-rw-r--r--system/libraries/Cache.php248
-rw-r--r--system/libraries/Cache_Exception.php11
-rw-r--r--system/libraries/Controller.php44
-rw-r--r--system/libraries/Database.php648
-rw-r--r--system/libraries/Database_Builder.php1241
-rw-r--r--system/libraries/Database_Cache_Result.php81
-rw-r--r--system/libraries/Database_Exception.php15
-rw-r--r--system/libraries/Database_Expression.php23
-rw-r--r--system/libraries/Database_Mysql.php226
-rw-r--r--system/libraries/Database_Mysql_Result.php174
-rw-r--r--system/libraries/Database_Mysqli.php90
-rw-r--r--system/libraries/Database_Mysqli_Result.php175
-rw-r--r--system/libraries/Database_Query.php95
-rw-r--r--system/libraries/Database_Result.php170
-rw-r--r--system/libraries/Encrypt.php176
-rw-r--r--system/libraries/I18n.php100
-rw-r--r--system/libraries/Image.php501
-rw-r--r--system/libraries/Input.php507
-rw-r--r--system/libraries/Kohana_404_Exception.php56
-rw-r--r--system/libraries/Kohana_Log.php90
-rw-r--r--system/libraries/Kohana_PHP_Exception.php99
-rw-r--r--system/libraries/Kohana_User_Exception.php30
-rw-r--r--system/libraries/Model.php62
-rw-r--r--system/libraries/ORM.php1528
-rw-r--r--system/libraries/ORM_Iterator.php266
-rw-r--r--system/libraries/ORM_Validation_Exception.php24
-rw-r--r--system/libraries/Profiler.php306
-rw-r--r--system/libraries/Profiler_Table.php67
-rw-r--r--system/libraries/Router.php315
-rw-r--r--system/libraries/Session.php500
-rw-r--r--system/libraries/URI.php279
-rw-r--r--system/libraries/Validation.php815
-rw-r--r--system/libraries/View.php329
-rw-r--r--system/libraries/drivers/Cache.php42
-rw-r--r--system/libraries/drivers/Cache/File.php255
-rw-r--r--system/libraries/drivers/Cache/Memcache.php132
-rw-r--r--system/libraries/drivers/Cache/Xcache.php161
-rw-r--r--system/libraries/drivers/Config.php257
-rw-r--r--system/libraries/drivers/Config/Array.php83
-rw-r--r--system/libraries/drivers/Image.php158
-rw-r--r--system/libraries/drivers/Image/GD.php440
-rw-r--r--system/libraries/drivers/Image/GraphicsMagick.php225
-rw-r--r--system/libraries/drivers/Image/ImageMagick.php233
-rw-r--r--system/libraries/drivers/Log.php22
-rw-r--r--system/libraries/drivers/Log/Database.php40
-rw-r--r--system/libraries/drivers/Log/File.php44
-rw-r--r--system/libraries/drivers/Log/Syslog.php34
-rw-r--r--system/libraries/drivers/Session.php70
-rw-r--r--system/libraries/drivers/Session/Cache.php108
-rw-r--r--system/libraries/drivers/Session/Cookie.php83
-rw-r--r--system/libraries/drivers/Session/Database.php178
51 files changed, 11856 insertions, 0 deletions
diff --git a/system/libraries/Cache.php b/system/libraries/Cache.php
new file mode 100644
index 0000000..c7954d3
--- /dev/null
+++ b/system/libraries/Cache.php
@@ -0,0 +1,248 @@
+<?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.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Cache_Core {
+
+ protected static $instances = array();
+
+ // 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 Cache_Exception('The :group: group is not defined in your configuration.', array(':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 Cache_Exception('The :driver: driver for the :class: library could not be found',
+ array(':driver:' => $this->config['driver'], ':class:' => get_class($this)));
+
+ // Initialize the driver
+ $this->driver = new $driver($this->config['params']);
+
+ // Validate the driver
+ if ( ! ($this->driver instanceof Cache_Driver))
+ throw new Cache_Exception('The :driver: driver for the :library: library must implement the :interface: interface',
+ array(':driver:' => $this->config['driver'], ':library:' => get_class($this), ':interface:' => 'Cache_Driver'));
+
+ Kohana_Log::add('debug', 'Cache Library initialized');
+ }
+
+ /**
+ * Set cache items
+ */
+ public function set($key, $value = NULL, $tags = NULL, $lifetime = NULL)
+ {
+ if ($lifetime === NULL)
+ {
+ $lifetime = $this->config['lifetime'];
+ }
+
+ if ( ! is_array($key))
+ {
+ $key = array($key => $value);
+ }
+
+ if ($this->config['prefix'] !== NULL)
+ {
+ $key = $this->add_prefix($key);
+
+ if ($tags !== NULL)
+ {
+ $tags = $this->add_prefix($tags, FALSE);
+ }
+ }
+
+ return $this->driver->set($key, $tags, $lifetime);
+ }
+
+ /**
+ * Get a cache items by key
+ */
+ public function get($keys)
+ {
+ $single = FALSE;
+
+ if ( ! is_array($keys))
+ {
+ $keys = array($keys);
+ $single = TRUE;
+ }
+
+ if ($this->config['prefix'] !== NULL)
+ {
+ $keys = $this->add_prefix($keys, FALSE);
+
+ if ( ! $single)
+ {
+ return $this->strip_prefix($this->driver->get($keys, $single));
+ }
+
+ }
+
+ return $this->driver->get($keys, $single);
+ }
+
+ /**
+ * Get cache items by tags
+ */
+ public function get_tag($tags)
+ {
+ if ( ! is_array($tags))
+ {
+ $tags = array($tags);
+ }
+
+ if ($this->config['prefix'] !== NULL)
+ {
+ $tags = $this->add_prefix($tags, FALSE);
+ return $this->strip_prefix($this->driver->get_tag($tags));
+ }
+ else
+ {
+ return $this->driver->get_tag($tags);
+ }
+ }
+
+ /**
+ * Delete cache item by key
+ */
+ public function delete($keys)
+ {
+ if ( ! is_array($keys))
+ {
+ $keys = array($keys);
+ }
+
+ if ($this->config['prefix'] !== NULL)
+ {
+ $keys = $this->add_prefix($keys, FALSE);
+ }
+
+ return $this->driver->delete($keys);
+ }
+
+ /**
+ * Delete cache items by tag
+ */
+ public function delete_tag($tags)
+ {
+ if ( ! is_array($tags))
+ {
+ $tags = array($tags);
+ }
+
+ if ($this->config['prefix'] !== NULL)
+ {
+ $tags = $this->add_prefix($tags, FALSE);
+ }
+
+ return $this->driver->delete_tag($tags);
+ }
+
+ /**
+ * Empty the cache
+ */
+ public function delete_all()
+ {
+ return $this->driver->delete_all();
+ }
+
+ /**
+ * Add a prefix to keys or tags
+ */
+ protected function add_prefix($array, $to_key = TRUE)
+ {
+ $out = array();
+
+ foreach($array as $key => $value)
+ {
+ if ($to_key)
+ {
+ $out[$this->config['prefix'].$key] = $value;
+ }
+ else
+ {
+ $out[$key] = $this->config['prefix'].$value;
+ }
+ }
+
+ return $out;
+ }
+
+ /**
+ * Strip a prefix to keys or tags
+ */
+ protected function strip_prefix($array)
+ {
+ $out = array();
+
+ $start = strlen($this->config['prefix']);
+
+ foreach($array as $key => $value)
+ {
+ $out[substr($key, $start)] = $value;
+ }
+
+ return $out;
+ }
+
+} // End Cache Library \ No newline at end of file
diff --git a/system/libraries/Cache_Exception.php b/system/libraries/Cache_Exception.php
new file mode 100644
index 0000000..706dc09
--- /dev/null
+++ b/system/libraries/Cache_Exception.php
@@ -0,0 +1,11 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+
+class Cache_Exception_Core extends Kohana_Exception {}
+// End Kohana User Exception
diff --git a/system/libraries/Controller.php b/system/libraries/Controller.php
new file mode 100644
index 0000000..a1139f6
--- /dev/null
+++ b/system/libraries/Controller.php
@@ -0,0 +1,44 @@
+<?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.
+ *
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+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;
+ }
+ }
+
+ /**
+ * 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');
+ }
+
+} // 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 0000000..253bb15
--- /dev/null
+++ b/system/libraries/Database.php
@@ -0,0 +1,648 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Database wrapper.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2008-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+abstract class Database_Core {
+
+ const SELECT = 1;
+ const INSERT = 2;
+ const UPDATE = 3;
+ const DELETE = 4;
+ const CROSS_REQUEST = 5;
+ const PER_REQUEST = 6;
+
+ protected static $instances = array();
+
+ // Global benchmarks
+ public static $benchmarks = array();
+
+ // Last execute query
+ protected $last_query;
+
+ // Configuration array
+ protected $config;
+
+ // Required configuration keys
+ protected $config_required = array();
+
+ // Raw server connection
+ protected $connection;
+
+ // Cache (Cache object for cross-request, array for per-request)
+ protected $cache;
+
+ // Quote character to use for identifiers (tables/columns/aliases)
+ protected $quote = '"';
+
+ /**
+ * Returns a singleton instance of Database.
+ *
+ * @param string Database name
+ * @return Database_Core
+ */
+ public static function instance($name = 'default')
+ {
+ if ( ! isset(Database::$instances[$name]))
+ {
+ // Load the configuration for this database group
+ $config = Kohana::config('database.'.$name);
+
+ if (is_string($config['connection']))
+ {
+ // Parse the DSN into connection array
+ $config['connection'] = Database::parse_dsn($config['connection']);
+ }
+
+ // Set the driver class name
+ $driver = 'Database_'.ucfirst($config['connection']['type']);
+
+ // Create the database connection instance
+ Database::$instances[$name] = new $driver($config);
+ }
+
+ return Database::$instances[$name];
+ }
+
+ /**
+ * Constructs a new Database object
+ *
+ * @param array Database config array
+ * @return Database_Core
+ */
+ protected function __construct(array $config)
+ {
+ // Store the config locally
+ $this->config = $config;
+
+ if ($this->config['cache'] !== FALSE)
+ {
+ if (is_string($this->config['cache']))
+ {
+ // Use Cache library
+ $this->cache = new Cache($this->config['cache']);
+ }
+ elseif ($this->config['cache'] === TRUE)
+ {
+ // Use array
+ $this->cache = array();
+ }
+ }
+ }
+
+ public function __destruct()
+ {
+ $this->disconnect();
+ }
+
+ /**
+ * Connects to the database
+ *
+ * @return void
+ */
+ abstract public function connect();
+
+ /**
+ * Disconnects from the database
+ *
+ * @return void
+ */
+ abstract public function disconnect();
+
+ /**
+ * Sets the character set
+ *
+ * @return void
+ */
+ abstract public function set_charset($charset);
+
+ /**
+ * Executes the query
+ *
+ * @param string SQL
+ * @return Database_Result
+ */
+ abstract public function query_execute($sql);
+
+ /**
+ * Escapes the given value
+ *
+ * @param mixed Value
+ * @return mixed Escaped value
+ */
+ abstract public function escape($value);
+
+ /**
+ * List constraints for the given table
+ *
+ * @param string Table name
+ * @return array
+ */
+ abstract public function list_constraints($table);
+
+ /**
+ * List fields for the given table
+ *
+ * @param string Table name
+ * @return array
+ */
+ abstract public function list_fields($table);
+
+ /**
+ * List tables for the given connection (checks for prefix)
+ *
+ * @return array
+ */
+ abstract public function list_tables();
+
+ /**
+ * Converts the given DSN string to an array of database connection components
+ *
+ * @param string DSN string
+ * @return array
+ */
+ public static function parse_dsn($dsn)
+ {
+ $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('://', $dsn, 2);
+
+ if ($connection[0] === '/')
+ {
+ // Strip leading slash
+ $db['database'] = substr($connection, 1);
+ }
+ else
+ {
+ $connection = parse_url('http://'.$connection);
+
+ if (isset($connection['user']))
+ {
+ $db['user'] = $connection['user'];
+ }
+
+ if (isset($connection['pass']))
+ {
+ $db['pass'] = $connection['pass'];
+ }
+
+ if (isset($connection['port']))
+ {
+ $db['port'] = $connection['port'];
+ }
+
+ if (isset($connection['host']))
+ {
+ if ($connection['host'] === 'unix(')
+ {
+ list($db['socket'], $connection['path']) = explode(')', $connection['path'], 2);
+ }
+ else
+ {
+ $db['host'] = $connection['host'];
+ }
+ }
+
+ if (isset($connection['path']) AND $connection['path'])
+ {
+ // Strip leading slash
+ $db['database'] = substr($connection['path'], 1);
+ }
+ }
+
+ return $db;
+ }
+
+ /**
+ * Returns the last executed query for this database
+ *
+ * @return string
+ */
+ public function last_query()
+ {
+ return $this->last_query;
+ }
+
+ /**
+ * Executes the given query, returning the cached version if enabled
+ *
+ * @param string SQL query
+ * @return Database_Result
+ */
+ public function query($sql)
+ {
+ // Start the benchmark
+ $start = microtime(TRUE);
+
+ if (is_array($this->cache))
+ {
+ $hash = $this->query_hash($sql);
+
+ if (isset($this->cache[$hash]))
+ {
+ // Use cached result
+ $result = $this->cache[$hash];
+
+ // It's from cache
+ $sql .= ' [CACHE]';
+ }
+ else
+ {
+ // No cache, execute query and store in cache
+ $result = $this->cache[$hash] = $this->query_execute($sql);
+ }
+ }
+ else
+ {
+ // Execute the query, cache is off
+ $result = $this->query_execute($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;
+ }
+
+ /**
+ * Performs the query on the cache (and caches it if it's not found)
+ *
+ * @param string query
+ * @param int time-to-live (NULL for Cache default)
+ * @return Database_Cache_Result
+ */
+ public function query_cache($sql, $ttl)
+ {
+ if ( ! $this->cache instanceof Cache)
+ {
+ throw new Database_Exception('Database :name has not been configured to use the Cache library.');
+ }
+
+ // Start the benchmark
+ $start = microtime(TRUE);
+
+ $hash = $this->query_hash($sql);
+
+ if (($data = $this->cache->get($hash)) !== NULL)
+ {
+ // Found in cache, create result
+ $result = new Database_Cache_Result($data, $sql, $this->config['object']);
+
+ // It's from the cache
+ $sql .= ' [CACHE]';
+ }
+ else
+ {
+ // Run the query and return the full array of rows
+ $data = $this->query_execute($sql)->as_array(TRUE);
+
+ // Set the Cache
+ $this->cache->set($hash, $data, NULL, $ttl);
+
+ // Create result
+ $result = new Database_Cache_Result($data, $sql, $this->config['object']);
+ }
+
+ // 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;
+ }
+
+ /**
+ * Generates a hash for the given query
+ *
+ * @param string SQL query string
+ * @return string
+ */
+ protected function query_hash($sql)
+ {
+ return sha1(str_replace("\n", ' ', trim($sql)));
+ }
+
+ /**
+ * Clears the internal query cache.
+ *
+ * @param mixed clear cache by SQL statement, NULL for all, or TRUE for last query
+ * @param integer Type of cache to clear, Database::CROSS_REQUEST or Database::PER_REQUEST
+ * @return Database
+ */
+ public function clear_cache($sql = NULL, $type = NULL)
+ {
+ if ($this->cache instanceof Cache AND ($type == NULL OR $type == Database::CROSS_REQUEST))
+ {
+ // Using cross-request Cache library
+ if ($sql === TRUE)
+ {
+ $this->cache->delete($this->query_hash($this->last_query));
+ }
+ elseif (is_string($sql))
+ {
+ $this->cache->delete($this->query_hash($sql));
+ }
+ else
+ {
+ $this->cache->delete_all();
+ }
+ }
+ elseif (is_array($this->cache) AND ($type == NULL OR $type == Database::PER_REQUEST))
+ {
+ // Using per-request memory cache
+ if ($sql === TRUE)
+ {
+ unset($this->cache[$this->query_hash($this->last_query)]);
+ }
+ elseif (is_string($sql))
+ {
+ unset($this->cache[$this->query_hash($sql)]);
+ }
+ else
+ {
+ $this->cache = array();
+ }
+ }
+ }
+
+ /**
+ * Quotes the given value
+ *
+ * @param mixed value
+ * @return mixed
+ */
+ public function quote($value)
+ {
+ if ( ! $this->config['escape'])
+ return $value;
+
+ if ($value === NULL)
+ {
+ return 'NULL';
+ }
+ elseif ($value === TRUE)
+ {
+ return 'TRUE';
+ }
+ elseif ($value === FALSE)
+ {
+ return 'FALSE';
+ }
+ elseif (is_int($value))
+ {
+ return (int) $value;
+ }
+ elseif ($value instanceof Database_Expression)
+ {
+ return (string) $value;
+ }
+ elseif (is_float($value))
+ {
+ // Convert to non-locale aware float to prevent possible commas
+ return sprintf('%F', $value);
+ }
+
+ return '\''.$this->escape($value).'\'';
+ }
+
+ /**
+ * Quotes a table, adding the table prefix
+ * Reserved characters not allowed in table names for the builder are [ .*] (space, dot, asterisk)
+ *
+ * @param string|array table name or array - 'users u' or array('u' => 'users') both valid
+ * @param string table alias
+ * @return string
+ */
+ public function quote_table($table, $alias = NULL)
+ {
+ if (is_array($table))
+ {
+ // Using array('u' => 'user')
+ list($alias, $table) = each($table);
+ }
+ elseif (strpos(' ', $table) !== FALSE)
+ {
+ // Using format 'user u'
+ list($table, $alias) = explode(' ', $table);
+ }
+
+ if ($table instanceof Database_Expression)
+ {
+ if ($alias)
+ {
+ if ($this->config['escape'])
+ {
+ $alias = $this->quote.$alias.$this->quote;
+ }
+
+ return $table.' AS '.$alias;
+ }
+
+ return (string) $table;
+ }
+
+ if ($this->config['table_prefix'])
+ {
+ $table = $this->config['table_prefix'].$table;
+ }
+
+ if ($alias)
+ {
+ if ($this->config['escape'])
+ {
+ $table = $this->quote.$table.$this->quote;
+ $alias = $this->quote.$alias.$this->quote;
+ }
+
+ return $table.' AS '.$alias;
+ }
+
+ if ($this->config['escape'])
+ {
+ $table = $this->quote.$table.$this->quote;
+ }
+
+ return $table;
+ }
+
+ /**
+ * Quotes column or table.column, adding the table prefix if necessary
+ * Reserved characters not allowed in table names for the builder are [ .*] (space, dot, asterisk)
+ * Complex column names must have table/columns in double quotes, e.g. array('mycount' => 'COUNT("users.id")')
+ *
+ * @param string|array column name or array('u' => 'COUNT("*")')
+ * @param string column alias
+ * @return string
+ */
+ public function quote_column($column, $alias = NULL)
+ {
+ if ($column === '*')
+ return $column;
+
+ if (is_array($column))
+ {
+ list($alias, $column) = each($column);
+ }
+
+ if ($column instanceof Database_Expression)
+ {
+ if ($alias)
+ {
+ if ($this->config['escape'])
+ {
+ $alias = $this->quote.$alias.$this->quote;
+ }
+
+ return $column.' AS '.$alias;
+ }
+
+ return (string) $column;
+ }
+
+ if ($this->config['table_prefix'] AND strpos($column, '.') !== FALSE)
+ {
+ if (strpos($column, '"') !== FALSE)
+ {
+ // Find "table.column" and replace them with "[prefix]table.column"
+ $column = preg_replace('/"([^.]++)\.([^"]++)"/', '"'.$this->config['table_prefix'].'$1.$2"', $column);
+ }
+ else
+ {
+ // Attach table prefix if table.column format
+ $column = $this->config['table_prefix'].$column;
+ }
+ }
+
+ if ($this->config['escape'])
+ {
+ if (strpos($column, '"') === FALSE)
+ {
+ // Quote the column
+ $column = $this->quote.$column.$this->quote;
+ }
+ elseif ($this->quote !== '"')
+ {
+ // Replace double quotes
+ $column = str_replace('"', $this->quote, $column);
+ }
+
+ // Replace . with "."
+ $column = str_replace('.', $this->quote.'.'.$this->quote, $column);
+
+ // Unescape any asterisks
+ $column = str_replace($this->quote.'*'.$this->quote, '*', $column);
+
+ if ($alias)
+ {
+ // Quote the alias
+ return $column.' AS '.$this->quote.$alias.$this->quote;
+ }
+
+ return $column;
+ }
+
+ // Strip double quotes
+ $column = str_replace('"', '', $column);
+
+ if ($alias)
+ return $column.' AS '.$alias;
+
+ return $column;
+ }
+
+ /**
+ * Get the table prefix
+ *
+ * @param string Optional new table prefix to set
+ * @return string
+ */
+ public function table_prefix($new_prefix = NULL)
+ {
+ $prefix = $this->config['table_prefix'];
+
+ if ($new_prefix !== NULL)
+ {
+ // Set a new prefix
+ $this->config['table_prefix'] = $new_prefix;
+ }
+
+ return $prefix;
+ }
+
+ /**
+ * 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 = trim($str);
+
+ if (($open = strpos($str, '(')) !== FALSE)
+ {
+ // Closing bracket
+ $close = strpos($str, ')', $open);
+
+ // Length without brackets
+ $length = substr($str, $open + 1, $close - 1 - $open);
+
+ // Type without the length
+ $type = substr($str, 0, $open).substr($str, $close + 1);
+ }
+ else
+ {
+ // No length
+ $type = $str;
+ }
+
+ if (empty($sql_types[$type]))
+ throw new Database_Exception('Undefined field type :type', array(':type' => $str));
+
+ // Fetch the field definition
+ $field = $sql_types[$type];
+
+ $field['sql_type'] = $type;
+
+ if (isset($length))
+ {
+ // Add the length to the field info
+ $field['length'] = $length;
+ }
+
+ return $field;
+ }
+
+} // End Database
diff --git a/system/libraries/Database_Builder.php b/system/libraries/Database_Builder.php
new file mode 100644
index 0000000..e86ce37
--- /dev/null
+++ b/system/libraries/Database_Builder.php
@@ -0,0 +1,1241 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * The Database Query Builder provides methods for creating database agnostic queries and
+ * data manipulation.
+ *
+ * ##### A basic select query
+ *
+ * $builder = new Database_Builder;
+ * $kohana = $builder
+ * ->select()
+ * ->where('name', '=', 'Kohana')
+ * ->from('frameworks')
+ * ->execute();
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2008-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Database_Builder_Core {
+
+ // Valid ORDER BY directions
+ protected $order_directions = array('ASC', 'DESC', 'RAND()');
+
+ // Database object
+ protected $db;
+
+ // Builder members
+ protected $select = array();
+ protected $from = array();
+ protected $join = array();
+ protected $where = array();
+ protected $group_by = array();
+ protected $having = array();
+ protected $order_by = array();
+ protected $limit = NULL;
+ protected $offset = NULL;
+ protected $set = array();
+ protected $columns = array();
+ protected $values = array();
+ protected $type;
+ protected $distinct = FALSE;
+ protected $reset = TRUE;
+
+ // TTL for caching (using Cache library)
+ protected $ttl = FALSE;
+
+ public function __construct($db = 'default')
+ {
+ $this->db = $db;
+ }
+
+ /**
+ * Compiles the builder object into a SQL query. Useful for debugging
+ *
+ * ##### Example
+ *
+ * echo $builder->select()->from('products');
+ * // Output: SELECT * FROM `products`
+ *
+ * @return string Compiled query
+ */
+ public function __toString()
+ {
+ return $this->compile();
+ }
+
+ /**
+ * Creates a `SELECT` query with support for column aliases, database functions,
+ * subqueries or a [Database_Expression]
+ *
+ * ##### Examples
+ *
+ * // Simple select
+ * echo $builder->select()->from('products');
+ *
+ * // Select with database function
+ * echo $builder->select(array('records_found' => 'COUNT("*")'))->from('products');
+ *
+ * // Select with sub query
+ * echo $builder->select(array('field', 'test' => db::select('test')->from('table')))->from('products');
+ *
+ * @chainable
+ * @param string|array column name or array(alias => column)
+ * @return Database_Builder
+ */
+ public function select($columns = NULL)
+ {
+ $this->type = Database::SELECT;
+
+ if ($columns === NULL)
+ {
+ $columns = array('*');
+ }
+ elseif ( ! is_array($columns))
+ {
+ $columns = func_get_args();
+ }
+
+ $this->select = array_merge($this->select, $columns);
+
+ return $this;
+ }
+
+ /**
+ * Creates a `DISTINCT SELECT` query. For more information see see [Database_Builder::select].
+ *
+ * @chainable
+ * @param string|array column name or array(alias => column)
+ * @return Database_Builder
+ */
+ public function select_distinct($columns = NULL)
+ {
+ $this->select($columns);
+ $this->distinct = TRUE;
+ return $this;
+ }
+
+ /**
+ * Add tables to the FROM portion of the builder
+ *
+ * ##### Example
+ *
+ * $builder->select()->from('products')
+ * ->from(array('other' => 'other_table'));
+ * // Output: SELECT * FROM `products`, `other_table` AS `other`
+ *
+ * @chainable
+ * @param string|array table name or array(alias => table)
+ * @return Database_Builder
+ */
+ public function from($tables)
+ {
+ if ( ! is_array($tables))
+ {
+ $tables = func_get_args();
+ }
+
+ $this->from = array_merge($this->from, $tables);
+
+ return $this;
+ }
+
+ /**
+ * Add conditions to the `WHERE` clause. Alias for [Database_Builder::and_where].
+ *
+ * @chainable
+ * @param mixed Column name or array of columns => vals
+ * @param string Operation to perform
+ * @param mixed Value
+ * @return Database_Builder
+ */
+ public function where($columns, $op = '=', $value = NULL)
+ {
+ return $this->and_where($columns, $op, $value);
+ }
+
+ /**
+ * Add conditions to the `WHERE` clause separating multiple conditions with `AND`.
+ * This function supports all `WHERE` operators including `LIKE` and `IN`. It can
+ * also be used with a [Database_Expression] or subquery.
+ *
+ * ##### Examples
+ *
+ * // Basic where condition
+ * $builder->where('field', '=', 'value');
+ *
+ * // Multiple conditions with an array (you can also chain where() function calls)
+ * $builder->where(array(array('field', '=', 'value'), array(...)));
+ *
+ * // With a database expression
+ * $builder->where('field', '=', db::expr('field + 1'));
+ * // or a function
+ * $builder->where('field', '=', db::expr('UNIX_TIMESTAMP()'));
+ *
+ * // With a subquery
+ * $builder->where('field', 'IN', db::select('id')->from('table'));
+ *
+ * [!!] You must manually escape all data you pass into a database expression!
+ *
+ * @chainable
+ * @param mixed Column name or array of triplets
+ * @param string Operation to perform
+ * @param mixed Value
+ * @return Database_Builder
+ */
+ public function and_where($columns, $op = '=', $value = NULL)
+ {
+ if (is_array($columns))
+ {
+ foreach ($columns as $column)
+ {
+ $this->where[] = array('AND' => $column);
+ }
+ }
+ else
+ {
+ $this->where[] = array('AND' => array($columns, $op, $value));
+ }
+ return $this;
+ }
+
+ /**
+ * Add conditions to the `WHERE` clause separating multiple conditions with `OR`.
+ * For more information about building a `WHERE` clause see [Database_Builder::and_where]
+ *
+ * @chainable
+ * @param mixed Column name or array of triplets
+ * @param string Operation to perform
+ * @param mixed Value
+ * @return Database_Builder
+ */
+ public function or_where($columns, $op = '=', $value = NULL)
+ {
+ if (is_array($columns))
+ {
+ foreach ($columns as $column)
+ {
+ $this->where[] = array('OR' => $column);
+ }
+ }
+ else
+ {
+ $this->where[] = array('OR' => array($columns, $op, $value));
+ }
+ return $this;
+ }
+
+ /**
+ * Join tables to the builder
+ *
+ * ##### Example
+ *
+ * // Basic join
+ * db::select()->from('products')
+ * ->join('reviews', 'reviews.product_id', 'products.id');
+ *
+ * // Advanced joins
+ * echo db::select()->from('products')
+ * ->join('reviews', 'field', db::expr('advanced condition here'), 'RIGHT');
+ *
+ * @chainable
+ * @param mixed Table name
+ * @param mixed Key, or an array of key => value pair, for join condition (can be a Database_Expression)
+ * @param mixed Value if $keys is not an array or Database_Expression
+ * @param string Join type (LEFT, RIGHT, INNER, etc.)
+ * @return Database_Builder
+ */
+ public function join($table, $keys, $value = NULL, $type = NULL)
+ {
+ if (is_string($keys))
+ {
+ $keys = array($keys => $value);
+ }
+
+ if ($type !== NULL)
+ {
+ $type = strtoupper($type);
+ }
+
+ $this->join[] = array($table, $keys, $type);
+
+ return $this;
+ }
+
+ /**
+ * This function is an alias for [Database_Builder::join]
+ * with the join type set to `LEFT`.
+ *
+ * @chainable
+ * @param mixed Table name
+ * @param mixed Key, or an array of key => value pair, for join condition (can be a Database_Expression)
+ * @param mixed Value if $keys is not an array or Database_Expression
+ * @return Database_Builder
+ */
+ public function left_join($table, $keys, $value = NULL)
+ {
+ return $this->join($table, $keys, $value, 'LEFT');
+ }
+
+ /**
+ * This function is an alias for [Database_Builder::join]
+ * with the join type set to `RIGHT`.
+ *
+ * @chainable
+ * @param mixed Table name
+ * @param mixed Key, or an array of key => value pair, for join condition (can be a Database_Expression)
+ * @param mixed Value if $keys is not an array or Database_Expression
+ * @return Database_Builder
+ */
+ public function right_join($table, $keys, $value = NULL)
+ {
+ return $this->join($table, $keys, $value, 'RIGHT');
+ }
+
+ /**
+ * This function is an alias for [Database_Builder::join]
+ * with the join type set to `INNER`.
+ *
+ * @chainable
+ * @param mixed Table name
+ * @param mixed Key, or an array of key => value pair, for join condition (can be a Database_Expression)
+ * @param mixed Value if $keys is not an array or Database_Expression
+ * @return Database_Builder
+ */
+ public function inner_join($table, $keys, $value = NULL)
+ {
+ return $this->join($table, $keys, $value, 'INNER');
+ }
+
+ /**
+ * This function is an alias for [Database_Builder::join]
+ * with the join type set to `OUTER`.
+ *
+ * @chainable
+ * @param mixed Table name
+ * @param mixed Key, or an array of key => value pair, for join condition (can be a Database_Expression)
+ * @param mixed Value if $keys is not an array or Database_Expression
+ * @return Database_Builder
+ */
+ public function outer_join($table, $keys, $value = NULL)
+ {
+ return $this->join($table, $keys, $value, 'OUTER');
+ }
+
+ /**
+ * This function is an alias for [Database_Builder::join]
+ * with the join type set to `FULL`.
+ *
+ * @chainable
+ * @param mixed Table name
+ * @param mixed Key, or an array of key => value pair, for join condition (can be a Database_Expression)
+ * @param mixed Value if $keys is not an array or Database_Expression
+ * @return Database_Builder
+ */
+ public function full_join($table, $keys, $value = NULL)
+ {
+ return $this->join($table, $keys, $value, 'FULL');
+ }
+
+ /**
+ * This function is an alias for [Database_Builder::join]
+ * with the join type set to `LEFT INNER`.
+ *
+ * @chainable
+ * @param mixed Table name
+ * @param mixed Key, or an array of key => value pair, for join condition (can be a Database_Expression)
+ * @param mixed Value if $keys is not an array or Database_Expression
+ * @return Database_Builder
+ */
+ public function left_inner_join($table, $keys, $value = NULL)
+ {
+ return $this->join($table, $keys, $value, 'LEFT INNER');
+ }
+
+ /**
+ * This function is an alias for [Database_Builder::join]
+ * with the join type set to `RIGHT INNER`.
+ *
+ * @chainable
+ * @param mixed Table name
+ * @param mixed Key, or an array of key => value pair, for join condition (can be a Database_Expression)
+ * @param mixed Value if $keys is not an array or Database_Expression
+ * @return Database_Builder
+ */
+ public function right_inner_join($table, $keys, $value = NULL)
+ {
+ return $this->join($table, $keys, $value, 'RIGHT INNER');
+ }
+
+ /**
+ * Add fields to the GROUP BY portion
+ *
+ * ##### Example
+ *
+ * db::select()->from('products')
+ * ->group_by(array('name', 'cat_id'));
+ * // Output: SELECT * FROM `products` GROUP BY `name`, `cat_id`
+ *
+ * @chainable
+ * @param mixed Field names or an array of fields
+ * @return Database_Builder
+ */
+ public function group_by($columns)
+ {
+ if ( ! is_array($columns))
+ {
+ $columns = func_get_args();
+ }
+
+ $this->group_by = array_merge($this->group_by, $columns);
+
+ return $this;
+ }
+
+ /**
+ * Add conditions to the HAVING clause (AND)
+ *
+ * @chainable
+ * @param mixed Column name or array of columns => vals
+ * @param string Operation to perform
+ * @param mixed Value
+ * @return Database_Builder
+ */
+ public function having($columns, $op = '=', $value = NULL)
+ {
+ return $this->and_having($columns, $op, $value);
+ }
+
+ /**
+ * Add conditions to the HAVING clause (AND)
+ *
+ * @chainable
+ * @param mixed Column name or array of triplets
+ * @param string Operation to perform
+ * @param mixed Value
+ * @return Database_Builder
+ */
+ public function and_having($columns, $op = '=', $value = NULL)
+ {
+ if (is_array($columns))
+ {
+ foreach ($columns as $column)
+ {
+ $this->having[] = array('AND' => $column);
+ }
+ }
+ else
+ {
+ $this->having[] = array('AND' => array($columns, $op, $value));
+ }
+ return $this;
+ }
+
+ /**
+ * Add conditions to the HAVING clause (OR)
+ *
+ * @chainable
+ * @param mixed Column name or array of triplets
+ * @param string Operation to perform
+ * @param mixed Value
+ * @return Database_Builder
+ */
+ public function or_having($columns, $op = '=', $value = NULL)
+ {
+ if (is_array($columns))
+ {
+ foreach ($columns as $column)
+ {
+ $this->having[] = array('OR' => $column);
+ }
+ }
+ else
+ {
+ $this->having[] = array('OR' => array($columns, $op, $value));
+ }
+ return $this;
+ }
+
+ /**
+ * Add fields to the ORDER BY portion
+ *
+ * @chainable
+ * @param mixed Field names or an array of fields (field => direction)
+ * @param string Direction or NULL for ascending
+ * @return Database_Builder
+ */
+ public function order_by($columns, $direction = NULL)
+ {
+ if (is_array($columns))
+ {
+ foreach ($columns as $column => $direction)
+ {
+ if (is_string($column))
+ {
+ $this->order_by[] = array($column => $direction);
+ }
+ else
+ {
+ // $direction is the column name when the array key is numeric
+ $this->order_by[] = array($direction => NULL);
+ }
+ }
+ }
+ else
+ {
+ $this->order_by[] = array($columns => $direction);
+ }
+ return $this;
+ }
+
+ /**
+ * Limit rows returned
+ *
+ * @chainable
+ * @param int Number of rows
+ * @return Database_Builder
+ */
+ public function limit($number)
+ {
+ $this->limit = (int) $number;
+
+ return $this;
+ }
+
+ /**
+ * Offset into result set
+ *
+ * @chainable
+ * @param int Offset
+ * @return Database_Builder
+ */
+ public function offset($number)
+ {
+ $this->offset = (int) $number;
+
+ return $this;
+ }
+
+ /**
+ * Alias for [Database_Builder::and_open]
+ *
+ * @chainable
+ * @param string Clause (WHERE OR HAVING)
+ * @return Database_Builder
+ */
+ public function open($clause = 'WHERE')
+ {
+ return $this->and_open($clause);
+ }
+
+ /**
+ * Open new **ANDs** parenthesis set
+ *
+ * @chainable
+ * @param string Clause (WHERE OR HAVING)
+ * @return Database_Builder
+ */
+ public function and_open($clause = 'WHERE')
+ {
+ if ($clause === 'WHERE')
+ {
+ $this->where[] = array('AND' => '(');
+ }
+ else
+ {
+ $this->having[] = array('AND' => '(');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Open new **OR** parenthesis set
+ *
+ * @chainable
+ * @param string Clause (WHERE OR HAVING)
+ * @return Database_Builder
+ */
+ public function or_open($clause = 'WHERE')
+ {
+ if ($clause === 'WHERE')
+ {
+ $this->where[] = array('OR' => '(');
+ }
+ else
+ {
+ $this->having[] = array('OR' => '(');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Close close parenthesis set
+ *
+ * @chainable
+ * @param string Clause (WHERE OR HAVING)
+ * @return Database_Builder
+ */
+ public function close($clause = 'WHERE')
+ {
+ if ($clause === 'WHERE')
+ {
+ $this->where[] = array(')');
+ }
+ else
+ {
+ $this->having[] = array(')');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set values for UPDATE
+ *
+ * @chainable
+ * @param mixed Column name or array of columns => vals
+ * @param mixed Value (can be a Database_Expression)
+ * @return Database_Builder
+ */
+ public function set($keys, $value = NULL)
+ {
+ if (is_string($keys))
+ {
+ $keys = array($keys => $value);
+ }
+
+ $this->set = array_merge($keys, $this->set);
+
+ return $this;
+ }
+
+ /**
+ * Columns used for INSERT queries
+ *
+ * @chainable
+ * @param array Columns
+ * @return Database_Builder
+ */
+ public function columns($columns)
+ {
+ if ( ! is_array($columns))
+ {
+ $columns = func_get_args();
+ }
+
+ $this->columns = $columns;
+
+ return $this;
+ }
+
+ /**
+ * Values used for INSERT queries
+ *
+ * @chainable
+ * @param array Values
+ * @return Database_Builder
+ */
+ public function values($values)
+ {
+ if ( ! is_array($values))
+ {
+ $values = func_get_args();
+ }
+
+ $this->values[] = $values;
+
+ return $this;
+ }
+
+ /**
+ * Set caching for the query
+ *
+ * @chainable
+ * @param mixed Time-to-live (FALSE to disable, NULL for Cache default, seconds otherwise)
+ * @return Database_Builder
+ */
+ public function cache($ttl = NULL)
+ {
+ $this->ttl = $ttl;
+
+ return $this;
+ }
+
+ /**
+ * Resets the database builder after execution. By default after you `execute()` a query
+ * the database builder will reset to its default state. You can use `reset(FALSE)`
+ * to stop this from happening. This is useful for pagination when you might want to
+ * apply a limit to the previous query.
+ *
+ * ##### Example
+ *
+ * $db = new Database_Builder;
+ * $all_results = $db->select()
+ * ->where('id', '=', 3)
+ * ->from('products')
+ * ->reset(FALSE)
+ * ->execute();
+ *
+ * // Run the query again with a limit of 10
+ * $ten_results = $db->limit(10)
+ * ->execute();
+ * @chainable
+ * @param bool reset builder
+ * @return Database_Builder
+ */
+ public function reset($reset = TRUE)
+ {
+ $this->reset = (bool) $reset;
+ return $this;
+ }
+
+ /**
+ * Compiles the given clause's conditions
+ *
+ * @param array Clause conditions
+ * @return string
+ */
+ protected function compile_conditions($groups)
+ {
+ $last_condition = NULL;
+
+ $sql = '';
+ foreach ($groups as $group)
+ {
+ // Process groups of conditions
+ foreach ($group as $logic => $condition)
+ {
+ if ($condition === '(')
+ {
+ if ( ! empty($sql) AND $last_condition !== '(')
+ {
+ // Include logic operator
+ $sql .= ' '.$logic.' ';
+ }
+
+ $sql .= '(';
+ }
+ elseif ($condition === ')')
+ {
+ $sql .= ')';
+ }
+ else
+ {
+ list($columns, $op, $value) = $condition;
+
+ // Stores each individual condition
+ $vals = array();
+
+ if ($columns instanceof Database_Expression)
+ {
+ // Add directly to condition list
+ $vals[] = (string) $columns;
+ }
+ else
+ {
+ $op = strtoupper($op);
+
+ if ( ! is_array($columns))
+ {
+ $columns = array($columns => $value);
+ }
+
+ foreach ($columns as $column => $value)
+ {
+ if ($value instanceof Database_Builder)
+ {
+ // Using a subquery
+ $value->db = $this->db;
+ $value = '('.(string) $value.')';
+ }
+ elseif (is_array($value))
+ {
+ if ($op === 'BETWEEN' OR $op === 'NOT BETWEEN')
+ {
+ // Falls between two values
+ $value = $this->db->quote($value[0]).' AND '.$this->db->quote($value[1]);
+ }
+ else
+ {
+ // Return as list
+ $value = array_map(array($this->db, 'quote'), $value);
+ $value = '('.implode(', ', $value).')';
+ }
+ }
+ else
+ {
+ $value = $this->db->quote($value);
+ }
+
+ if ( ! empty($column))
+ {
+ // Ignore blank columns
+ $column = $this->db->quote_column($column);
+ }
+
+ // Add to condition list
+ $vals[] = $column.' '.$op.' '.$value;
+ }
+ }
+
+ if ( ! empty($sql) AND $last_condition !== '(')
+ {
+ // Add the logic operator
+ $sql .= ' '.$logic.' ';
+ }
+
+ // Join the condition list items together by the given logic operator
+ $sql .= implode(' '.$logic.' ', $vals);
+ }
+
+ $last_condition = $condition;
+ }
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Compiles the columns portion of the query for INSERT
+ *
+ * @return string
+ */
+ protected function compile_columns()
+ {
+ return '('.implode(', ', array_map(array($this->db, 'quote_column'), $this->columns)).')';
+ }
+
+ /**
+ * Compiles the VALUES portion of the query for INSERT
+ *
+ * @return string
+ */
+ protected function compile_values()
+ {
+ $values = array();
+ foreach ($this->values as $group)
+ {
+ // Each set of values to be inserted
+ $values[] = '('.implode(', ', array_map(array($this->db, 'quote'), $group)).')';
+ }
+
+ return implode(', ', $values);
+ }
+
+ /**
+ * Create an UPDATE query
+ *
+ * @chainable
+ * @param string Table name
+ * @param array Array of Keys => Values
+ * @param array WHERE conditions
+ * @return Database_Builder
+ */
+ public function update($table = NULL, $set = NULL, $where = NULL)
+ {
+ $this->type = Database::UPDATE;
+
+ if (is_array($set))
+ {
+ $this->set($set);
+ }
+
+ if ($where !== NULL)
+ {
+ $this->where($where);
+ }
+
+ if ($table !== NULL)
+ {
+ $this->from($table);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Create an INSERT query. Use 'columns' and 'values' methods for multi-row inserts
+ *
+ * @chainable
+ * @param string Table name
+ * @param array Array of Keys => Values
+ * @return Database_Builder
+ */
+ public function insert($table = NULL, $set = NULL)
+ {
+ $this->type = Database::INSERT;
+
+ if (is_array($set))
+ {
+ $this->columns(array_keys($set));
+ $this->values(array_values($set));
+ }
+
+ if ($table !== NULL)
+ {
+ $this->from($table);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Create a DELETE query
+ *
+ * @chainable
+ * @param string Table name
+ * @param array WHERE conditions
+ * @return Database_Builder
+ */
+ public function delete($table, $where = NULL)
+ {
+ $this->type = Database::DELETE;
+
+ if ($where !== NULL)
+ {
+ $this->where($where);
+ }
+
+ if ($table !== NULL)
+ {
+ $this->from($table);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Count records for a given table
+ *
+ * @param string Table name
+ * @param array WHERE conditions
+ * @return int
+ */
+ public function count_records($table = FALSE, $where = NULL)
+ {
+ if (count($this->from) < 1)
+ {
+ if ($table === FALSE)
+ throw new Database_Exception('Database count_records requires a table');
+
+ $this->from($table);
+ }
+
+ if ($where !== NULL)
+ {
+ $this->where($where);
+ }
+
+ // Grab the count AS records_found
+ $result = $this->select(array('records_found' => 'COUNT("*")'))->execute();
+
+ return $result->get('records_found');
+ }
+
+ /**
+ * Executes the built query
+ *
+ * @param mixed Database name or object
+ * @return Database_Result
+ */
+ public function execute($db = NULL)
+ {
+ if ($db !== NULL)
+ {
+ $this->db = $db;
+ }
+
+ if ( ! is_object($this->db))
+ {
+ // Get the database instance
+ $this->db = Database::instance($this->db);
+ }
+
+ $query = $this->compile();
+
+ if ($this->reset)
+ {
+ // Reset the query after executing
+ $this->_reset();
+ }
+
+ if ($this->ttl !== FALSE AND $this->type === Database::SELECT)
+ {
+ // Return result from cache (only allowed with SELECT)
+ return $this->db->query_cache($query, $this->ttl);
+ }
+ else
+ {
+ // Load the result (no caching)
+ return $this->db->query($query);
+ }
+ }
+
+ /**
+ * Compiles the builder object into a SQL query
+ *
+ * @return string Compiled query
+ */
+ protected function compile()
+ {
+ if ( ! is_object($this->db))
+ {
+ // Use default database for compiling to string if none is given
+ $this->db = Database::instance($this->db);
+ }
+
+ if ($this->type === Database::SELECT)
+ {
+ // SELECT columns FROM table
+ $sql = $this->distinct ? 'SELECT DISTINCT ' : 'SELECT ';
+ $sql .= $this->compile_select();
+
+ if ( ! empty($this->from))
+ {
+ $sql .= "\nFROM ".$this->compile_from();
+ }
+ }
+ elseif ($this->type === Database::UPDATE)
+ {
+ $sql = 'UPDATE '.$this->compile_from()."\n".'SET '.$this->compile_set();
+ }
+ elseif ($this->type === Database::INSERT)
+ {
+ $sql = 'INSERT INTO '.$this->compile_from()."\n".$this->compile_columns()."\nVALUES ".$this->compile_values();
+ }
+ elseif ($this->type === Database::DELETE)
+ {
+ $sql = 'DELETE FROM '.$this->compile_from();
+ }
+
+ if ( ! empty($this->join))
+ {
+ $sql .= $this->compile_join();
+ }
+
+ if ( ! empty($this->where))
+ {
+ $sql .= "\n".'WHERE '.$this->compile_conditions($this->where);
+ }
+
+ if ( ! empty($this->group_by))
+ {
+ $sql .= "\n".'GROUP BY '.$this->compile_group_by();
+ }
+
+ if ( ! empty($this->having))
+ {
+ $sql .= "\n".'HAVING '.$this->compile_conditions($this->having);
+ }
+
+ if ( ! empty($this->order_by))
+ {
+ $sql .= "\nORDER BY ".$this->compile_order_by();
+ }
+
+ if (is_int($this->limit))
+ {
+ $sql .= "\nLIMIT ".$this->limit;
+ }
+
+ if (is_int($this->offset))
+ {
+ $sql .= "\nOFFSET ".$this->offset;
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Compiles the SELECT portion of the query
+ *
+ * @return string
+ */
+ protected function compile_select()
+ {
+ $vals = array();
+
+ foreach ($this->select as $alias => $name)
+ {
+ if ($name instanceof Database_Builder)
+ {
+ // Using a subquery
+ $name->db = $this->db;
+ $vals[] = '('.(string) $name.') AS '.$this->db->quote_column($alias);
+ }
+ elseif (is_string($alias))
+ {
+ $vals[] = $this->db->quote_column($name, $alias);
+ }
+ else
+ {
+ $vals[] = $this->db->quote_column($name);
+ }
+ }
+
+ return implode(', ', $vals);
+ }
+
+ /**
+ * Compiles the FROM portion of the query
+ *
+ * @return string
+ */
+ protected function compile_from()
+ {
+ $vals = array();
+
+ foreach ($this->from as $alias => $name)
+ {
+ if (is_string($alias))
+ {
+ // Using AS format so escape both
+ $vals[] = $this->db->quote_table($name, $alias);
+ }
+ else
+ {
+ // Just using the table name itself
+ $vals[] = $this->db->quote_table($name);
+ }
+ }
+
+ return implode(', ', $vals);
+ }
+
+ /**
+ * Compiles the JOIN portion of the query
+ *
+ * @return string
+ */
+ protected function compile_join()
+ {
+ $sql = '';
+ foreach ($this->join as $join)
+ {
+ list($table, $keys, $type) = $join;
+
+ if ($type !== NULL)
+ {
+ // Join type
+ $sql .= ' '.$type;
+ }
+
+ $sql .= ' JOIN '.$this->db->quote_table($table);
+
+ $condition = '';
+ if ($keys instanceof Database_Expression)
+ {
+ $condition = (string) $keys;
+ }
+ elseif (is_array($keys))
+ {
+ // ON condition is an array of matches
+ foreach ($keys as $key => $value)
+ {
+ if ( ! empty($condition))
+ {
+ $condition .= ' AND ';
+ }
+
+ $condition .= $this->db->quote_column($key).' = '.$this->db->quote_column($value);
+ }
+ }
+
+ if ( ! empty($condition))
+ {
+ // Add ON condition
+ $sql .= ' ON ('.$condition.')';
+ }
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Compiles the GROUP BY portion of the query
+ *
+ * @return string
+ */
+ protected function compile_group_by()
+ {
+ $vals = array();
+
+ foreach ($this->group_by as $column)
+ {
+ // Escape the column
+ $vals[] = $this->db->quote_column($column);
+ }
+
+ return implode(', ', $vals);
+ }
+
+ /**
+ * Compiles the ORDER BY portion of the query
+ *
+ * @return string
+ */
+ protected function compile_order_by()
+ {
+ $ordering = array();
+
+ foreach ($this->order_by as $column => $order_by)
+ {
+ list($column, $direction) = each($order_by);
+
+ $column = $this->db->quote_column($column);
+
+ if ($direction !== NULL)
+ {
+ $direction = ' '.$direction;
+ }
+
+ $ordering[] = $column.$direction;
+ }
+
+ return implode(', ', $ordering);
+ }
+
+ /**
+ * Compiles the SET portion of the query for UPDATE
+ *
+ * @return string
+ */
+ protected function compile_set()
+ {
+ $vals = array();
+
+ foreach ($this->set as $key => $value)
+ {
+ // Using an UPDATE so Key = Val
+ $vals[] = $this->db->quote_column($key).' = '.$this->db->quote($value);
+ }
+
+ return implode(', ', $vals);
+ }
+
+ /**
+ * Resets all query components
+ */
+ protected function _reset()
+ {
+ $this->select = array();
+ $this->from = array();
+ $this->join = array();
+ $this->where = array();
+ $this->group_by = array();
+ $this->having = array();
+ $this->order_by = array();
+ $this->limit = NULL;
+ $this->offset = NULL;
+ $this->set = array();
+ $this->values = array();
+ $this->type = NULL;
+ $this->distinct = FALSE;
+ $this->reset = TRUE;
+ $this->ttl = FALSE;
+ }
+
+} // End Database_Builder
diff --git a/system/libraries/Database_Cache_Result.php b/system/libraries/Database_Cache_Result.php
new file mode 100644
index 0000000..3945c42
--- /dev/null
+++ b/system/libraries/Database_Cache_Result.php
@@ -0,0 +1,81 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Cached database result.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2008-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Database_Cache_Result_Core extends Database_Result {
+
+ /**
+ * Result data (array of rows)
+ * @var array
+ */
+ protected $data;
+
+ public function __construct($data, $sql, $return_objects)
+ {
+ $this->data = $data;
+ $this->sql = $sql;
+ $this->total_rows = count($data);
+ $this->return_objects = $return_objects;
+ }
+
+ public function __destruct()
+ {
+ // Not used
+ }
+
+ public function as_array($return = FALSE)
+ {
+ // Return arrays rather than objects
+ $this->return_objects = FALSE;
+
+ if ( ! $return )
+ {
+ // Return this result object
+ return $this;
+ }
+
+ // Return the entire array of rows
+ return $this->data;
+ }
+
+ public function as_object($class = NULL, $return = FALSE)
+ {
+ if ($class !== NULL)
+ throw new Database_Exception('Database cache results do not support object casting');
+
+ // Return objects of type $class (or stdClass if none given)
+ $this->return_objects = TRUE;
+
+ return $this;
+ }
+
+ public function seek($offset)
+ {
+ if ( ! $this->offsetExists($offset))
+ return FALSE;
+
+ $this->current_row = $offset;
+
+ return TRUE;
+ }
+
+ public function current()
+ {
+ if ($this->return_objects)
+ {
+ // Return a new object with the current row of data
+ return (object) $this->data[$this->current_row];
+ }
+ else
+ {
+ // Return an array of the row
+ return $this->data[$this->current_row];
+ }
+ }
+
+} // End Database_Cache_Result \ No newline at end of file
diff --git a/system/libraries/Database_Exception.php b/system/libraries/Database_Exception.php
new file mode 100644
index 0000000..0f6bb75
--- /dev/null
+++ b/system/libraries/Database_Exception.php
@@ -0,0 +1,15 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Database exceptions.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2008-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Database_Exception_Core extends Kohana_Exception {
+
+ // Database error code
+ protected $code = E_DATABASE_ERROR;
+
+} // End Database_Exception \ No newline at end of file
diff --git a/system/libraries/Database_Expression.php b/system/libraries/Database_Expression.php
new file mode 100644
index 0000000..007a0cb
--- /dev/null
+++ b/system/libraries/Database_Expression.php
@@ -0,0 +1,23 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Database expression.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2008-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Database_Expression_Core {
+
+ protected $expression;
+
+ public function __construct($expression)
+ {
+ $this->expression = $expression;
+ }
+
+ public function __toString()
+ {
+ return $this->expression;
+ }
+}
diff --git a/system/libraries/Database_Mysql.php b/system/libraries/Database_Mysql.php
new file mode 100644
index 0000000..a325cbc
--- /dev/null
+++ b/system/libraries/Database_Mysql.php
@@ -0,0 +1,226 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * MySQL database connection.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2008-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Database_Mysql_Core extends Database {
+
+ // Quote character to use for identifiers (tables/columns/aliases)
+ protected $quote = '`';
+
+ // Use SET NAMES to set the character set
+ protected static $set_names;
+
+ public function connect()
+ {
+ if ($this->connection)
+ return;
+
+ if (Database_Mysql::$set_names === NULL)
+ {
+ // Determine if we can use mysql_set_charset(), which is only
+ // available on PHP 5.2.3+ when compiled against MySQL 5.0+
+ Database_Mysql::$set_names = ! function_exists('mysql_set_charset');
+ }
+
+ extract($this->config['connection']);
+
+ $host = isset($host) ? $host : $socket;
+ $port = isset($port) ? ':'.$port : '';
+
+ try
+ {
+ // Connect to the database
+ $this->connection = ($this->config['persistent'] === TRUE)
+ ? mysql_pconnect($host.$port, $user, $pass, $params)
+ : mysql_connect($host.$port, $user, $pass, TRUE, $params);
+ }
+ catch (Kohana_PHP_Exception $e)
+ {
+ // No connection exists
+ $this->connection = NULL;
+
+ // Unable to connect to the database
+ throw new Database_Exception('#:errno: :error',
+ array(':error' => mysql_error(),
+ ':errno' => mysql_errno()));
+ }
+
+ if ( ! mysql_select_db($database, $this->connection))
+ {
+ // Unable to select database
+ throw new Database_Exception('#:errno: :error',
+ array(':error' => mysql_error($this->connection),
+ ':errno' => mysql_errno($this->connection)));
+ }
+
+ if (isset($this->config['character_set']))
+ {
+ // Set the character set
+ $this->set_charset($this->config['character_set']);
+ }
+ }
+
+ public function disconnect()
+ {
+ try
+ {
+ // Database is assumed disconnected
+ $status = TRUE;
+
+ if (is_resource($this->connection))
+ {
+ $status = mysql_close($this->connection);
+ }
+ }
+ catch (Exception $e)
+ {
+ // Database is probably not disconnected
+ $status = is_resource($this->connection);
+ }
+
+ return $status;
+ }
+
+ public function set_charset($charset)
+ {
+ // Make sure the database is connected
+ $this->connection or $this->connect();
+
+ if (Database_Mysql::$set_names === TRUE)
+ {
+ // PHP is compiled against MySQL 4.x
+ $status = (bool) mysql_query('SET NAMES '.$this->quote($charset), $this->connection);
+ }
+ else
+ {
+ // PHP is compiled against MySQL 5.x
+ $status = mysql_set_charset($charset, $this->connection);
+ }
+
+ if ($status === FALSE)
+ {
+ // Unable to set charset
+ throw new Database_Exception('#:errno: :error',
+ array(':error' => mysql_error($this->connection),
+ ':errno' => mysql_errno($this->connection)));
+ }
+ }
+
+ public function query_execute($sql)
+ {
+ // Make sure the database is connected
+ $this->connection or $this->connect();
+
+ $result = mysql_query($sql, $this->connection);
+
+ // Set the last query
+ $this->last_query = $sql;
+
+ return new Database_Mysql_Result($result, $sql, $this->connection, $this->config['object']);
+ }
+
+ public function escape($value)
+ {
+ // Make sure the database is connected
+ $this->connection or $this->connect();
+
+ if (($value = mysql_real_escape_string($value, $this->connection)) === FALSE)
+ {
+ throw new Database_Exception('#:errno: :error',
+ array(':error' => mysql_error($this->connection),
+ ':errno' => mysql_errno($this->connection)));
+ }
+
+ return $value;
+ }
+
+ public function list_constraints($table)
+ {
+ $prefix = strlen($this->table_prefix());
+ $result = array();
+
+ $constraints = $this->query('
+ SELECT c.constraint_name, c.constraint_type, k.column_name, k.referenced_table_name, k.referenced_column_name
+ FROM information_schema.table_constraints c
+ JOIN information_schema.key_column_usage k ON (k.table_schema = c.table_schema AND k.table_name = c.table_name AND k.constraint_name = c.constraint_name)
+ WHERE c.table_schema = '.$this->quote($this->config['connection']['database']).'
+ AND c.table_name = '.$this->quote($this->table_prefix().$table).'
+ AND (k.referenced_table_schema IS NULL OR k.referenced_table_schema ='.$this->quote($this->config['connection']['database']).')
+ ORDER BY k.ordinal_position
+ ');
+
+ foreach ($constraints->as_array() as $row)
+ {
+ switch ($row['constraint_type'])
+ {
+ case 'FOREIGN KEY':
+ if (isset($result[$row['constraint_name']]))
+ {
+ $result[$row['constraint_name']][1][] = $row['column_name'];
+ $result[$row['constraint_name']][3][] = $row['referenced_column_name'];
+ }
+ else
+ {
+ $result[$row['constraint_name']] = array($row['constraint_type'], array($row['column_name']), substr($row['referenced_table_name'], $prefix), array($row['referenced_column_name']));
+ }
+ break;
+ case 'PRIMARY KEY':
+ case 'UNIQUE':
+ if (isset($result[$row['constraint_name']]))
+ {
+ $result[$row['constraint_name']][1][] = $row['column_name'];
+ }
+ else
+ {
+ $result[$row['constraint_name']] = array($row['constraint_type'], array($row['column_name']));
+ }
+ break;
+ }
+ }
+
+ return $result;
+ }
+
+ public function list_fields($table)
+ {
+ $result = array();
+
+ foreach ($this->query('SHOW COLUMNS FROM '.$this->quote_table($table))->as_array() as $row)
+ {
+ $column = $this->sql_type($row['Type']);
+
+ $column['default'] = $row['Default'];
+ $column['nullable'] = $row['Null'] === 'YES';
+ $column['sequenced'] = $row['Extra'] === 'auto_increment';
+
+ if (isset($column['length']) AND $column['type'] === 'float')
+ {
+ list($column['precision'], $column['scale']) = explode(',', $column['length']);
+ }
+
+ $result[$row['Field']] = $column;
+ }
+
+ return $result;
+ }
+
+ public function list_tables()
+ {
+ $prefix = strlen($this->table_prefix());
+ $tables = array();
+
+ foreach ($this->query('SHOW TABLES LIKE '.$this->quote($this->table_prefix().'%'))->as_array() as $row)
+ {
+ // The value is the table name
+ $tables[] = substr(current($row), $prefix);
+ }
+
+ return $tables;
+ }
+
+} // End Database_MySQL
diff --git a/system/libraries/Database_Mysql_Result.php b/system/libraries/Database_Mysql_Result.php
new file mode 100644
index 0000000..0f89898
--- /dev/null
+++ b/system/libraries/Database_Mysql_Result.php
@@ -0,0 +1,174 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * MySQL database result.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2008-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Database_Mysql_Result_Core extends Database_Result {
+
+ protected $internal_row = 0;
+
+ public function __construct($result, $sql, $link, $return_objects)
+ {
+ if (is_resource($result))
+ {
+ // True to return objects, false for arrays
+ $this->return_objects = $return_objects;
+
+ $this->total_rows = mysql_num_rows($result);
+ }
+ elseif (is_bool($result))
+ {
+ if ($result == FALSE)
+ {
+ throw new Database_Exception('#:errno: :error [ :query ]',
+ array(':error' => mysql_error($link),
+ ':query' => $sql,
+ ':errno' => mysql_errno($link)));
+
+ }
+ else
+ {
+ // It's a DELETE, INSERT, REPLACE, or UPDATE query
+ $this->insert_id = mysql_insert_id($link);
+ $this->total_rows = mysql_affected_rows($link);
+ }
+ }
+
+ // Store the result locally
+ $this->result = $result;
+
+ $this->sql = $sql;
+ }
+
+ public function __destruct()
+ {
+ if (is_resource($this->result))
+ {
+ mysql_free_result($this->result);
+ }
+ }
+
+ public function as_array($return = FALSE)
+ {
+ // Return arrays rather than objects
+ $this->return_objects = FALSE;
+
+ if ( ! $return )
+ {
+ // Return this result object
+ return $this;
+ }
+
+ // Return a nested array of all results
+ $array = array();
+
+ if ($this->total_rows > 0)
+ {
+ // Seek to the beginning of the result
+ mysql_data_seek($this->result, 0);
+
+ while ($row = mysql_fetch_assoc($this->result))
+ {
+ // Add each row to the array
+ $array[] = $row;
+ }
+
+ $this->internal_row = $this->total_rows;
+ }
+
+ return $array;
+ }
+
+ public function as_object($class = NULL, $return = FALSE)
+ {
+ // Return objects of type $class (or stdClass if none given)
+ $this->return_objects = ($class !== NULL) ? $class : TRUE;
+
+ if ( ! $return )
+ {
+ // Return this result object
+ return $this;
+ }
+
+ // Return a nested array of all results
+ $array = array();
+
+ if ($this->total_rows > 0)
+ {
+ // Seek to the beginning of the result
+ mysql_data_seek($this->result, 0);
+
+ if (is_string($this->return_objects))
+ {
+ while ($row = mysql_fetch_object($this->result, $this->return_objects))
+ {
+ // Add each row to the array
+ $array[] = $row;
+ }
+ }
+ else
+ {
+ while ($row = mysql_fetch_object($this->result))
+ {
+ // Add each row to the array
+ $array[] = $row;
+ }
+ }
+
+ $this->internal_row = $this->total_rows;
+ }
+
+ return $array;
+ }
+
+ /**
+ * SeekableIterator: seek
+ */
+ 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 = $this->internal_row = $offset;
+
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+ }
+
+ /**
+ * Iterator: current
+ */
+ public function current()
+ {
+ if ($this->current_row !== $this->internal_row AND ! $this->seek($this->current_row))
+ return NULL;
+
+ ++$this->internal_row;
+
+ if ($this->return_objects)
+ {
+ if (is_string($this->return_objects))
+ {
+ return mysql_fetch_object($this->result, $this->return_objects);
+ }
+ else
+ {
+ return mysql_fetch_object($this->result);
+ }
+ }
+ else
+ {
+ // Return an array of the row
+ return mysql_fetch_assoc($this->result);
+ }
+ }
+
+} // End Database_MySQL_Result \ No newline at end of file
diff --git a/system/libraries/Database_Mysqli.php b/system/libraries/Database_Mysqli.php
new file mode 100644
index 0000000..41b635d
--- /dev/null
+++ b/system/libraries/Database_Mysqli.php
@@ -0,0 +1,90 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * MySQL database connection.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2008-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+
+define('RUNS_MYSQLND', function_exists('mysqli_fetch_all'));
+
+class Database_Mysqli_Core extends Database_Mysql {
+
+ public function connect()
+ {
+ if (is_object($this->connection))
+ return;
+
+ extract($this->config['connection']);
+
+ // Persistent connections are supported as of PHP 5.3
+ if (RUNS_MYSQLND AND $this->config['persistent'] === TRUE)
+ {
+ $host = 'p:'.$host;
+ }
+
+ $host = isset($host) ? $host : $socket;
+
+ $mysqli = mysqli_init();
+
+ if ( ! $mysqli->real_connect($host, $user, $pass, $database, $port, $socket, $params))
+ throw new Database_Exception('#:errno: :error',
+ array(':error' => $mysqli->connect_error, ':errno' => $mysqli->connect_errno));
+
+ $this->connection = $mysqli;
+
+ if (isset($this->config['character_set']))
+ {
+ // Set the character set
+ $this->set_charset($this->config['character_set']);
+ }
+ }
+
+ public function disconnect()
+ {
+ if (is_object($this->connection))
+ {
+ $this->connection->close();
+ }
+
+ $this->connection = NULL;
+ }
+
+ public function set_charset($charset)
+ {
+ // Make sure the database is connected
+ is_object($this->connection) or $this->connect();
+
+ if ( ! $this->connection->set_charset($charset))
+ {
+ // Unable to set charset
+ throw new Database_Exception('#:errno: :error',
+ array(':error' => $this->connection->connect_error,
+ ':errno' => $this->connection->connect_errno));
+ }
+ }
+
+ public function query_execute($sql)
+ {
+ // Make sure the database is connected
+ is_object($this->connection) or $this->connect();
+
+ $result = $this->connection->query($sql);
+
+ // Set the last query
+ $this->last_query = $sql;
+
+ return new Database_Mysqli_Result($result, $sql, $this->connection, $this->config['object']);
+ }
+
+ public function escape($value)
+ {
+ // Make sure the database is connected
+ is_object($this->connection) or $this->connect();
+
+ return $this->connection->real_escape_string($value);
+ }
+
+} // End Database_MySQLi
diff --git a/system/libraries/Database_Mysqli_Result.php b/system/libraries/Database_Mysqli_Result.php
new file mode 100644
index 0000000..f8b7b58
--- /dev/null
+++ b/system/libraries/Database_Mysqli_Result.php
@@ -0,0 +1,175 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * MySQL database result.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2008-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Database_Mysqli_Result_Core extends Database_Result {
+
+ protected $internal_row = 0;
+
+ public function __construct($result, $sql, $link, $return_objects)
+ {
+ if (is_object($result))
+ {
+ // True to return objects, false for arrays
+ $this->return_objects = $return_objects;
+
+ $this->total_rows = $result->num_rows;
+ }
+ elseif (is_bool($result))
+ {
+ if ($result == FALSE)
+ {
+ throw new Database_Exception('#:errno: :error [ :query ]',
+ array(':error' => $link->error,
+ ':query' => $sql,
+ ':errno' => $link->errno));
+ }
+ else
+ {
+ // It's a DELETE, INSERT, REPLACE, or UPDATE query
+ $this->insert_id = $link->insert_id;
+ $this->total_rows = $link->affected_rows;
+ }
+ }
+
+ // Store the result locally
+ $this->result = $result;
+
+ $this->sql = $sql;
+ }
+
+ public function __destruct()
+ {
+ if (is_object($this->result))
+ {
+ $this->result->free();
+ }
+ }
+
+ public function as_array($return = FALSE)
+ {
+ // Return arrays rather than objects
+ $this->return_objects = FALSE;
+
+ if ( ! $return )
+ {
+ // Return this result object
+ return $this;
+ }
+
+ // Return a nested array of all results
+ if (RUNS_MYSQLND)
+ return $this->result->fetch_all(MYSQLI_ASSOC);
+
+ $array = array();
+
+ if ($this->total_rows > 0)
+ {
+ // Seek to the beginning of the result
+ $this->result->data_seek(0);
+
+ while ($row = $this->result->fetch_assoc())
+ {
+ // Add each row to the array
+ $array[] = $row;
+ }
+ $this->internal_row = $this->total_rows;
+ }
+
+ return $array;
+ }
+
+ public function as_object($class = NULL, $return = FALSE)
+ {
+ // Return objects of type $class (or stdClass if none given)
+ $this->return_objects = ($class !== NULL) ? $class : TRUE;
+
+ if ( ! $return )
+ {
+ // Return this result object
+ return $this;
+ }
+
+ // Return a nested array of all results
+ $array = array();
+
+ if ($this->total_rows > 0)
+ {
+ // Seek to the beginning of the result
+ $this->result->data_seek(0);
+
+ if (is_string($this->return_objects))
+ {
+ while ($row = $this->result->fetch_object($this->return_objects))
+ {
+ // Add each row to the array
+ $array[] = $row;
+ }
+ }
+ else
+ {
+ while ($row = $this->result->fetch_object())
+ {
+ // Add each row to the array
+ $array[] = $row;
+ }
+ }
+
+ $this->internal_row = $this->total_rows;
+ }
+
+ return $array;
+ }
+
+ /**
+ * SeekableIterator: seek
+ */
+ 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;
+ }
+ else
+ {
+ return FALSE;
+ }
+ }
+
+ /**
+ * Iterator: current
+ */
+ public function current()
+ {
+ if ($this->current_row !== $this->internal_row AND ! $this->seek($this->current_row))
+ return NULL;
+
+ ++$this->internal_row;
+
+ if ($this->return_objects)
+ {
+ if (is_string($this->return_objects))
+ {
+ return $this->result->fetch_object($this->return_objects);
+ }
+ else
+ {
+ return $this->result->fetch_object();
+ }
+ }
+ else
+ {
+ // Return an array of the row
+ return $this->result->fetch_assoc();
+ }
+ }
+
+} // End Database_MySQLi_Result \ No newline at end of file
diff --git a/system/libraries/Database_Query.php b/system/libraries/Database_Query.php
new file mode 100644
index 0000000..d9399d6
--- /dev/null
+++ b/system/libraries/Database_Query.php
@@ -0,0 +1,95 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Database query wrapper.
+ *
+ * $Id: Database_Query.php 4679 2009-11-10 01:45:52Z isaiah $
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2008-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Database_Query_Core {
+
+ protected $sql;
+ protected $params;
+ protected $ttl = FALSE;
+
+ public function __construct($sql = NULL)
+ {
+ $this->sql = $sql;
+ }
+
+ public function __toString()
+ {
+ // Return the SQL of this query
+ return $this->sql;
+ }
+
+ public function sql($sql)
+ {
+ $this->sql = $sql;
+
+ return $this;
+ }
+
+ public function value($key, $value)
+ {
+ $this->params[$key] = $value;
+
+ return $this;
+ }
+
+ public function bind($key, & $value)
+ {
+ $this->params[$key] =& $value;
+
+ return $this;
+ }
+
+ public function execute($db = 'default')
+ {
+ if ( ! is_object($db))
+ {
+ // Get the database instance
+ $db = Database::instance($db);
+ }
+
+ // Import the SQL locally
+ $sql = $this->sql;
+
+ if ( ! empty($this->params))
+ {
+ // Quote all of the values
+ $params = array_map(array($db, 'quote'), $this->params);
+
+ // Replace the values in the SQL
+ $sql = strtr($sql, $params);
+ }
+
+ if ($this->ttl !== FALSE)
+ {
+ // Load the result from the cache
+ return $db->query_cache($sql, $this->ttl);
+ }
+ else
+ {
+ // Load the result (no caching)
+ return $db->query($sql);
+ }
+ }
+
+ /**
+ * Set caching for the query
+ *
+ * @param mixed Time-to-live (FALSE to disable, NULL for Cache default, seconds otherwise)
+ * @return Database_Query
+ */
+ public function cache($ttl = NULL)
+ {
+ $this->ttl = $ttl;
+
+ return $this;
+ }
+
+} // End Database_Query \ No newline at end of file
diff --git a/system/libraries/Database_Result.php b/system/libraries/Database_Result.php
new file mode 100644
index 0000000..cf2056f
--- /dev/null
+++ b/system/libraries/Database_Result.php
@@ -0,0 +1,170 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Database result wrapper.
+ *
+ * $Id: Database_Result.php 4679 2009-11-10 01:45:52Z isaiah $
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2008-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+abstract class Database_Result_Core implements Countable, Iterator, SeekableIterator, ArrayAccess {
+
+ protected $result;
+
+ protected $total_rows = 0;
+ protected $current_row = 0;
+ protected $insert_id;
+
+ // Return objects or arrays for each row
+ protected $return_objects;
+
+ /**
+ * Sets the total number of rows and stores the result locally.
+ *
+ * @param mixed $result query result
+ * @param boolean $return_objects True for results as objects, false for arrays
+ * @return void
+ */
+ abstract public function __construct($result, $sql, $link, $return_objects);
+
+ /**
+ * Result destruction cleans up all open result sets.
+ */
+ abstract public function __destruct();
+
+ /**
+ * Return arrays for reach result, or the entire set of results
+ *
+ * @param boolean $return True to return entire result array
+ * @return Database_Result|array
+ */
+ abstract public function as_array($return = FALSE);
+
+ /**
+ * Returns objects for each result
+ *
+ * @param string $class Class name to return objects as or NULL for stdClass
+ * @return Database_Result
+ */
+ abstract public function as_object($class = NULL, $return = FALSE);
+
+ /**
+ * Returns the insert id
+ *
+ * @return int
+ */
+ public function insert_id()
+ {
+ return $this->insert_id;
+ }
+
+ /**
+ * Return the named column from the current row.
+ *
+ * @param string Column name
+ * @return mixed
+ */
+ public function get($name)
+ {
+ // Get the current row
+ $row = $this->current();
+
+ if ( ! $this->return_objects)
+ return $row[$name];
+
+ return $row->$name;
+ }
+
+ /**
+ * Countable: count
+ */
+ public function count()
+ {
+ return $this->total_rows;
+ }
+
+ /**
+ * ArrayAccess: offsetExists
+ */
+ public function offsetExists($offset)
+ {
+ return ($offset >= 0 AND $offset < $this->total_rows);
+ }
+
+ /**
+ * ArrayAccess: offsetGet
+ */
+ public function offsetGet($offset)
+ {
+ if ( ! $this->seek($offset))
+ return NULL;
+
+ return $this->current();
+ }
+
+ /**
+ * ArrayAccess: offsetSet
+ *
+ * @throws Kohana_Database_Exception
+ */
+ final public function offsetSet($offset, $value)
+ {
+ throw new Kohana_Exception('Database results are read-only');
+ }
+
+ /**
+ * ArrayAccess: offsetUnset
+ *
+ * @throws Kohana_Database_Exception
+ */
+ final public function offsetUnset($offset)
+ {
+ throw new Kohana_Exception('Database results are read-only');
+ }
+
+ /**
+ * 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 \ No newline at end of file
diff --git a/system/libraries/Encrypt.php b/system/libraries/Encrypt.php
new file mode 100644
index 0000000..15d4087
--- /dev/null
+++ b/system/libraries/Encrypt.php
@@ -0,0 +1,176 @@
+<?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 4729 2009-12-29 20:35:19Z isaiah $
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+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('To use the Encrypt library, mcrypt must be enabled in your PHP installation');
+
+ if (is_string($config))
+ {
+ $name = $config;
+
+ // Test the config group name
+ if (($config = Kohana::config('encryption.'.$config)) === NULL)
+ throw new Kohana_Exception('The :name: group is not defined in your configuration.', array(':name:' => $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('To use the Encrypt library, you must set an encryption key in your config file');
+
+ // 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::add('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 or FALSE if decryption fails
+ */
+ public function decode($data)
+ {
+ // Convert the data back to binary
+ $data = base64_decode($data, TRUE);
+
+ if ( ! $data)
+ {
+ // Invalid base64 data
+ return FALSE;
+ }
+
+ // Extract the initialization vector from the data
+ $iv = substr($data, 0, $this->config['iv_size']);
+
+ if ($this->config['iv_size'] !== strlen($iv))
+ {
+ // The iv is not the correct size
+ return FALSE;
+ }
+
+ // 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/I18n.php b/system/libraries/I18n.php
new file mode 100644
index 0000000..9401ddc
--- /dev/null
+++ b/system/libraries/I18n.php
@@ -0,0 +1,100 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Kohana I18N System
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+
+class I18n_Core
+{
+ protected static $locale;
+ // All the translations will be cached in here, after the first call of get_text()
+ protected static $translations = array();
+
+ public static function set_locale($locale)
+ {
+ // Reset the translations array
+ I18n::$translations = array();
+
+ I18n::$locale = $locale;
+ }
+
+
+ /**
+ *
+ * Returns the locale.
+ * If $ext is true, the UTF8 extension gets returned as well, otherwise, just the language code.
+ * Defaults to true.
+ *
+ * @return The locale
+ * @param boolean $ext[optional] Get the Extension?
+ */
+ public static function get_locale($ext = true)
+ {
+ if($ext)
+ return I18n::$locale;
+ else
+ return arr::get(explode('.', I18n::$locale), 0);
+ }
+
+
+ /**
+ *
+ * Translates $string into language I18n::$locale and caches all found translations on the first call
+ *
+ * @return The translated String
+ * @param string $string The String to translate
+ */
+ public static function get_text($string)
+ {
+ if ( ! I18n::$translations)
+ {
+ $locale = explode('_', I18n::get_locale(FALSE));
+
+ // Get the translation files
+ $translation_files = Kohana::find_file('i18n', $locale[0]);
+
+ if($local_translation_files = Kohana::find_file('i18n', $locale[0].'/'.$locale[1]))
+ $translation_files = array_merge($translation_files, $local_translation_files);
+
+ if ($translation_files)
+ {
+ // Merge the translations
+ foreach ($translation_files as $file)
+ {
+ include $file;
+ I18n::$translations = array_merge(I18n::$translations, $translations);
+ }
+ }
+ }
+
+ if (isset(I18n::$translations[$string]))
+ return I18n::$translations[$string];
+ else
+ return $string;
+ }
+}
+
+/**
+ * Loads the configured driver and validates it.
+ *
+ * @param string Text to output
+ * @param array Key/Value pairs of arguments to replace in the string
+ * @return string Translated text
+ */
+function __($string, $args = NULL)
+{
+ // KOHANA_LOCALE is the default locale, in which all of Kohana's __() calls are written in
+ if (I18n::get_locale() != Kohana::LOCALE)
+ {
+ $string = I18n::get_text($string);
+ }
+
+ if ($args === NULL)
+ return $string;
+
+ return strtr($string, $args);
+}
diff --git a/system/libraries/Image.php b/system/libraries/Image.php
new file mode 100644
index 0000000..991c8d5
--- /dev/null
+++ b/system/libraries/Image.php
@@ -0,0 +1,501 @@
+<?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.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+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;
+
+ // Orientations
+ const PORTRAIT = 7;
+ const LANDSCAPE = 8;
+ const SQUARE = 9;
+
+ // 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('The Image library requires the getimagesize() PHP function, which is not available in your installation.');
+
+ // Check to make sure the image exists
+ if ( ! is_file($image))
+ throw new Kohana_Exception('The specified image, :image:, was not found. Please verify that images exist by using file_exists() before manipulating them.', array(':image:' => $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('The file specified, :file:, is not readable or is not an image', array(':file:' => $image));
+
+ // Check to make sure the image type is allowed
+ if ( ! isset(Image::$allowed_types[$image_info[2]]))
+ throw new Kohana_Exception('The specified image, :type:, is not an allowed image type.', array(':type:' => $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']
+ );
+
+ $this->determine_orientation();
+
+ // 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('The :driver: driver for the :library: library could not be found',
+ array(':driver:' => $this->config['driver'], ':library:' => 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('The :driver: driver for the :library: library must implement the :interface: interface',
+ array(':driver:' => $this->config['driver'], ':library:' => get_class($this), ':interface:' => 'Image_Driver'));
+ }
+
+ /**
+ * Works out the correct orientation for the image
+ *
+ * @return void
+ */
+ protected function determine_orientation()
+ {
+ switch (TRUE)
+ {
+ case $this->image['height'] > $this->image['width']:
+ $orientation = Image::PORTRAIT;
+ break;
+
+ case $this->image['height'] < $this->image['width']:
+ $orientation = Image::LANDSCAPE;
+ break;
+
+ default:
+ $orientation = Image::SQUARE;
+ }
+
+ $this->image['orientation'] = $orientation;
+ }
+
+ /**
+ * 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('The :property: property does not exist in the :class: class.',
+ array(':property:' => $property, ':class:' => 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('The width you specified, :width:, is not valid.', array(':width:' => $width));
+
+ if ( ! $this->valid_size('height', $height))
+ throw new Kohana_Exception('The height you specified, :height:, is not valid.', array(':height:' => $height));
+
+ if (empty($width) AND empty($height))
+ throw new Kohana_Exception('The dimensions specified for :function: are not valid.', array(':function:' => __FUNCTION__));
+
+ if ($master === NULL)
+ {
+ // Maintain the aspect ratio by default
+ $master = Image::AUTO;
+ }
+ elseif ( ! $this->valid_size('master', $master))
+ throw new Kohana_Exception('The master dimension specified is not valid.');
+
+ $this->actions['resize'] = array
+ (
+ 'width' => $width,
+ 'height' => $height,
+ 'master' => $master,
+ );
+
+ $this->determine_orientation();
+
+ 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('The width you specified, :width:, is not valid.', array(':width:' => $width));
+
+ if ( ! $this->valid_size('height', $height))
+ throw new Kohana_Exception('The height you specified, :height:, is not valid.', array(':height:' => $height));
+
+ if ( ! $this->valid_size('top', $top))
+ throw new Kohana_Exception('The top offset you specified, :top:, is not valid.', array(':top:' => $top));
+
+ if ( ! $this->valid_size('left', $left))
+ throw new Kohana_Exception('The left offset you specified, :left:, is not valid.', array(':left:' => $left));
+
+ if (empty($width) AND empty($height))
+ throw new Kohana_Exception('The dimensions specified for :function: are not valid.', array(':function:' => __FUNCTION__));
+
+ $this->actions['crop'] = array
+ (
+ 'width' => $width,
+ 'height' => $height,
+ 'top' => $top,
+ 'left' => $left,
+ );
+
+ $this->determine_orientation();
+
+ 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('The specified image, :type:, is not an allowed image type.', array(':type:' => $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('The flip direction specified is not valid.');
+
+ $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, $background = NULL)
+ {
+ // 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('The specified directory, :dir:, is not writable.', array(':dir:' => $dir));
+
+ if ($status = $this->driver->process($this->image, $this->actions, $dir, $file, FALSE, $background))
+ {
+ if ($chmod !== FALSE)
+ {
+ // Set permissions
+ chmod($new_image, $chmod);
+ }
+ }
+
+ if ($keep_actions !== TRUE)
+ {
+ // Reset actions. Subsequent save() or render() will not apply previous actions.
+ $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, $background = NULL)
+ {
+ $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, TRUE, $background);
+
+ if ($keep_actions !== TRUE)
+ {
+ // Reset actions. Subsequent save() or render() will not apply previous actions.
+ $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 0000000..fa984a8
--- /dev/null
+++ b/system/libraries/Input.php
@@ -0,0 +1,507 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Input library.
+ *
+ * $Id: Input.php 4729 2009-12-29 20:35:19Z isaiah $
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+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()
+ {
+ // Convert all global variables to Kohana charset
+ $_GET = Input::clean($_GET);
+ $_POST = Input::clean($_POST);
+ $_COOKIE = Input::clean($_COOKIE);
+ $_SERVER = Input::clean($_SERVER);
+
+ if (Kohana::$server_api === 'cli')
+ {
+ // Convert command line arguments
+ $_SERVER['argv'] = Input::clean($_SERVER['argv']);
+ }
+
+ // 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::add('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::add('debug', 'Disable magic_quotes_gpc! It is evil and deprecated: http://php.net/magic_quotes');
+ }
+
+ 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::add('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::get() ($_COOKIE won't work with signed
+ * cookies.)
+ *
+ * @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::get(), $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 (is_bool($tool))
+ {
+ $tool = 'default';
+ }
+ elseif ( ! method_exists($this, 'xss_filter_'.$tool))
+ {
+ Kohana_Log::add('error', 'Unable to use Input::xss_filter_'.$tool.'(), no such method exists');
+ $tool = 'default';
+ }
+
+ $method = 'xss_filter_'.$tool;
+
+ return $this->$method($data);
+ }
+
+ /**
+ * Default built-in cross site scripting filter.
+ *
+ * @param string data to clean
+ * @return string
+ */
+ protected function xss_filter_default($data)
+ {
+ // 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
+ //
+ // Gallery Modifications:
+ // * Wrap the loop around all the changes to detect nested exploits
+
+ do
+ {
+ $old_data = $data;
+
+ // 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('#(?:on[a-z]+|xmlns)\s*=\s*[\'"\x00-\x20]?[^\'>"]*[\'"\x00-\x20]?\s?#iu', '', $data);
+
+ // Remove javascript: and vbscript: protocols
+ $data = preg_replace('#([a-z]*)[\x00-\x20]*=[\x00-\x20]*([`\'"]*)[\x00-\x20]*j[\x00-\x20]*a[\x00-\x20]*v[\x00-\x20]*a[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2nojavascript...', $data);
+ $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);
+
+ //remove any style attributes, IE allows too much stupid things in them, eg.
+ //<span style="width: expression(alert('Ping!'));"></span>
+ // and in general you really don't want style declarations in your UGC
+ $data = preg_replace('#(<[^>]+[\x00-\x20\"\'\/])style[^>]*>#iUu', "$1>", $data);
+
+ // Remove namespaced elements (we do not need them)
+ $data = preg_replace('#</*\w+:\w[^>]*+>#i', '', $data);
+
+ // Remove really unwanted tags
+ $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);
+
+ return $data;
+ }
+
+ /**
+ * HTMLPurifier cross site scripting filter. This version assumes the
+ * existence of the "Standalone Distribution" htmlpurifier library, and is set to not tidy
+ * input.
+ *
+ * @param string data to clean
+ * @return string
+ */
+ protected function xss_filter_htmlpurifier($data)
+ {
+ /**
+ * @todo License should go here, http://htmlpurifier.org/
+ */
+ if ( ! class_exists('HTMLPurifier_Config', FALSE))
+ {
+ // Load HTMLPurifier
+ require Kohana::find_file('vendor', 'htmlpurifier/HTMLPurifier.standalone', TRUE);
+ }
+
+ // Set configuration
+ $config = HTMLPurifier_Config::createDefault();
+ $config->set('HTML.TidyLevel', 'none'); // Only XSS cleaning now
+
+ $cache = Kohana::config('html_purifier.cache');
+
+ if ($cache AND is_string($cache))
+ {
+ $config->set('Cache.SerializerPath', $cache);
+ }
+
+ // Run HTMLPurifier
+ $data = HTMLPurifier::instance($config)->purify($data);
+
+ 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)
+ {
+ if ( ! preg_match('#^[\pL0-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;
+ }
+
+ /**
+ * Recursively cleans arrays, objects, and strings. Removes ASCII control
+ * codes and converts to UTF-8 while silently discarding incompatible
+ * UTF-8 characters.
+ *
+ * @param string string to clean
+ * @return string
+ */
+ public static function clean($str)
+ {
+ if (is_array($str) OR is_object($str))
+ {
+ foreach ($str as $key => $val)
+ {
+ // Recursion!
+ $str[Input::clean($key)] = Input::clean($val);
+ }
+ }
+ elseif (is_string($str) AND $str !== '')
+ {
+ // Remove control characters
+ $str = text::strip_ascii_ctrl($str);
+
+ if ( ! text::is_ascii($str))
+ {
+ // Disable notices
+ $ER = error_reporting(~E_NOTICE);
+
+ // iconv is expensive, so it is only used when needed
+ $str = iconv(Kohana::CHARSET, Kohana::CHARSET.'//IGNORE', $str);
+
+ // Turn notices back on
+ error_reporting($ER);
+ }
+ }
+
+ return $str;
+ }
+
+} // End Input Class
diff --git a/system/libraries/Kohana_404_Exception.php b/system/libraries/Kohana_404_Exception.php
new file mode 100644
index 0000000..7bb7708
--- /dev/null
+++ b/system/libraries/Kohana_404_Exception.php
@@ -0,0 +1,56 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Creates a "Page Not Found" exception.
+ *
+ * $Id: Kohana_404_Exception.php 4729 2009-12-29 20:35:19Z isaiah $
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+
+class Kohana_404_Exception_Core extends Kohana_Exception {
+
+ protected $code = E_PAGE_NOT_FOUND;
+
+ /**
+ * Set internal properties.
+ *
+ * @param string URI of page
+ * @param string custom error template
+ */
+ public function __construct($page = NULL)
+ {
+ if ($page === NULL)
+ {
+ // Use the complete URI
+ $page = Router::$complete_uri;
+ }
+
+ parent::__construct('The page you requested, %page%, could not be found.', array('%page%' => $page));
+ }
+
+ /**
+ * Throws a new 404 exception.
+ *
+ * @throws Kohana_404_Exception
+ * @return void
+ */
+ public static function trigger($page = NULL)
+ {
+ throw new Kohana_404_Exception($page);
+ }
+
+ /**
+ * Sends 404 headers, to emulate server behavior.
+ *
+ * @return void
+ */
+ public function sendHeaders()
+ {
+ // Send the 404 header
+ header('HTTP/1.1 404 File Not Found');
+ }
+
+} // End Kohana 404 Exception \ No newline at end of file
diff --git a/system/libraries/Kohana_Log.php b/system/libraries/Kohana_Log.php
new file mode 100644
index 0000000..5126013
--- /dev/null
+++ b/system/libraries/Kohana_Log.php
@@ -0,0 +1,90 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Logging class.
+ *
+ * $Id: Kohana_Log.php 4729 2009-12-29 20:35:19Z isaiah $
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Kohana_Log_Core {
+
+ // Configuration
+ protected static $config;
+
+ // Drivers
+ protected static $drivers;
+
+ // Logged messages
+ protected static $messages;
+
+ /**
+ * Add a new message to the log.
+ *
+ * @param string type of message
+ * @param string message text
+ * @return void
+ */
+ public static function add($type, $message)
+ {
+ // Make sure the drivers and config are loaded
+ if ( ! is_array(Kohana_Log::$config))
+ {
+ Kohana_Log::$config = Kohana::config('log');
+ }
+
+ if ( ! is_array(Kohana_Log::$drivers))
+ {
+ foreach ( (array) Kohana::config('log.drivers') as $driver_name)
+ {
+ // Set driver name
+ $driver = 'Log_'.ucfirst($driver_name).'_Driver';
+
+ // Load the driver
+ if ( ! Kohana::auto_load($driver))
+ throw new Kohana_Exception('Log Driver Not Found: %driver%', array('%driver%' => $driver));
+
+ // Initialize the driver
+ $driver = new $driver(array_merge(Kohana::config('log'), Kohana::config('log_'.$driver_name)));
+
+ // Validate the driver
+ if ( ! ($driver instanceof Log_Driver))
+ throw new Kohana_Exception('%driver% does not implement the Log_Driver interface', array('%driver%' => $driver));
+
+ Kohana_Log::$drivers[] = $driver;
+ }
+
+ // Always save logs on shutdown
+ Event::add('system.shutdown', array('Kohana_Log', 'save'));
+ }
+
+ Kohana_Log::$messages[] = array('date' => time(), 'type' => $type, 'message' => $message);
+ }
+
+ /**
+ * Save all currently logged messages.
+ *
+ * @return void
+ */
+ public static function save()
+ {
+ if (empty(Kohana_Log::$messages))
+ return;
+
+ foreach (Kohana_Log::$drivers as $driver)
+ {
+ // We can't throw exceptions here or else we will get a
+ // Exception thrown without a stack frame error
+ try
+ {
+ $driver->save(Kohana_Log::$messages);
+ }
+ catch(Exception $e){}
+ }
+
+ // Reset the messages
+ Kohana_Log::$messages = array();
+ }
+} \ No newline at end of file
diff --git a/system/libraries/Kohana_PHP_Exception.php b/system/libraries/Kohana_PHP_Exception.php
new file mode 100644
index 0000000..779c229
--- /dev/null
+++ b/system/libraries/Kohana_PHP_Exception.php
@@ -0,0 +1,99 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Kohana PHP Error Exceptions
+ *
+ * $Id: Kohana_PHP_Exception.php 4729 2009-12-29 20:35:19Z isaiah $
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+
+class Kohana_PHP_Exception_Core extends Kohana_Exception {
+
+ public static $enabled = FALSE;
+
+ /**
+ * Enable Kohana PHP error handling.
+ *
+ * @return void
+ */
+ public static function enable()
+ {
+ if ( ! Kohana_PHP_Exception::$enabled)
+ {
+ // Handle runtime errors
+ set_error_handler(array('Kohana_PHP_Exception', 'error_handler'));
+
+ // Handle errors which halt execution
+ Event::add('system.shutdown', array('Kohana_PHP_Exception', 'shutdown_handler'));
+
+ Kohana_PHP_Exception::$enabled = TRUE;
+ }
+ }
+
+ /**
+ * Disable Kohana PHP error handling.
+ *
+ * @return void
+ */
+ public static function disable()
+ {
+ if (Kohana_PHP_Exception::$enabled)
+ {
+ restore_error_handler();
+
+ Event::clear('system.shutdown', array('Kohana_PHP_Exception', 'shutdown_handler'));
+
+ Kohana_PHP_Exception::$enabled = FALSE;
+ }
+ }
+
+ /**
+ * Create a new PHP error exception.
+ *
+ * @return void
+ */
+ public function __construct($code, $error, $file, $line, $context = NULL)
+ {
+ parent::__construct($error);
+
+ // Set the error code, file, line, and context manually
+ $this->code = $code;
+ $this->file = $file;
+ $this->line = $line;
+ }
+
+ /**
+ * PHP error handler.
+ *
+ * @throws Kohana_PHP_Exception
+ * @return void
+ */
+ public static function error_handler($code, $error, $file, $line, $context = NULL)
+ {
+ // Respect error_reporting settings
+ if (error_reporting() & $code)
+ {
+ // Throw an exception
+ throw new Kohana_PHP_Exception($code, $error, $file, $line, $context);
+ }
+ }
+
+ /**
+ * Catches errors that are not caught by the error handler, such as E_PARSE.
+ *
+ * @uses Kohana_Exception::handle()
+ * @return void
+ */
+ public static function shutdown_handler()
+ {
+ if (Kohana_PHP_Exception::$enabled AND $error = error_get_last() AND (error_reporting() & $error['type']))
+ {
+ // Fake an exception for nice debugging
+ Kohana_Exception::handle(new Kohana_PHP_Exception($error['type'], $error['message'], $error['file'], $error['line']));
+ }
+ }
+
+} // End Kohana PHP Exception
diff --git a/system/libraries/Kohana_User_Exception.php b/system/libraries/Kohana_User_Exception.php
new file mode 100644
index 0000000..a0ec3ac
--- /dev/null
+++ b/system/libraries/Kohana_User_Exception.php
@@ -0,0 +1,30 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Creates a custom exception message.
+ *
+ * $Id: Kohana_User_Exception.php 4729 2009-12-29 20:35:19Z isaiah $
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+
+class Kohana_User_Exception_Core extends Kohana_Exception {
+
+ /**
+ * Set exception title and message.
+ *
+ * @param string exception title string
+ * @param string exception message string
+ * @param string custom error template
+ */
+ public function __construct($title, $message, array $variables = NULL)
+ {
+ parent::__construct($message, $variables);
+
+ // Code is the error title
+ $this->code = $title;
+ }
+
+} // End Kohana User Exception
diff --git a/system/libraries/Model.php b/system/libraries/Model.php
new file mode 100644
index 0000000..ee50d50
--- /dev/null
+++ b/system/libraries/Model.php
@@ -0,0 +1,62 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Model base class.
+ *
+ * $Id: Model.php 4729 2009-12-29 20:35:19Z isaiah $
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Model_Core {
+
+ /**
+ * Creates and returns a new model.
+ *
+ * @param string model name
+ * @param mixed constructor arguments
+ * @param boolean construct the model with multiple arguments
+ * @return Model
+ */
+ public static function factory($name, $args = NULL, $multiple = FALSE)
+ {
+ // Model class name
+ $class = ucfirst($name).'_Model';
+
+ if ($args === NULL)
+ {
+ // Create a new model with no arguments
+ return new $class;
+ }
+
+ if ($multiple !== TRUE)
+ {
+ // Create a model with a single argument
+ return new $class($args);
+ }
+
+ $class = new ReflectionClass($class);
+
+ // Create a model with multiple arguments
+ return $class->newInstanceArgs($args);
+ }
+
+ // 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 0000000..16e047b
--- /dev/null
+++ b/system/libraries/ORM.php
@@ -0,0 +1,1528 @@
+<?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
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class ORM_Core {
+
+ // Current relationships
+ protected $has_one = array();
+ protected $belongs_to = array();
+ protected $has_many = array();
+ protected $has_and_belongs_to_many = array();
+ protected $has_many_through = array();
+
+ // Relationships that should always be joined
+ protected $load_with = array();
+
+ // Current object
+ protected $object = array();
+ protected $changed = array();
+ protected $related = array();
+ protected $_valid = FALSE;
+ protected $_loaded = FALSE;
+ protected $_saved = FALSE;
+ protected $sorting;
+ protected $rules = array();
+
+ // 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;
+
+ // Auto-update columns for creation and updates
+ protected $updated_column = NULL;
+ protected $created_column = NULL;
+
+ // 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();
+ protected $db_builder;
+
+ // 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))
+ {
+ // Set the object's primary key, but don't load it until needed
+ $this->object[$this->primary_key] = $id;
+
+ // Object is considered saved until something is set
+ $this->_saved = TRUE;
+ }
+ }
+
+ /**
+ * 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();
+
+ // Initialize the builder
+ $this->db_builder = db::build();
+ }
+
+ /**
+ * 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_builder, $method))
+ {
+ if (in_array($method, array('execute', 'insert', 'update', 'delete')))
+ throw new Kohana_Exception('Query methods cannot be used through ORM');
+
+ // 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_builder->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', 'and_open', 'or_open', 'close', 'cache')))
+ {
+ // Should return ORM, not Database
+ $this->db_builder->$method();
+ }
+ else
+ {
+ // Support for things like reset_select, reset_write, list_tables
+ return $this->db_builder->$method();
+ }
+ break;
+ case 1:
+ $this->db_builder->$method($args[0]);
+ break;
+ case 2:
+ $this->db_builder->$method($args[0], $args[1]);
+ break;
+ case 3:
+ $this->db_builder->$method($args[0], $args[1], $args[2]);
+ break;
+ case 4:
+ $this->db_builder->$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('Invalid method :method called in :class',
+ array(':method' => $method, ':class' => 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))
+ {
+ if( ! $this->loaded() AND ! $this->empty_primary_key())
+ {
+ // Column asked for but the object hasn't been loaded yet, so do it now
+ // Ignore loading of any columns that have been changed
+ $this->find($this->object[$this->primary_key], TRUE);
+ }
+
+ return $this->object[$column];
+ }
+ elseif (isset($this->related[$column]))
+ {
+ return $this->related[$column];
+ }
+ elseif ($column === 'primary_key_value')
+ {
+ if( ! $this->loaded() AND ! $this->empty_primary_key() AND $this->unique_key($this->object[$this->primary_key]) !== $this->primary_key)
+ {
+ // Load if object hasn't been loaded and the key given isn't the primary_key
+ // that we need (i.e. passing an email address to ORM::factory rather than the id)
+ $this->find($this->object[$this->primary_key], TRUE);
+ }
+
+ 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))
+ {
+ if ( ! $this->loaded() AND ! $this->empty_primary_key())
+ {
+ // Load this object first so we know what id to look for in the foreign table
+ $this->find($this->object[$this->primary_key], TRUE);
+ }
+
+ // Foreign key lies in this table (this model belongs_to target model)
+ $where = array($model->foreign_key(TRUE), '=', $this->object[$this->foreign_key($column)]);
+ }
+ else
+ {
+ // Foreign key lies in the target table (this model has_one target model)
+ $where = array($this->foreign_key($column, $model->table_name), '=', $this->primary_key_value);
+ }
+
+ // one<>alias:one relationship
+ return $this->related[$column] = $model->find($where);
+ }
+ elseif (isset($this->has_many_through[$column]))
+ {
+ // Load the "middle" model
+ $through = ORM::factory(inflector::singular($this->has_many_through[$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->foreign_key(TRUE);
+
+ // one<>alias:many relationship
+ return $model
+ ->join($join_table, $join_col1, $join_col2)
+ ->where($through->foreign_key($this->object_name, $join_table), '=', $this->primary_key_value);
+ }
+ elseif (isset($this->has_many[$column]))
+ {
+ // one<>many aliased relationship
+ $model_name = $this->has_many[$column];
+
+ $model = ORM::factory(inflector::singular($model_name));
+
+ return $model->where($this->foreign_key($column, $model->table_name), '=', $this->primary_key_value);
+ }
+ elseif (in_array($column, $this->has_many))
+ {
+ // one<>many relationship
+ $model = ORM::factory(inflector::singular($column));
+ return $model->where($this->foreign_key($column, $model->table_name), '=', $this->primary_key_value);
+ }
+ 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 $model->where($model->foreign_key(TRUE), 'IN', $this->changed_relations[$column]);
+ }
+ else
+ {
+ // empty many<>many relationship
+ return $model->where($model->foreign_key(TRUE), 'IS', NULL);
+ }
+ }
+ elseif (isset($this->ignored_columns[$column]))
+ {
+ return NULL;
+ }
+ elseif (in_array($column, array
+ (
+ 'object_name', 'object_plural','_valid', // Object
+ 'primary_key', 'primary_val', 'table_name', 'table_columns', // Table
+ 'has_one', 'belongs_to', 'has_many', 'has_many_through', 'has_and_belongs_to_many', 'load_with' // Relationships
+ )))
+ {
+ // Model meta information
+ return $this->$column;
+ }
+ else
+ {
+ throw new Kohana_Exception('The :property property does not exist in the :class class',
+ array(':property' => $column, ':class' => get_class($this)));
+ }
+ }
+
+ /**
+ * Tells you if the Model has been loaded or not
+ *
+ * @return bool
+ */
+ public function loaded() {
+ if ( ! $this->_loaded AND ! $this->empty_primary_key())
+ {
+ // If returning the loaded member and no load has been attempted, do it now
+ $this->find($this->object[$this->primary_key], TRUE);
+ }
+ return $this->_loaded;
+ }
+
+ /**
+ * Tells you if the model was saved successfully or not
+ *
+ * @return bool
+ */
+ public function saved() {
+ return $this->_saved;
+ }
+
+ /**
+ * 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 or valid
+ $this->_saved = $this->_valid = FALSE;
+ }
+
+ $this->object[$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('The :property: property does not exist in the :class: class',
+ array(':property:' => $column, ':class:' => get_class($this)));
+ }
+ }
+
+ /**
+ * Chainable set method
+ *
+ * @param string name of field or array of key => val
+ * @param mixed value
+ * @return ORM
+ */
+ 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 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]);
+ }
+
+ /**
+ * 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;
+ }
+
+ foreach ($this->with_applied as $model => $enabled)
+ {
+ // Generate arrays for relationships
+ if ($enabled)
+ {
+ $object[$model] = $this->$model->as_array();
+ }
+ }
+
+ 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 model to bind to
+ * @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;
+
+ $select = array();
+
+ // Use the keys of the empty object to determine the columns
+ foreach (array_keys($target->object) as $column)
+ {
+ // Add the prefix so that load_result can determine the relationship
+ $select[$target_path.':'.$column] = $target_path.'.'.$column;
+ }
+
+ // Select all of the prefixed keys in the object
+ $this->db_builder->select($select);
+
+ if (in_array($target->object_name, $parent->belongs_to))
+ {
+ // 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 trick allows for models to use different table prefixes (sharing the same database)
+ $join_table = array($this->db->quote_table($target_path) => $target->db->quote_table($target->table_name));
+
+ // Join the related object into the result
+ // Use Database_Expression to disable prefixing
+ $this->db_builder->join(new Database_Expression($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
+ * @param bool ignore loading of columns that have been modified
+ * @return ORM
+ */
+ public function find($id = NULL, $ignore_changed = FALSE)
+ {
+ if ($id !== NULL)
+ {
+ if (is_array($id))
+ {
+ // Search for all clauses
+ $this->db_builder->where(array($id));
+ }
+ else
+ {
+ // Search for a specific column
+ $this->db_builder->where($this->table_name.'.'.$this->unique_key($id), '=', $id);
+ }
+ }
+
+ return $this->load_result(FALSE, $ignore_changed);
+ }
+
+ /**
+ * 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($this->table_name.'.'.$key, $this->table_name.'.'.$val)->find_all()->select_list($key, $val);
+ }
+
+ /**
+ * Validates the current object. This method requires that rules are set to be useful, if called with out
+ * any rules set, or if a Validation object isn't passed, nothing happens.
+ *
+ * @param object Validation array
+ * @param boolean Save on validate
+ * @return ORM
+ * @chainable
+ */
+ public function validate(Validation $array = NULL)
+ {
+ if ($array === NULL)
+ $array = new Validation($this->object);
+
+ if (count($this->rules) > 0)
+ {
+ foreach ($this->rules as $field => $parameters)
+ {
+ foreach ($parameters as $type => $value) {
+ switch ($type) {
+ case 'pre_filter':
+ $array->pre_filter($value,$field);
+ break;
+ case 'rules':
+ $rules = array_merge(array($field),$value);
+ call_user_func_array(array($array,'add_rules'), $rules);
+ break;
+ case 'callbacks':
+ $callbacks = array_merge(array($field),$value);
+ call_user_func_array(array($array,'add_callbacks'), $callbacks);
+ break;
+ }
+ }
+ }
+ }
+ // Validate the array
+ if (($this->_valid = $array->validate()) === FALSE)
+ {
+ ORM_Validation_Exception::handle_validation($this->table_name, $array);
+ }
+
+ // Fields may have been modified by filters
+ $this->object = array_merge($this->object, $array->getArrayCopy());
+
+ // Return validation status
+ return $this;
+ }
+
+ /**
+ * Saves the current object.
+ *
+ * @chainable
+ * @return ORM
+ */
+ public function save()
+ {
+ if ( ! empty($this->changed))
+ {
+ // Require model validation before saving
+ if ( ! $this->_valid)
+ {
+ $this->validate();
+ }
+
+ $data = array();
+ foreach ($this->changed as $column)
+ {
+ // Compile changed data
+ $data[$column] = $this->object[$column];
+ }
+
+ if ( ! $this->empty_primary_key() AND ! isset($this->changed[$this->primary_key]))
+ {
+ // Primary key isn't empty and hasn't been changed so do an update
+
+ if (is_array($this->updated_column))
+ {
+ // Fill the updated column
+ $column = $this->updated_column['column'];
+ $format = $this->updated_column['format'];
+
+ $data[$column] = $this->object[$column] = ($format === TRUE) ? time() : date($format);
+ }
+
+ $query = db::update($this->table_name)
+ ->set($data)
+ ->where($this->primary_key, '=', $this->primary_key_value)
+ ->execute($this->db);
+
+ // Object has been saved
+ $this->_saved = TRUE;
+ }
+ else
+ {
+ if (is_array($this->created_column))
+ {
+ // Fill the created column
+ $column = $this->created_column['column'];
+ $format = $this->created_column['format'];
+
+ $data[$column] = $this->object[$column] = ($format === TRUE) ? time() : date($format);
+ }
+
+ $result = db::insert($this->table_name)
+ ->columns(array_keys($data))
+ ->values(array_values($data))
+ ->execute($this->db);
+
+ if ($result->count() > 0)
+ {
+ if (empty($this->object[$this->primary_key]))
+ {
+ // Load the insert id as the primary key
+ $this->object[$this->primary_key] = $result->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($join_table);
+ $related_fk = $model->foreign_key($join_table);
+
+ if ( ! empty($added))
+ {
+ foreach ($added as $id)
+ {
+ // Insert the new relationship
+ db::insert($join_table)
+ ->columns($object_fk, $related_fk)
+ ->values($this->primary_key_value, $id)
+ ->execute($this->db);
+ }
+ }
+
+ if ( ! empty($removed))
+ {
+ db::delete($join_table)
+ ->where($object_fk, '=', $this->primary_key_value)
+ ->where($related_fk, 'IN', $removed)
+ ->execute($this->db);
+ }
+
+ // Clear all relations for this column
+ unset($this->object_relations[$column], $this->changed_relations[$column]);
+ }
+ }
+
+ if ($this->saved() === TRUE)
+ {
+ // Always force revalidation after saving
+ $this->_valid = FALSE;
+
+ // Clear the per-request database cache
+ $this->db->clear_cache(NULL, Database::PER_REQUEST);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Deletes the current object from the database. This does NOT destroy
+ * relationships that have been created with other objects.
+ *
+ * @chainable
+ * @param mixed id to delete
+ * @return ORM
+ */
+ public function delete($id = NULL)
+ {
+ if ($id === NULL)
+ {
+ // Use the the primary key value
+ $id = $this->primary_key_value;
+ }
+
+ // Delete this object
+ db::delete($this->table_name)
+ ->where($this->primary_key, '=', $id)
+ ->execute($this->db);
+
+ // Clear the per-request database cache
+ $this->db->clear_cache(NULL, Database::PER_REQUEST);
+
+ return $this->clear();
+ }
+
+ /**
+ * 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
+ db::delete($this->table_name)
+ ->where($this->primary_key, 'IN', $ids)
+ ->execute($this->db);
+ }
+ elseif ($ids === NULL)
+ {
+ // Delete all records
+ db::delete($this->table_name)
+ ->execute($this->db);
+ }
+ else
+ {
+ // Do nothing - safeguard
+ return $this;
+ }
+
+ // Clear the per-request database cache
+ $this->db->clear_cache(NULL, Database::PER_REQUEST);
+
+ 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()
+ {
+ if ($this->_loaded) {
+ return $this->find($this->object[$this->primary_key]);
+ } else {
+ return $this->clear();
+ }
+ }
+
+ /**
+ * 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->loaded() AND ! $model->empty_primary_key())
+ {
+ // Load the related model if it hasn't already been
+ $model->find($model->object[$model->primary_key]);
+ }
+
+ 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_builder->count_records($this->table_name);
+ }
+
+ /**
+ * Proxy method to Database list_fields.
+ *
+ * @return array
+ */
+ public function list_fields()
+ {
+ // Proxy to database
+ return $this->db->list_fields($this->table_name);
+ }
+
+ /**
+ * Proxy method to Database clear_cache.
+ *
+ * @chainable
+ * @param string SQL query to clear
+ * @param integer Type of cache to clear, Database::CROSS_REQUEST or Database::PER_REQUEST
+ * @return ORM
+ */
+ public function clear_cache($sql = NULL, $type = NULL)
+ {
+ // Proxy to database
+ $this->db->clear_cache($sql, $type);
+
+ 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)
+ {
+ // Related objects
+ $related = array();
+
+ foreach ($values as $column => $value)
+ {
+ if (strpos($column, ':') === FALSE)
+ {
+ if ( ! isset($this->changed[$column]))
+ {
+ if (isset($this->table_columns[$column]))
+ {
+ //Update the column, respects __get()
+ $this->$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 an array of values into into the current object. Only used internally
+ *
+ * @chainable
+ * @param array values to load
+ * @param bool ignore loading of columns that have been modified
+ * @return ORM
+ */
+ public function _load_values(array $values, $ignore_changed = FALSE)
+ {
+ if (array_key_exists($this->primary_key, $values))
+ {
+ if ( ! $ignore_changed)
+ {
+ // 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 ( ! $ignore_changed OR ! isset($this->changed[$column]))
+ {
+ $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 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
+ * @param boolean ignore loading of columns that have been modified
+ * @return ORM for single rows
+ * @return ORM_Iterator for multiple rows
+ */
+ protected function load_result($array = FALSE, $ignore_changed = FALSE)
+ {
+ $this->db_builder->from($this->table_name);
+
+ if ($array === FALSE)
+ {
+ // Only fetch 1 record
+ $this->db_builder->limit(1);
+ }
+
+ if ( ! isset($this->db_applied['select']))
+ {
+ // Select all columns by default
+ $this->db_builder->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['order_by']) 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_builder->order_by($sorting);
+ }
+
+ // Load the result
+ $result = $this->db_builder->execute($this->db);
+ $this->db_applied = array();
+
+ if ($array === TRUE)
+ {
+ // Return an iterated result
+ return new ORM_Iterator($this, $result);
+ }
+
+ if ($result->count() === 1)
+ {
+ // Load object values
+ $this->_load_values($result->as_array()->current(), $ignore_changed);
+ }
+ 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)
+ {
+ $result = db::select(array('id' => $model->foreign_key($table)))
+ ->from($table)
+ ->where($this->foreign_key($table, $table), '=', $this->primary_key_value)
+ ->execute($this->db)
+ ->as_object();
+
+ $relations = array();
+ foreach ($result 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 0000000..0bf2b47
--- /dev/null
+++ b/system/libraries/ORM_Iterator.php
@@ -0,0 +1,266 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Object Relational Mapping (ORM) result iterator.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+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 (make sure rows are returned as arrays)
+ $this->result = $result;
+ }
+
+ /**
+ * Returns an array of the results as ORM objects or a nested array
+ *
+ * @param bool TRUE to return an array of ORM objects, FALSE for an array of arrays
+ * @param string key column to index on, NULL to ignore
+ * @return array
+ */
+ public function as_array($objects = TRUE, $key = NULL)
+ {
+ $array = array();
+
+ // Import class name
+ $class = $this->class_name;
+
+ if ($objects)
+ {
+ // Generate an array of objects
+ foreach ($this->result as $data)
+ {
+ if ($key === NULL)
+ {
+ // No indexing
+ $array[] = new $class($data);
+ }
+ else
+ {
+ // Index on the given key
+ $array[$data->$key] = new $class($data);
+ }
+ }
+ }
+ else
+ {
+ // Generate an array of arrays (and the subarrays may be nested in the case of relationships)
+ // This could be done by creating a new ORM object and calling as_array on it, but this is much faster
+ foreach ($this->result as $data)
+ {
+ // Have to do a bit of magic here to handle any relationships and generate a nested array for them
+ $temp = array();
+
+ foreach ($data as $key => $val)
+ {
+ $ptr = & $temp;
+
+ foreach (explode(':', $key) as $subkey)
+ {
+ // Walk thru the relationships (separated in the key name by a ':')
+ // 'user:email:address' will be array['user']['email']['address']
+ $ptr = & $ptr[$subkey];
+ }
+
+ // Set the value
+ $ptr = $val;
+ }
+
+ // Append the result
+ $array[] = $temp;
+ }
+ }
+
+ 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 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_Validation_Exception.php b/system/libraries/ORM_Validation_Exception.php
new file mode 100644
index 0000000..9044aa6
--- /dev/null
+++ b/system/libraries/ORM_Validation_Exception.php
@@ -0,0 +1,24 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * ORM Validation exceptions.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2008-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class ORM_Validation_Exception_Core extends Database_Exception {
+
+ /**
+ * Handles Database Validation Exceptions
+ *
+ * @param Validation $array
+ * @return
+ */
+ public static function handle_validation($table, Validation $array)
+ {
+ $exception = new ORM_Validation_Exception('ORM Validation has failed for :table model',array(':table'=>$table));
+ $exception->validation = $array;
+ throw $exception;
+ }
+} // End ORM_Validation_Exception \ No newline at end of file
diff --git a/system/libraries/Profiler.php b/system/libraries/Profiler.php
new file mode 100644
index 0000000..3ac8707
--- /dev/null
+++ b/system/libraries/Profiler.php
@@ -0,0 +1,306 @@
+<?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.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Profiler_Core {
+
+ protected static $profiles = array();
+ protected static $show;
+
+ /**
+ * Enable the profiler.
+ *
+ * @return void
+ */
+ public static function enable()
+ {
+ // Add all built in profiles to event
+ Event::add('profiler.run', array('Profiler', 'benchmarks'));
+ Event::add('profiler.run', array('Profiler', 'database'));
+ Event::add('profiler.run', array('Profiler', 'session'));
+ Event::add('profiler.run', array('Profiler', 'post'));
+ Event::add('profiler.run', array('Profiler', 'cookies'));
+
+ // Add profiler to page output automatically
+ Event::add('system.display', array('Profiler', 'render'));
+
+ Kohana_Log::add('debug', 'Profiler library enabled');
+
+ }
+
+ /**
+ * Disables the profiler for this page only.
+ * Best used when profiler is autoloaded.
+ *
+ * @return void
+ */
+ public static function disable()
+ {
+ // Removes itself from the event queue
+ Event::clear('system.display', array('Profiler', 'render'));
+ }
+
+ /**
+ * Return whether a profile should be shown.
+ * Determined by the config setting or GET parameter.
+ *
+ * @param string profile name
+ * @return boolean
+ */
+ public static function show($name)
+ {
+ return (Profiler::$show === TRUE OR (is_array(Profiler::$show) AND in_array($name, Profiler::$show))) ? TRUE : FALSE;
+ }
+
+ /**
+ * Add a new profile.
+ *
+ * @param object profile object
+ * @return boolean
+ * @throws Kohana_Exception
+ */
+ public static function add($profile)
+ {
+ if (is_object($profile))
+ {
+ Profiler::$profiles[] = $profile;
+ return TRUE;
+ }
+
+ throw new Kohana_Exception('The profile must be an object');
+ }
+
+ /**
+ * Render the profiler.
+ *
+ * @param boolean return the output instead of adding it to bottom of page
+ * @return void|string
+ */
+ public static function render($return = FALSE)
+ {
+ $start = microtime(TRUE);
+
+ // Determine the profiles that should be shown
+ $get = isset($_GET['profiler']) ? explode(',', $_GET['profiler']) : array();
+ Profiler::$show = empty($get) ? Kohana::config('profiler.show') : $get;
+
+ Event::run('profiler.run');
+
+ // Don't display if there's no profiles
+ if (empty(Profiler::$profiles))
+ return Kohana::$output;
+
+ $styles = '';
+ foreach (Profiler::$profiles as $profile)
+ {
+ $styles .= $profile->styles();
+ }
+
+ // Load the profiler view
+ $data = array
+ (
+ 'profiles' => Profiler::$profiles,
+ 'styles' => $styles,
+ 'execution_time' => microtime(TRUE) - $start
+ );
+ $view = new View('profiler/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 static function benchmarks()
+ {
+ if ( ! Profiler::show('benchmarks'))
+ return;
+
+ $table = new Profiler_Table();
+ $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'), __('Count'), __('Time'), __('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), $benchmark['count'], number_format($benchmark['time'], Kohana::config('profiler.time_decimals')), number_format($benchmark['memory'] / 1024 / 1024, Kohana::config('profiler.memory_decimals')).'MB');
+ $class = text::alternate('', 'kp-altrow');
+
+ if ($name == 'Total Execution')
+ {
+ // Clear the count column
+ $data[1] = '';
+ $class = 'kp-totalrow';
+ }
+
+ $table->add_row($data, $class);
+ }
+
+ Profiler::add($table);
+ }
+
+ /**
+ * Database query benchmarks.
+ *
+ * @return void
+ */
+ public static function database()
+ {
+ if ( ! Profiler::show('database'))
+ return;
+
+ $queries = Database::$benchmarks;
+
+ // Don't show if there are no queries
+ if (empty($queries)) return;
+
+ $table = new Profiler_Table();
+ $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');
+
+ text::alternate();
+ $total_time = $total_rows = 0;
+ foreach ($queries as $query)
+ {
+ $data = array($query['query'], number_format($query['time'], Kohana::config('profiler.time_decimals')), $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, Kohana::config('profiler.time_decimals')), $total_rows);
+ $table->add_row($data, 'kp-totalrow');
+
+ Profiler::add($table);
+ }
+
+ /**
+ * Session data.
+ *
+ * @return void
+ */
+ public static function session()
+ {
+ if (empty($_SESSION)) return;
+
+ if ( ! Profiler::show('session'))
+ return;
+
+ $table = new Profiler_Table();
+ $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);
+ }
+
+ Profiler::add($table);
+ }
+
+ /**
+ * POST data.
+ *
+ * @return void
+ */
+ public static function post()
+ {
+ if (empty($_POST)) return;
+
+ if ( ! Profiler::show('post'))
+ return;
+
+ $table = new Profiler_Table();
+ $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);
+ }
+
+ Profiler::add($table);
+ }
+
+ /**
+ * Cookie data.
+ *
+ * @return void
+ */
+ public static function cookies()
+ {
+ if (empty($_COOKIE)) return;
+
+ if ( ! Profiler::show('cookies'))
+ return;
+
+ $table = new Profiler_Table();
+ $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);
+ }
+
+ Profiler::add($table);
+ }
+}
diff --git a/system/libraries/Profiler_Table.php b/system/libraries/Profiler_Table.php
new file mode 100644
index 0000000..e590ad7
--- /dev/null
+++ b/system/libraries/Profiler_Table.php
@@ -0,0 +1,67 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Provides a table layout for sections in the Profiler library.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+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', '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('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 0000000..c36121d
--- /dev/null
+++ b/system/libraries/Router.php
@@ -0,0 +1,315 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Router
+ *
+ * $Id: Router.php 4729 2009-12-29 20:35:19Z isaiah $
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+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 = '?'.urldecode(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 (empty(Router::$routes['_default']))
+ throw new Kohana_Exception('Please set a default route in config/routes.php.');
+
+ // Use the default route when no segments exist
+ Router::$current_uri = Router::$routes['_default'];
+
+ // Default route is in use
+ $default_route = TRUE;
+ }
+
+ // Remove all dot-paths from the URI, they are not valid
+ Router::$current_uri = preg_replace('#\.[\s./]*/#', '', Router::$current_uri);
+
+ // At this point routed URI and current URI are the same
+ Router::$routed_uri = Router::$current_uri = trim(Router::$current_uri, '/');
+
+ if ($default_route === TRUE)
+ {
+ Router::$complete_uri = Router::$query_string;
+ Router::$current_uri = '';
+ Router::$segments = array();
+ }
+ else
+ {
+ Router::$complete_uri = Router::$current_uri.Router::$query_string;
+
+ // Explode the segments by slashes
+ Router::$segments = explode('/', Router::$current_uri);
+
+ if (count(Router::$routes) > 1)
+ {
+ // Custom routing
+ Router::$routed_uri = Router::routed_uri(Router::$current_uri);
+ }
+ }
+
+ // Explode the routed segments by slashes
+ Router::$rsegments = explode('/', Router::$routed_uri);
+
+ // 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 (Kohana::$server_api === '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 (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 = Input::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']);
+ }
+ else
+ {
+ if (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'])
+ {
+ // PATH_INFO is empty during requests to the front controller
+ Router::$current_uri = $_SERVER['PHP_SELF'];
+ }
+
+ if (isset($_SERVER['SCRIPT_NAME']) AND $_SERVER['SCRIPT_NAME'])
+ {
+ // Clean up PATH_INFO fallbacks
+ // PATH_INFO may be formatted for ISAPI instead of CGI on IIS
+ if (strncmp(Router::$current_uri, $_SERVER['SCRIPT_NAME'], strlen($_SERVER['SCRIPT_NAME'])) === 0)
+ {
+ // Remove the front controller from the current uri
+ Router::$current_uri = (string) substr(Router::$current_uri, strlen($_SERVER['SCRIPT_NAME']));
+ }
+ }
+ }
+
+ // 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 0000000..e57908e
--- /dev/null
+++ b/system/libraries/Session.php
@@ -0,0 +1,500 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Session library.
+ *
+ * $Id: Session.php 4729 2009-12-29 20:35:19Z isaiah $
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+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;
+
+ // Automatically save the session by default
+ public static $should_save = true;
+
+ /**
+ * Singleton instance of Session.
+ *
+ * @param string Force a specific session_id
+ */
+ public static function instance($session_id = NULL)
+ {
+ if (Session::$instance == NULL)
+ {
+ // Create a new instance
+ new Session($session_id);
+ }
+ elseif( ! is_null($session_id) AND $session_id != session_id() )
+ {
+ throw new Kohana_Exception('A session (SID: :session:) is already open, cannot open the specified session (SID: :new_session:).', array(':session:' => session_id(), ':new_session:' => $session_id));
+ }
+
+ return Session::$instance;
+ }
+
+ /**
+ * Be sure to block the use of __clone.
+ */
+ private function __clone(){}
+
+ /**
+ * On first session instance creation, sets up the driver and creates session.
+ *
+ * @param string Force a specific session_id
+ */
+ protected function __construct($session_id = NULL)
+ {
+ $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(NULL, $session_id);
+
+ 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 on system shutdown (run before sending the headers), so that
+ // the session cookie(s) can be written.
+ Event::add('system.shutdown', array($this, 'write_close'));
+
+ // Singleton instance
+ Session::$instance = $this;
+ }
+
+ Kohana_Log::add('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
+ * @param string Force a specific session_id
+ * @return void
+ */
+ public function create($vars = NULL, $session_id = 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('The :driver: driver for the :library: library could not be found',
+ array(':driver:' => Session::$config['driver'], ':library:' => get_class($this)));
+
+ // Initialize the driver
+ Session::$driver = new $driver();
+
+ // Validate the driver
+ if ( ! (Session::$driver instanceof Session_Driver))
+ throw new Kohana_Exception('The :driver: driver for the :library: library must implement the :interface: interface',
+ array(':driver:' => Session::$config['driver'], ':library:' => get_class($this), ':interface:' => '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('The session_name, :session:, is invalid. It must contain only alphanumeric characters and underscores. Also at least one letter must be present.', array(':session:' => 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')
+ );
+
+ $cookie = cookie::get(Session::$config['name']);
+
+ if ($session_id === NULL)
+ {
+ // Reopen session from signed cookie value.
+ $session_id = $cookie;
+ }
+
+ // Reopen an existing session if supplied
+ if ( ! is_null($session_id))
+ {
+ session_id($session_id);
+ }
+
+ // 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'] = request::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] !== request::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::set($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]);
+ }
+ }
+
+ /**
+ * Do not save this session.
+ * This is a performance feature only, if using the native
+ * session "driver" the save will NOT be aborted.
+ *
+ * @return void
+ */
+ public function abort_save()
+ {
+ Session::$should_save = FALSE;
+ }
+
+} // End Session Class
diff --git a/system/libraries/URI.php b/system/libraries/URI.php
new file mode 100644
index 0000000..16d101a
--- /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 4729 2009-12-29 20:35:19Z isaiah $
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+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 0000000..9917fbb
--- /dev/null
+++ b/system/libraries/Validation.php
@@ -0,0 +1,815 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Validation library.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+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();
+
+ // Field labels
+ protected $labels = array();
+
+ // Fields that are expected to be arrays
+ protected $array_fields = array();
+
+ /**
+ * 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)
+ {
+ 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;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Sets or overwrites the label name for a field.
+ *
+ * @param string field name
+ * @param string label
+ * @return $this
+ */
+ public function label($field, $label = NULL)
+ {
+ if ($label === NULL AND ($field !== TRUE OR $field !== '*') AND ! isset($this->labels[$field]))
+ {
+ // Set the field label to the field name
+ $this->labels[$field] = ucfirst(preg_replace('/[^\pL]+/u', ' ', $field));
+ }
+ elseif ($label !== NULL)
+ {
+ // Set the label for this field
+ $this->labels[$field] = $label;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets labels using an array.
+ *
+ * @param array list of field => label names
+ * @return $this
+ */
+ public function labels(array $labels)
+ {
+ $this->labels = $labels + $this->labels;
+
+ return $this;
+ }
+
+ /**
+ * Converts a filter, rule, or callback into a fully-qualified callback array.
+ *
+ * @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('Callback %name% used for Validation is not callable', array('%name%' => $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);
+
+ // Set a default field label
+ $this->label($field);
+
+ if ($field === TRUE)
+ {
+ // Use wildcard
+ $field = '*';
+ }
+
+ foreach ($rules as $rule)
+ {
+ // Arguments for rule
+ $args = NULL;
+
+ // False rule
+ $false_rule = FALSE;
+
+ $rule_tmp = trim(is_string($rule) ? $rule : $rule[1]);
+
+ // Should the rule return false?
+ if ($rule_tmp !== ($rule_name = ltrim($rule_tmp, '! ')))
+ {
+ $false_rule = TRUE;
+ }
+
+ if (is_string($rule))
+ {
+ // Use the updated rule name
+ $rule = $rule_name;
+
+ // Have arguments?
+ 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);
+ }
+ }
+ else
+ {
+ $rule[1] = $rule_name;
+ }
+
+ 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, $false_rule);
+ }
+
+ 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);
+
+ // Set a default field label
+ $this->label($field);
+
+ 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;
+ }
+
+ $array = $this->safe_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)
+ {
+ $array[$f] = is_array($array[$f]) ? array_map($callback, $array[$f]) : call_user_func($callback, $array[$f]);
+ }
+ }
+ else
+ {
+ $array[$field] = is_array($array[$field]) ? array_map($callback, $array[$field]) : call_user_func($callback, $array[$field]);
+ }
+ }
+ }
+
+ foreach ($this->rules as $field => $callbacks)
+ {
+ foreach ($callbacks as $callback)
+ {
+ // Separate the callback, arguments and is false bool
+ list ($callback, $args, $is_false) = $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($array[$f]) AND ! in_array($rule, $this->empty_rules))
+ {
+ // This rule does not need to be processed on empty fields
+ continue;
+ }
+
+ $result = ($args === NULL) ? call_user_func($callback, $array[$f]) : call_user_func($callback, $array[$f], $args);
+
+ if (($result == $is_false))
+ {
+ $this->add_error($f, $rule, $args);
+
+ // 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($array[$field]))
+ {
+ // This rule does not need to be processed on empty fields
+ continue;
+ }
+
+ // Results of our test
+ $result = ($args === NULL) ? call_user_func($callback, $array[$field]) : call_user_func($callback, $array[$field], $args);
+
+ if (($result == $is_false))
+ {
+ $rule = $is_false ? '!'.$rule : $rule;
+ $this->add_error($field, $rule, $args);
+
+ // 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)
+ {
+ $array[$f] = is_array($array[$f]) ? array_map($callback, $array[$f]) : call_user_func($callback, $array[$f]);
+ }
+ }
+ else
+ {
+ $array[$field] = is_array($array[$field]) ? array_map($callback, $array[$field]) : call_user_func($callback, $array[$field]);
+ }
+ }
+ }
+
+ // Swap the array back into the object
+ $this->exchangeArray($array);
+
+ // 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
+ * @param string arguments to pass to lang file
+ * @return object
+ */
+ public function add_error($field, $name, $args = NULL)
+ {
+ $this->errors[$field] = array($name, $args);
+
+ return $this;
+ }
+
+ /**
+ * Return the errors array.
+ *
+ * @param boolean load errors from a message file
+ * @return array
+ */
+ public function errors($file = NULL)
+ {
+ if ($file === NULL)
+ {
+ $errors = array();
+ foreach($this->errors as $field => $error)
+ {
+ $errors[$field] = $error[0];
+ }
+ return $errors;
+ }
+ else
+ {
+ $errors = array();
+ foreach ($this->errors as $input => $error)
+ {
+ // Locations to check for error messages
+ $error_locations = array
+ (
+ "validation/{$file}.{$input}.{$error[0]}",
+ "validation/{$file}.{$input}.default",
+ "validation/default.{$error[0]}"
+ );
+
+ if (($message = Kohana::message($error_locations[0])) !== $error_locations[0])
+ {
+ // Found a message for this field and error
+ }
+ elseif (($message = Kohana::message($error_locations[1])) !== $error_locations[1])
+ {
+ // Found a default message for this field
+ }
+ elseif (($message = Kohana::message($error_locations[2])) !== $error_locations[2])
+ {
+ // Found a default message for this error
+ }
+ else
+ {
+ // No message exists, display the path expected
+ $message = "validation/{$file}.{$input}.{$error[0]}";
+ }
+
+ // Start the translation values list
+ $values = array(':field' => __($this->labels[$input]));
+
+ if ( ! empty($error[1]))
+ {
+ foreach ($error[1] as $key => $value)
+ {
+ // Add each parameter as a numbered value, starting from 1
+ $values[':param'.($key + 1)] = __($value);
+ }
+ }
+
+ // Translate the message using the default language
+ $errors[$input] = __($message, $values);
+ }
+
+ return $errors;
+ }
+ }
+
+ /**
+ * 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 = mb_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 \ No newline at end of file
diff --git a/system/libraries/View.php b/system/libraries/View.php
new file mode 100644
index 0000000..ba482bb
--- /dev/null
+++ b/system/libraries/View.php
@@ -0,0 +1,329 @@
+<?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 4729 2009-12-29 20:35:19Z isaiah $
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+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();
+
+ /**
+ * 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('The requested filetype, .:type:, is not allowed in your view configuration file', array(':type:' => $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)) ? TRUE : FALSE;
+ }
+ }
+ else
+ {
+ // Otherwise just check one property
+ $result = (array_key_exists($key, $this->kohana_local_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;
+ }
+
+ /**
+ * 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];
+ }
+ elseif (isset($this->$key))
+ {
+ return $this->$key;
+ }
+ else
+ {
+ throw new Kohana_Exception('Undefined view variable: :var',
+ array(':var' => $key));
+ }
+ }
+
+ /**
+ * Magically converts view object to string.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ try
+ {
+ return $this->render();
+ }
+ catch (Exception $e)
+ {
+ Kohana_Exception::handle($e);
+ return (string) '';
+ }
+ }
+
+ /**
+ * 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
+ * @param callback modifier to pass the data through before rendering
+ * @return string if print is FALSE
+ * @return void if print is TRUE
+ */
+ public function render($print = FALSE, $renderer = FALSE, $modifier = FALSE)
+ {
+ if (empty($this->kohana_filename))
+ throw new Kohana_Exception('You must set the the view filename before calling render');
+
+ if (is_string($this->kohana_filetype))
+ {
+ // Merge global and local data, local overrides global with the same name
+ $data = $this->kohana_local_data;
+
+ if ($modifier !== FALSE AND is_callable($modifier, TRUE))
+ {
+ // Pass the data through the user defined modifier
+ $data = call_user_func($modifier, $data);
+ }
+
+ $output = $this->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;
+ }
+
+ /**
+ * Includes a View within the controller scope.
+ *
+ * @param string view filename
+ * @param array array of view variables
+ * @return string
+ */
+ public function 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);
+
+ try
+ {
+ include $kohana_view_filename;
+ }
+ catch (Exception $e)
+ {
+ ob_end_clean();
+ throw $e;
+ }
+
+ // Fetch the output and close the buffer
+ return ob_get_clean();
+ }
+} // End View
diff --git a/system/libraries/drivers/Cache.php b/system/libraries/drivers/Cache.php
new file mode 100644
index 0000000..9741509
--- /dev/null
+++ b/system/libraries/drivers/Cache.php
@@ -0,0 +1,42 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Cache driver abstract class.
+ *
+ * $Id$
+ *
+ * @package Cache
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+abstract class Cache_Driver {
+ /**
+ * Set cache items
+ */
+ abstract public function set($items, $tags = NULL, $lifetime = NULL);
+
+ /**
+ * Get a cache items by key
+ */
+ abstract public function get($keys, $single = FALSE);
+
+ /**
+ * Get cache items by tag
+ */
+ abstract public function get_tag($tags);
+
+ /**
+ * Delete cache item by key
+ */
+ abstract public function delete($keys);
+
+ /**
+ * Delete cache items by tag
+ */
+ abstract public function delete_tag($tags);
+
+ /**
+ * Empty the cache
+ */
+ abstract public function delete_all();
+} // End Cache 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 0000000..d6ec037
--- /dev/null
+++ b/system/libraries/drivers/Cache/File.php
@@ -0,0 +1,255 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Memcache-based Cache driver.
+ *
+ * $Id: File.php 4605 2009-09-14 17:22:21Z kiall $
+ *
+ * @package Cache
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Cache_File_Driver extends Cache_Driver {
+ protected $config;
+ protected $backend;
+
+ public function __construct($config)
+ {
+ $this->config = $config;
+ $this->config['directory'] = str_replace('\\', '/', realpath($this->config['directory'])).'/';
+
+ if ( ! is_dir($this->config['directory']) OR ! is_writable($this->config['directory']))
+ throw new Cache_Exception('The configured cache directory, :directory:, is not writable.', array(':directory:' => $this->config['directory']));
+ }
+
+ /**
+ * Finds an array of files matching the given id or tag.
+ *
+ * @param string cache key or tag
+ * @param bool search for tags
+ * @return array of filenames matching the id or tag
+ */
+ public function exists($keys, $tag = FALSE)
+ {
+ if ($keys === TRUE)
+ {
+ // Find all the files
+ return glob($this->config['directory'].'*~*~*');
+ }
+ elseif ($tag === TRUE)
+ {
+ // Find all the files that have the tag name
+ $paths = array();
+
+ foreach ( (array) $keys as $tag)
+ {
+ $paths = array_merge($paths, glob($this->config['directory'].'*~*'.$tag.'*~*'));
+ }
+
+ // Find all tags matching the given tag
+ $files = array();
+
+ foreach ($paths as $path)
+ {
+ // Split the files
+ $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
+ $item_tags = explode('+', $tags[1]);
+
+ // Check each supplied tag, and match aginst the tags on each item.
+ foreach ($keys as $tag)
+ {
+ if (in_array($tag, $item_tags))
+ {
+ // Add the file to the array, it has the requested tag
+ $files[] = $path;
+ }
+ }
+ }
+
+ return $files;
+ }
+ else
+ {
+ $paths = array();
+
+ foreach ( (array) $keys as $key)
+ {
+ // Find the file matching the given key
+ $paths = array_merge($paths, glob($this->config['directory'].str_replace(array('/', '\\', ' '), '_', $key).'~*'));
+ }
+
+ return $paths;
+ }
+ }
+
+ public function set($items, $tags = NULL, $lifetime = NULL)
+ {
+ if ($lifetime !== 0)
+ {
+ // File driver expects unix timestamp
+ $lifetime += time();
+ }
+
+
+ if ( ! is_null($tags) AND ! empty($tags))
+ {
+ // Convert the tags into a string list
+ $tags = implode('+', (array) $tags);
+ }
+
+ $success = TRUE;
+
+ foreach ($items as $key => $value)
+ {
+ if (is_resource($value))
+ throw new Cache_Exception('Caching of resources is impossible, because resources cannot be serialised.');
+
+ // Remove old cache file
+ $this->delete($key);
+
+ if ( ! (bool) file_put_contents($this->config['directory'].str_replace(array('/', '\\', ' '), '_', $key).'~'.$tags.'~'.$lifetime, serialize($value)))
+ {
+ $success = FALSE;
+ }
+ }
+
+ return $success;
+ }
+
+ public function get($keys, $single = FALSE)
+ {
+ $items = array();
+
+ if ($files = $this->exists($keys))
+ {
+ // Turn off errors while reading the files
+ $ER = error_reporting(0);
+
+ foreach ($files as $file)
+ {
+ // Validate that the item has not expired
+ if ($this->expired($file))
+ continue;
+
+ list($key, $junk) = explode('~', basename($file), 2);
+
+ if (($data = file_get_contents($file)) !== FALSE)
+ {
+ // Unserialize the data
+ $data = unserialize($data);
+ }
+ else
+ {
+ $data = NULL;
+ }
+
+ $items[$key] = $data;
+ }
+
+ // Turn errors back on
+ error_reporting($ER);
+ }
+
+ // Reutrn a single item if only one key was requested
+ if ($single)
+ {
+ return (count($items) > 0) ? current($items) : NULL;
+ }
+ else
+ {
+ return $items;
+ }
+ }
+
+ /**
+ * Get cache items by tag
+ */
+ public function get_tag($tags)
+ {
+ // An array will always be returned
+ $result = array();
+
+ if ($paths = $this->exists($tags, TRUE))
+ {
+ // Find all the files with the given tag
+ foreach ($paths as $path)
+ {
+ // Get the id from the filename
+ list($key, $junk) = explode('~', basename($path), 2);
+
+ if (($data = $this->get($key, TRUE)) !== FALSE)
+ {
+ // Add the result to the array
+ $result[$key] = $data;
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Delete cache items by keys or tags
+ */
+ public function delete($keys, $tag = FALSE)
+ {
+ $success = TRUE;
+
+ $paths = $this->exists($keys, $tag);
+
+ // Disable all error reporting while deleting
+ $ER = error_reporting(0);
+
+ foreach ($paths as $path)
+ {
+ // Remove the cache file
+ if ( ! unlink($path))
+ {
+ Kohana_Log::add('error', 'Cache: Unable to delete cache file: '.$path);
+ $success = FALSE;
+ }
+ }
+
+ // Turn on error reporting again
+ error_reporting($ER);
+
+ return $success;
+ }
+
+ /**
+ * Delete cache items by tag
+ */
+ public function delete_tag($tags)
+ {
+ return $this->delete($tags, TRUE);
+ }
+
+ /**
+ * Empty the cache
+ */
+ public function delete_all()
+ {
+ return $this->delete(TRUE);
+ }
+
+ /**
+ * Check if a cache file has expired by filename.
+ *
+ * @param string|array array of filenames
+ * @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 Memcache Driver
diff --git a/system/libraries/drivers/Cache/Memcache.php b/system/libraries/drivers/Cache/Memcache.php
new file mode 100644
index 0000000..13d61d8
--- /dev/null
+++ b/system/libraries/drivers/Cache/Memcache.php
@@ -0,0 +1,132 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Memcache-based Cache driver.
+ *
+ * $Id$
+ *
+ * @package Cache
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Cache_Memcache_Driver extends Cache_Driver {
+ protected $config;
+ protected $backend;
+ protected $flags;
+
+ public function __construct($config)
+ {
+ if ( ! extension_loaded('memcache'))
+ throw new Cache_Exception('The memcache PHP extension must be loaded to use this driver.');
+
+ ini_set('memcache.allow_failover', (isset($config['allow_failover']) AND $config['allow_failover']) ? TRUE : FALSE);
+
+ $this->config = $config;
+ $this->backend = new Memcache;
+
+ $this->flags = (isset($config['compression']) AND $config['compression']) ? MEMCACHE_COMPRESSED : FALSE;
+
+ foreach ($config['servers'] as $server)
+ {
+ // Make sure all required keys are set
+ $server += array('host' => '127.0.0.1',
+ 'port' => 11211,
+ 'persistent' => FALSE,
+ 'weight' => 1,
+ 'timeout' => 1,
+ 'retry_interval' => 15
+ );
+
+ // Add the server to the pool
+ $this->backend->addServer($server['host'], $server['port'], (bool) $server['persistent'], (int) $server['weight'], (int) $server['timeout'], (int) $server['retry_interval'], TRUE, array($this,'_memcache_failure_callback'));
+ }
+ }
+
+ public function _memcache_failure_callback($host, $port)
+ {
+ $this->backend->setServerParams($host, $port, 1, -1, FALSE);
+ Kohana_Log::add('error', __('Cache: Memcache server down: :host:::port:',array(':host:' => $host,':port:' => $port)));
+ }
+
+ public function set($items, $tags = NULL, $lifetime = NULL)
+ {
+ if ($lifetime !== 0)
+ {
+ // Memcache driver expects unix timestamp
+ $lifetime += time();
+ }
+
+ if ($tags !== NULL)
+ throw new Cache_Exception('Memcache driver does not support tags');
+
+ foreach ($items as $key => $value)
+ {
+ if (is_resource($value))
+ throw new Cache_Exception('Caching of resources is impossible, because resources cannot be serialised.');
+
+ if ( ! $this->backend->set($key, $value, $this->flags, $lifetime))
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+ }
+
+ public function get($keys, $single = FALSE)
+ {
+ $items = $this->backend->get($keys);
+
+ if ($single)
+ {
+ if ($items === FALSE)
+ return NULL;
+
+ return (count($items) > 0) ? current($items) : NULL;
+ }
+ else
+ {
+ return ($items === FALSE) ? array() : $items;
+ }
+ }
+
+ /**
+ * Get cache items by tag
+ */
+ public function get_tag($tags)
+ {
+ throw new Cache_Exception('Memcache driver does not support tags');
+ }
+
+ /**
+ * Delete cache item by key
+ */
+ public function delete($keys)
+ {
+ foreach ($keys as $key)
+ {
+ if ( ! $this->backend->delete($key))
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+ }
+
+ /**
+ * Delete cache items by tag
+ */
+ public function delete_tag($tags)
+ {
+ throw new Cache_Exception('Memcache driver does not support tags');
+ }
+
+ /**
+ * Empty the cache
+ */
+ public function delete_all()
+ {
+ return $this->backend->flush();
+ }
+} // End Cache Memcache Driver
diff --git a/system/libraries/drivers/Cache/Xcache.php b/system/libraries/drivers/Cache/Xcache.php
new file mode 100644
index 0000000..6761983
--- /dev/null
+++ b/system/libraries/drivers/Cache/Xcache.php
@@ -0,0 +1,161 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * XCache-based Cache driver.
+ *
+ * $Id: Memcache.php 4605 2009-09-14 17:22:21Z kiall $
+ *
+ * @package Cache
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ * @TODO Check if XCache cleans its own keys.
+ */
+class Cache_Xcache_Driver extends Cache_Driver {
+ protected $config;
+
+ public function __construct($config)
+ {
+ if ( ! extension_loaded('xcache'))
+ throw new Cache_Exception('The xcache PHP extension must be loaded to use this driver.');
+
+ $this->config = $config;
+ }
+
+ public function set($items, $tags = NULL, $lifetime = NULL)
+ {
+ if ($tags !== NULL)
+ {
+ Kohana_Log::add('debug', __('Cache: XCache driver does not support tags'));
+ }
+
+ foreach ($items as $key => $value)
+ {
+ if (is_resource($value))
+ throw new Cache_Exception('Caching of resources is impossible, because resources cannot be serialised.');
+
+ if ( ! xcache_set($key, $value, $lifetime))
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+ }
+
+ public function get($keys, $single = FALSE)
+ {
+ $items = array();
+
+ foreach ($keys as $key)
+ {
+ if (xcache_isset($key))
+ {
+ $items[$key] = xcache_get($key);
+ }
+ else
+ {
+ $items[$key] = NULL;
+ }
+ }
+
+ if ($single)
+ {
+ return ($items === FALSE OR count($items) > 0) ? current($items) : NULL;
+ }
+ else
+ {
+ return ($items === FALSE) ? array() : $items;
+ }
+ }
+
+ /**
+ * Get cache items by tag
+ */
+ public function get_tag($tags)
+ {
+ Kohana_Log::add('debug', __('Cache: XCache driver does not support tags'));
+ return NULL;
+ }
+
+ /**
+ * Delete cache item by key
+ */
+ public function delete($keys)
+ {
+ foreach ($keys as $key)
+ {
+ if ( ! xcache_unset($key))
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+ }
+
+ /**
+ * Delete cache items by tag
+ */
+ public function delete_tag($tags)
+ {
+ Kohana_Log::add('debug', __('Cache: XCache driver does not support tags'));
+ return NULL;
+ }
+
+ /**
+ * Empty the cache
+ */
+ public function delete_all()
+ {
+ $this->auth();
+ $result = TRUE;
+
+ for ($i = 0, $max = xcache_count(XC_TYPE_VAR); $i < $max; $i++)
+ {
+ if (xcache_clear_cache(XC_TYPE_VAR, $i) !== NULL)
+ {
+ $result = FALSE;
+ break;
+ }
+ }
+
+ // Undo the login
+ $this->auth(TRUE);
+
+ return $result;
+ }
+
+ private function auth($reverse = FALSE)
+ {
+ 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] = $this->config->{$key};
+ }
+ }
+ }
+} // End Cache XCache Driver
diff --git a/system/libraries/drivers/Config.php b/system/libraries/drivers/Config.php
new file mode 100644
index 0000000..a82684b
--- /dev/null
+++ b/system/libraries/drivers/Config.php
@@ -0,0 +1,257 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Kohana_Config abstract driver to get and set
+ * configuration options.
+ *
+ * Specific drivers should implement caching and encryption
+ * as they deem appropriate.
+ *
+ * $Id: Config.php 4679 2009-11-10 01:45:52Z isaiah $
+ *
+ * @package Kohana_Config
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ * @abstract
+ */
+abstract class Config_Driver {
+
+ /**
+ * Internal caching
+ *
+ * @var Cache
+ */
+ protected $cache;
+
+ /**
+ * The name of the internal cache
+ *
+ * @var string
+ */
+ protected $cache_name = 'Kohana_Config_Cache';
+
+ /**
+ * Cache Lifetime
+ *
+ * @var mixed
+ */
+ protected $cache_lifetime = FALSE;
+
+ /**
+ * The Encryption library
+ *
+ * @var Encrypt
+ */
+ protected $encrypt;
+
+ /**
+ * The config loaded
+ *
+ * @var array
+ */
+ protected $config = array();
+
+ /**
+ * The changed status of configuration values,
+ * current state versus the stored state.
+ *
+ * @var bool
+ */
+ protected $changed = FALSE;
+
+ /**
+ * Determines if any config has been loaded yet
+ */
+ public $loaded = FALSE;
+
+ /**
+ * Array driver constructor. Sets up the PHP array
+ * driver, including caching and encryption if
+ * required
+ *
+ * @access public
+ */
+ public function __construct($config)
+ {
+
+ if (($cache_setting = $config['internal_cache']) !== FALSE)
+ {
+ $this->cache_lifetime = $cache_setting;
+ // Restore the cached configuration
+ $this->config = $this->load_cache();
+
+ if (count($this->config) > 0)
+ $this->loaded = TRUE;
+
+ // Add the save cache method to system.shutshut event
+ Event::add('system.shutdown', array($this, 'save_cache'));
+ }
+
+ }
+
+ /**
+ * Gets a value from config. If required is TRUE
+ * then get will throw an exception if value cannot
+ * be loaded.
+ *
+ * @param string key the setting to get
+ * @param bool slash remove trailing slashes
+ * @param bool required is setting required?
+ * @return mixed
+ * @access public
+ */
+ public function get($key, $slash = FALSE, $required = FALSE)
+ {
+ // Get the group name from the key
+ $group = explode('.', $key, 2);
+ $group = $group[0];
+
+ // Check for existing value and load it dynamically if required
+ if ( ! isset($this->config[$group]))
+ $this->config[$group] = $this->load($group, $required);
+
+ // Get the value of the key string
+ $value = Kohana::key_string($this->config, $key);
+
+ if ($slash === TRUE AND is_string($value) AND $value !== '')
+ {
+ // Force the value to end with "/"
+ $value = rtrim($value, '/').'/';
+ }
+
+ if (($required === TRUE) AND ($value === null))
+ throw new Kohana_Config_Exception('Value not found in config driver');
+
+ $this->loaded = TRUE;
+ return $value;
+ }
+
+ /**
+ * Sets a new value to the configuration
+ *
+ * @param string key
+ * @param mixed value
+ * @return bool
+ * @access public
+ */
+ public function set($key, $value)
+ {
+ // Do this to make sure that the config array is already loaded
+ $this->get($key);
+
+ if (substr($key, 0, 7) === 'routes.')
+ {
+ // Routes cannot contain sub keys due to possible dots in regex
+ $keys = explode('.', $key, 2);
+ }
+ else
+ {
+ // Convert dot-noted key string to an array
+ $keys = explode('.', $key);
+ }
+
+ // Used for recursion
+ $conf =& $this->config;
+ $last = count($keys) - 1;
+
+ foreach ($keys as $i => $k)
+ {
+ if ($i === $last)
+ {
+ $conf[$k] = $value;
+ }
+ else
+ {
+ $conf =& $conf[$k];
+ }
+ }
+
+ if (substr($key,0,12) === 'core.modules')
+ {
+ // Reprocess the include paths
+ Kohana::include_paths(TRUE);
+ }
+
+ // Set config to changed
+ return $this->changed = TRUE;
+ }
+
+ /**
+ * Clear the configuration
+ *
+ * @param string group
+ * @return bool
+ * @access public
+ */
+ public function clear($group)
+ {
+ // Remove the group from config
+ unset($this->config[$group]);
+
+ // Set config to changed
+ return $this->changed = TRUE;
+ }
+
+ /**
+ * Checks whether the setting exists in
+ * config
+ *
+ * @param string $key
+ * @return bool
+ * @access public
+ */
+ public function setting_exists($key)
+ {
+ return $this->get($key) === NULL;
+ }
+
+ /**
+ * Loads a configuration group based on the setting
+ *
+ * @param string group
+ * @param bool required
+ * @return array
+ * @access public
+ * @abstract
+ */
+ abstract public function load($group, $required = FALSE);
+
+ /**
+ * Loads the cached version of this configuration driver
+ *
+ * @return array
+ * @access public
+ */
+ public function load_cache()
+ {
+ // Load the cache for this configuration
+ $cached_config = Kohana::cache($this->cache_name, $this->cache_lifetime);
+
+ // If the configuration wasn't loaded from the cache
+ if ($cached_config === NULL)
+ $cached_config = array();
+
+ // Return the cached config
+ return $cached_config;
+ }
+
+ /**
+ * Saves a cached version of this configuration driver
+ *
+ * @return bool
+ * @access public
+ */
+ public function save_cache()
+ {
+ // If this configuration has changed
+ if ($this->get('core.internal_cache') !== FALSE AND $this->changed)
+ {
+ $data = $this->config;
+
+ // Save the cache
+ return Kohana::cache_save($this->cache_name, $data, $this->cache_lifetime);
+ }
+
+ return TRUE;
+ }
+} // End Kohana_Config_Driver \ No newline at end of file
diff --git a/system/libraries/drivers/Config/Array.php b/system/libraries/drivers/Config/Array.php
new file mode 100644
index 0000000..b2ca19b
--- /dev/null
+++ b/system/libraries/drivers/Config/Array.php
@@ -0,0 +1,83 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Kohana_Config Array driver to get and set
+ * configuration options using PHP arrays.
+ *
+ * This driver can cache and encrypt settings
+ * if required.
+ *
+ * $Id: Array.php 4679 2009-11-10 01:45:52Z isaiah $
+ *
+ * @package Kohana_Config
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Config_Array_Driver extends Config_Driver {
+
+ /**
+ * Internal caching
+ *
+ * @var Cache
+ */
+ protected $cache;
+
+ /**
+ * The name of the internal cache
+ *
+ * @var string
+ */
+ protected $cache_name = 'Kohana_Config_Array_Cache';
+
+ /**
+ * The Encryption library
+ *
+ * @var Encrypt
+ */
+ protected $encrypt;
+
+ /**
+ * Loads a configuration group based on the setting
+ *
+ * @param string group
+ * @param bool required
+ * @return array
+ * @access public
+ */
+ public function load($group, $required = FALSE)
+ {
+ if ($group === 'core')
+ {
+ // Load the application configuration file
+ require APPPATH.'config/config'.EXT;
+
+ if ( ! isset($config['site_domain']))
+ {
+ // Invalid config file
+ throw new Kohana_Config_Exception('Your Kohana application configuration file is not valid.');
+ }
+
+ return $config;
+ }
+
+ // Load matching configs
+ $configuration = array();
+
+ if ($files = Kohana::find_file('config', $group, $required))
+ {
+ foreach ($files as $file)
+ {
+ require $file;
+
+ if (isset($config) AND is_array($config))
+ {
+ // Merge in configuration
+ $configuration = array_merge($configuration, $config);
+ }
+ }
+ }
+
+ // Return merged configuration
+ return $configuration;
+ }
+} // End Config_Array_Driver \ No newline at end of file
diff --git a/system/libraries/drivers/Image.php b/system/libraries/drivers/Image.php
new file mode 100644
index 0000000..39936c3
--- /dev/null
+++ b/system/libraries/drivers/Image.php
@@ -0,0 +1,158 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Image API driver.
+ *
+ * $Id: Image.php 4679 2009-11-10 01:45:52Z isaiah $
+ *
+ * @package Image
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+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
+ * @param boolean render the image
+ * @param string background color
+ * @return boolean
+ */
+ abstract public function process($image, $actions, $dir, $file, $render = FALSE, $background = NULL);
+
+ /**
+ * 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 0000000..6ffffe8
--- /dev/null
+++ b/system/libraries/drivers/Image/GD.php
@@ -0,0 +1,440 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * GD Image Driver.
+ *
+ * $Id: GD.php 4679 2009-11-10 01:45:52Z isaiah $
+ *
+ * @package Image
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+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('The Image library requires GD2. Please see http://php.net/gd_info for more information.');
+
+ // Get the GD information
+ $info = gd_info();
+
+ // Make sure that the GD2 is installed
+ if (strpos($info['GD Version'], '2.') === FALSE)
+ throw new Kohana_Exception('The Image library requires GD2. Please see http://php.net/gd_info for more information.');
+ }
+
+ public function process($image, $actions, $dir, $file, $render = FALSE, $background = NULL)
+ {
+ // 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('The specified image, :type:, is not an allowed image type.', array(':type:' => $image['file']));
+
+ // Make sure the image type is supported for saving
+ if (empty($save) OR ! function_exists($save))
+ throw new Kohana_Exception('The specified image, :type:, is not an allowed image type.', array(':type:' => $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('Your configured driver does not support the :method: image transformation.', array(':method:' => __FUNCTION__));
+
+ // Amount should be in the range of 18-10
+ $amount = round(abs(-18 + ($amount * 0.08)), 2);
+
+ // 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;
+ }
+
+ $this->imagecopymerge_alpha($this->tmp_image, $overlay_img, $properties['x'], $properties['y'], 0, 0, imagesx($overlay_img), imagesy($overlay_img), $properties['transparency']);
+
+ imagedestroy($overlay_img);
+
+ return TRUE;
+ }
+
+ /**
+ * A replacement for php's imagecopymerge() function that supports the alpha channel
+ * See php bug #23815: http://bugs.php.net/bug.php?id=23815
+ *
+ * @param resource $dst_im Destination image link resource
+ * @param resource $src_im Source image link resource
+ * @param integer $dst_x x-coordinate of destination point
+ * @param integer $dst_y y-coordinate of destination point
+ * @param integer $src_x x-coordinate of source point
+ * @param integer $src_y y-coordinate of source point
+ * @param integer $src_w Source width
+ * @param integer $src_h Source height
+ * @param integer $pct Transparency percent (0 to 100)
+ */
+ protected function imagecopymerge_alpha($dst_im, $src_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $pct)
+ {
+ // Create a new blank image the site of our source image
+ $cut = imagecreatetruecolor($src_w, $src_h);
+
+ // Copy the blank image into the destination image where the source goes
+ imagecopy($cut, $dst_im, 0, 0, $dst_x, $dst_y, $src_w, $src_h);
+
+ // Place the source image in the destination image
+ imagecopy($cut, $src_im, 0, 0, $src_x, $src_y, $src_w, $src_h);
+ imagecopymerge($dst_im, $cut, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $pct);
+ }
+
+ protected function properties()
+ {
+ return array(imagesx($this->tmp_image), imagesy($this->tmp_image));
+ }
+
+ /**
+ * 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 ($width < 1)
+ {
+ $width = 1;
+ }
+
+ if ($height < 1)
+ {
+ $height = 1;
+ }
+
+ if (self::$blank_png === NULL)
+ {
+ // Decode the blank PNG if it has not been done already
+ 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 0000000..89b40b4
--- /dev/null
+++ b/system/libraries/drivers/Image/GraphicsMagick.php
@@ -0,0 +1,225 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * GraphicsMagick Image Driver.
+ *
+ * @package Image
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+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('The GraphicsMagick directory specified does not contain a required program.');
+
+ $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('The GraphicsMagick directory specified does not contain a required program, :gm:.', array(':gm:' => '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, $background = NULL)
+ {
+ // Need to implement $background support
+ if ($background !== NULL)
+ throw new Kohana_Exception('The GraphicsMagick driver does not support setting a background color');
+
+ // We only need the filename
+ $image = $image['file'];
+
+ // 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 0000000..55c0ba2
--- /dev/null
+++ b/system/libraries/drivers/Image/ImageMagick.php
@@ -0,0 +1,233 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * ImageMagick Image Driver.
+ *
+ * $Id: ImageMagick.php 4679 2009-11-10 01:45:52Z isaiah $
+ *
+ * @package Image
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+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('The ImageMagick directory specified does not contain a required program.');
+
+ $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('The ImageMagick directory specified does not contain a required program, :im:', array(':im:' => 'convert'.$this->ext));
+
+ // Set the installation directory
+ $this->dir = str_replace('\\', '/', realpath($config['directory'])).'/';
+ }
+
+ /**
+ * 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, $background = NULL)
+ {
+ // 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;
+
+ if (is_string($background))
+ {
+ // Set the background color
+ $this->background = escapeshellarg($background);
+ }
+ else
+ {
+ // Use a transparent background
+ $this->background = 'transparent';
+ }
+
+ // All calls to these will need to be escaped, so do it now
+ $this->cmd_image = escapeshellarg($this->tmp_image);
+ $this->new_image = $render ? $this->cmd_image : escapeshellarg($dir.$file);
+
+ 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).' -background '.$this->background.' -flatten -quality '.$quality.'% '.$this->cmd_image.' '.$this->new_image))
+ {
+ $this->errors[] = $error;
+ }
+ else
+ {
+ // Output the image directly to the browser
+ if ($render === TRUE)
+ {
+ $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).' -background '.$this->background.' -flatten -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).' -background '.$this->background.' -flatten '.$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).' -background '.$this->background.' -flatten -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).' -background '.$this->background.' -flatten -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).' -background '.$this->background.' -flatten -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/Log.php b/system/libraries/drivers/Log.php
new file mode 100644
index 0000000..cd6dba7
--- /dev/null
+++ b/system/libraries/drivers/Log.php
@@ -0,0 +1,22 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Log API driver.
+ *
+ * $Id: Log.php 4679 2009-11-10 01:45:52Z isaiah $
+ *
+ * @package Kohana_Log
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+abstract class Log_Driver {
+
+ protected $config = array();
+
+ public function __construct(array $config)
+ {
+ $this->config = $config;
+ }
+
+ abstract public function save(array $messages);
+} \ No newline at end of file
diff --git a/system/libraries/drivers/Log/Database.php b/system/libraries/drivers/Log/Database.php
new file mode 100644
index 0000000..19db974
--- /dev/null
+++ b/system/libraries/drivers/Log/Database.php
@@ -0,0 +1,40 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Log API driver.
+ *
+ * $Id: Database.php 4679 2009-11-10 01:45:52Z isaiah $
+ *
+ * @package Kohana_Log
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Log_Database_Driver extends Log_Driver {
+
+ public function save(array $messages)
+ {
+ $insert = db::build($this->config['group'])
+ ->insert($this->config['table'])
+ ->columns(array('date', 'level', 'message'));
+
+ $run_insert = FALSE;
+
+ foreach ($messages AS $message)
+ {
+ if ($this->config['log_levels'][$message['type']] <= $this->config['log_threshold'])
+ {
+ // Add new message to database
+ $insert->values($message);
+
+ // There is data to insert
+ $run_insert = TRUE;
+ }
+ }
+
+ // Update the database
+ if ($run_insert)
+ {
+ $insert->execute();
+ }
+ }
+} \ No newline at end of file
diff --git a/system/libraries/drivers/Log/File.php b/system/libraries/drivers/Log/File.php
new file mode 100644
index 0000000..6ad565b
--- /dev/null
+++ b/system/libraries/drivers/Log/File.php
@@ -0,0 +1,44 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Log API driver.
+ *
+ * $Id: File.php 4679 2009-11-10 01:45:52Z isaiah $
+ *
+ * @package Kohana_Log
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Log_File_Driver extends Log_Driver {
+
+ public function save(array $messages)
+ {
+ // Filename of the log
+ $filename = $this->config['log_directory'].'/'.date('Y-m-d').'.log'.EXT;
+
+ if ( ! is_file($filename))
+ {
+ // Write the SYSPATH checking header
+ file_put_contents($filename,
+ '<?php defined(\'SYSPATH\') or die(\'No direct script access.\'); ?>'.PHP_EOL.PHP_EOL);
+
+ // Prevent external writes
+ chmod($filename, $this->config['posix_permissions']);
+ }
+
+ foreach ($messages AS $message)
+ {
+ if ($this->config['log_levels'][$message['type']] <= $this->config['log_threshold'])
+ {
+ // Add a new message line
+ $messages_to_write[] = date($this->config['date_format'], $message['date']).' --- '.$message['type'].': '.$message['message'];
+ }
+ }
+
+ if ( ! empty($messages_to_write))
+ {
+ // Write messages to log file
+ file_put_contents($filename, implode(PHP_EOL, $messages_to_write).PHP_EOL, FILE_APPEND);
+ }
+ }
+} \ No newline at end of file
diff --git a/system/libraries/drivers/Log/Syslog.php b/system/libraries/drivers/Log/Syslog.php
new file mode 100644
index 0000000..5da5d25
--- /dev/null
+++ b/system/libraries/drivers/Log/Syslog.php
@@ -0,0 +1,34 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Log API driver.
+ *
+ * @package Kohana_Log
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Log_Syslog_Driver extends Log_Driver {
+
+ protected $syslog_levels = array('error' => LOG_ERR,
+ 'alert' => LOG_WARNING,
+ 'info' => LOG_INFO,
+ 'debug' => LOG_DEBUG);
+
+ public function save(array $messages)
+ {
+ // Open the connection to syslog
+ openlog($this->config['ident'], LOG_CONS, LOG_USER);
+
+ do
+ {
+ // Load the next message
+ list ($date, $type, $text) = array_shift($messages);
+
+ syslog($this->syslog_levels[$type], $text);
+ }
+ while ( ! empty($messages));
+
+ // Close connection to syslog
+ closelog();
+ }
+} \ No newline at end of file
diff --git a/system/libraries/drivers/Session.php b/system/libraries/drivers/Session.php
new file mode 100644
index 0000000..e591b91
--- /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 4729 2009-12-29 20:35:19Z isaiah $
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+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 0000000..459f8b0
--- /dev/null
+++ b/system/libraries/drivers/Session/Cache.php
@@ -0,0 +1,108 @@
+<?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 4729 2009-12-29 20:35:19Z isaiah $
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+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::add('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('The :group: group is not defined in your configuration.', array(':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)
+ {
+ if ( ! Session::$should_save)
+ return TRUE;
+
+ $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 0000000..88f5e21
--- /dev/null
+++ b/system/libraries/drivers/Session/Cookie.php
@@ -0,0 +1,83 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Session cookie driver.
+ *
+ * $Id: Cookie.php 4729 2009-12-29 20:35:19Z isaiah $
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+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::add('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)
+ {
+ if ( ! Session::$should_save)
+ return TRUE;
+
+ $data = empty($this->encrypt) ? base64_encode($data) : $this->encrypt->encode($data);
+
+ if (strlen($data) > 4048)
+ {
+ Kohana_Log::add('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 0000000..7b372d2
--- /dev/null
+++ b/system/libraries/drivers/Session/Database.php
@@ -0,0 +1,178 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Session database driver.
+ *
+ * $Id: Database.php 4729 2009-12-29 20:35:19Z isaiah $
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+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'];
+ }
+ }
+
+ Kohana_Log::add('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 = db::select('data')
+ ->from($this->table)
+ ->where('session_id', '=', $id)
+ ->limit(1)
+ ->execute($this->db);
+
+ 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)
+ {
+ if ( ! Session::$should_save)
+ return TRUE;
+
+ $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 = db::insert($this->table, $data)
+ ->execute($this->db);
+ }
+ elseif ($id === $this->session_id)
+ {
+ // Do not update the session_id
+ unset($data['session_id']);
+
+ // Update the existing session
+ $query = db::update($this->table)
+ ->set($data)
+ ->where('session_id', '=', $id)
+ ->execute($this->db);
+ }
+ else
+ {
+ // Update the session and id
+ $query = db::update($this->table)
+ ->set($data)
+ ->where('session_id', '=', $this->session_id)
+ ->execute($this->db);
+
+ // Set the new session id
+ $this->session_id = $id;
+ }
+
+ return (bool) $query->count();
+ }
+
+ public function destroy($id)
+ {
+ // Delete the requested session
+ db::delete($this->table)
+ ->where('session_id', '=', $id)
+ ->execute($this->db);
+
+ // 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 = db::delete($this->table)
+ ->where('last_activity', '<', time() - $maxlifetime)
+ ->execute($this->db);
+
+ Kohana_Log::add('debug', 'Session garbage collected: '.$query->count().' row(s) deleted.');
+
+ return TRUE;
+ }
+
+} // End Session Database Driver