From 9b6663f87a7e679ffba691cf516191fc840cf978 Mon Sep 17 00:00:00 2001 From: Bharat Mediratta Date: Tue, 24 Nov 2009 19:20:36 -0800 Subject: Update to Kohana r4684 which is now Kohana 2.4 and has substantial changes. --- system/libraries/Cache.php | 145 +- system/libraries/Cache_Exception.php | 12 + system/libraries/Controller.php | 41 +- system/libraries/Database.php | 1677 ++++++--------------- system/libraries/Database_Builder.php | 938 ++++++++++++ system/libraries/Database_Cache_Result.php | 83 + system/libraries/Database_Exception.php | 17 + system/libraries/Database_Expression.php | 21 +- system/libraries/Database_Mysql.php | 229 +++ system/libraries/Database_Mysql_Result.php | 176 +++ system/libraries/Database_Mysqli.php | 92 ++ system/libraries/Database_Mysqli_Result.php | 177 +++ system/libraries/Database_Query.php | 95 ++ system/libraries/Database_Result.php | 170 +++ system/libraries/Encrypt.php | 30 +- system/libraries/Event_Observer.php | 70 - system/libraries/Event_Subject.php | 67 - system/libraries/I18n.php | 103 ++ system/libraries/Image.php | 103 +- system/libraries/Input.php | 266 ++-- system/libraries/Kohana_404_Exception.php | 56 + system/libraries/Kohana_Log.php | 90 ++ system/libraries/Kohana_PHP_Exception.php | 99 ++ system/libraries/Kohana_User_Exception.php | 30 + system/libraries/Model.php | 35 +- system/libraries/ORM.php | 525 ++++--- system/libraries/ORM_Iterator.php | 68 +- system/libraries/ORM_Tree.php | 76 - system/libraries/ORM_Validation_Exception.php | 26 + system/libraries/ORM_Versioned.php | 143 -- system/libraries/Pagination.php | 236 --- system/libraries/Profiler.php | 173 ++- system/libraries/Profiler_Table.php | 10 +- system/libraries/Router.php | 97 +- system/libraries/Session.php | 66 +- system/libraries/URI.php | 6 +- system/libraries/Validation.php | 146 +- system/libraries/View.php | 104 +- system/libraries/drivers/Cache.php | 36 +- system/libraries/drivers/Cache/Apc.php | 64 - system/libraries/drivers/Cache/Eaccelerator.php | 66 - system/libraries/drivers/Cache/File.php | 268 ++-- system/libraries/drivers/Cache/Memcache.php | 200 +-- system/libraries/drivers/Cache/Sqlite.php | 257 ---- system/libraries/drivers/Cache/Xcache.php | 144 +- system/libraries/drivers/Captcha.php | 227 --- system/libraries/drivers/Captcha/Alpha.php | 92 -- system/libraries/drivers/Captcha/Basic.php | 81 - system/libraries/drivers/Captcha/Black.php | 72 - system/libraries/drivers/Captcha/Math.php | 61 - system/libraries/drivers/Captcha/Riddle.php | 47 - system/libraries/drivers/Captcha/Word.php | 37 - system/libraries/drivers/Config.php | 257 ++++ system/libraries/drivers/Config/Array.php | 83 + system/libraries/drivers/Database.php | 636 -------- system/libraries/drivers/Database/Mssql.php | 462 ------ system/libraries/drivers/Database/Mysql.php | 496 ------ system/libraries/drivers/Database/Mysqli.php | 358 ----- system/libraries/drivers/Database/Pdosqlite.php | 486 ------ system/libraries/drivers/Database/Pgsql.php | 538 ------- system/libraries/drivers/Image.php | 10 +- system/libraries/drivers/Image/GD.php | 69 +- system/libraries/drivers/Image/GraphicsMagick.php | 14 +- system/libraries/drivers/Image/ImageMagick.php | 39 +- system/libraries/drivers/Log.php | 22 + system/libraries/drivers/Log/Database.php | 40 + system/libraries/drivers/Log/File.php | 44 + system/libraries/drivers/Log/Syslog.php | 34 + system/libraries/drivers/Session.php | 6 +- system/libraries/drivers/Session/Cache.php | 10 +- system/libraries/drivers/Session/Cookie.php | 10 +- system/libraries/drivers/Session/Database.php | 40 +- 72 files changed, 4840 insertions(+), 6964 deletions(-) create mode 100644 system/libraries/Cache_Exception.php create mode 100644 system/libraries/Database_Builder.php create mode 100644 system/libraries/Database_Cache_Result.php create mode 100644 system/libraries/Database_Exception.php create mode 100644 system/libraries/Database_Mysql.php create mode 100644 system/libraries/Database_Mysql_Result.php create mode 100644 system/libraries/Database_Mysqli.php create mode 100644 system/libraries/Database_Mysqli_Result.php create mode 100644 system/libraries/Database_Query.php create mode 100644 system/libraries/Database_Result.php delete mode 100644 system/libraries/Event_Observer.php delete mode 100644 system/libraries/Event_Subject.php create mode 100644 system/libraries/I18n.php create mode 100644 system/libraries/Kohana_404_Exception.php create mode 100644 system/libraries/Kohana_Log.php create mode 100644 system/libraries/Kohana_PHP_Exception.php create mode 100644 system/libraries/Kohana_User_Exception.php delete mode 100644 system/libraries/ORM_Tree.php create mode 100644 system/libraries/ORM_Validation_Exception.php delete mode 100644 system/libraries/ORM_Versioned.php delete mode 100644 system/libraries/Pagination.php delete mode 100644 system/libraries/drivers/Cache/Apc.php delete mode 100644 system/libraries/drivers/Cache/Eaccelerator.php delete mode 100644 system/libraries/drivers/Cache/Sqlite.php delete mode 100644 system/libraries/drivers/Captcha.php delete mode 100644 system/libraries/drivers/Captcha/Alpha.php delete mode 100644 system/libraries/drivers/Captcha/Basic.php delete mode 100644 system/libraries/drivers/Captcha/Black.php delete mode 100644 system/libraries/drivers/Captcha/Math.php delete mode 100644 system/libraries/drivers/Captcha/Riddle.php delete mode 100644 system/libraries/drivers/Captcha/Word.php create mode 100644 system/libraries/drivers/Config.php create mode 100644 system/libraries/drivers/Config/Array.php delete mode 100644 system/libraries/drivers/Database.php delete mode 100644 system/libraries/drivers/Database/Mssql.php delete mode 100644 system/libraries/drivers/Database/Mysql.php delete mode 100644 system/libraries/drivers/Database/Mysqli.php delete mode 100644 system/libraries/drivers/Database/Pdosqlite.php delete mode 100644 system/libraries/drivers/Database/Pgsql.php create mode 100644 system/libraries/drivers/Log.php create mode 100644 system/libraries/drivers/Log/Database.php create mode 100644 system/libraries/drivers/Log/File.php create mode 100644 system/libraries/drivers/Log/Syslog.php (limited to 'system/libraries') diff --git a/system/libraries/Cache.php b/system/libraries/Cache.php index 8a02a905..2ed344fa 100644 --- a/system/libraries/Cache.php +++ b/system/libraries/Cache.php @@ -4,20 +4,17 @@ * resources. Caches are identified by a unique string. Tagging of caches is * also supported, and caches can be found and deleted by id or tag. * - * $Id: Cache.php 4321 2009-05-04 01:39:44Z kiall $ + * $Id: Cache.php 4605 2009-09-14 17:22:21Z kiall $ * * @package Cache * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html + * @copyright (c) 2007-2009 Kohana Team + * @license http://kohanaphp.com/license */ class Cache_Core { protected static $instances = array(); - // For garbage collection - protected static $loaded; - // Configuration protected $config; @@ -55,7 +52,7 @@ class Cache_Core { // Test the config group name if (($config = Kohana::config('cache.'.$config)) === NULL) - throw new Kohana_Exception('cache.undefined_group', $name); + throw new Kohana_Exception('The :group: group is not defined in your configuration.', array(':group:' => $name)); } if (is_array($config)) @@ -77,132 +74,98 @@ class Cache_Core { // Load the driver if ( ! Kohana::auto_load($driver)) - throw new Kohana_Exception('core.driver_not_found', $this->config['driver'], get_class($this)); + throw new Kohana_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 Kohana_Exception('core.driver_implements', $this->config['driver'], get_class($this), 'Cache_Driver'); - - Kohana::log('debug', 'Cache Library initialized'); - - if (Cache::$loaded !== TRUE) - { - $this->config['requests'] = (int) $this->config['requests']; - - if ($this->config['requests'] > 0 AND mt_rand(1, $this->config['requests']) === 1) - { - // Do garbage collection - $this->driver->delete_expired(); + 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:' => 'Cache_Driver')); - Kohana::log('debug', 'Cache: Expired caches deleted.'); - } - - // Cache has been loaded once - Cache::$loaded = TRUE; - } + Kohana_Log::add('debug', 'Cache Library initialized'); } /** - * Fetches a cache by id. NULL is returned when a cache item is not found. - * - * @param string cache id - * @return mixed cached data or NULL + * Set cache items */ - public function get($id) + public function set($key, $value = NULL, $tags = NULL, $lifetime = NULL) { - // Sanitize the ID - $id = $this->sanitize_id($id); + if ($lifetime === NULL) + { + $lifetime = $this->config['lifetime']; + } - return $this->driver->get($id); - } + if ( ! is_array($key)) + { + $key = array($key => $value); + } - /** - * Fetches all of the caches for a given tag. An empty array will be - * returned when no matching caches are found. - * - * @param string cache tag - * @return array all cache items matching the tag - */ - public function find($tag) - { - return $this->driver->find($tag); + return $this->driver->set($key, $tags, $lifetime); } /** - * Set a cache item by id. Tags may also be added and a custom lifetime - * can be set. Non-string data is automatically serialized. - * - * @param string unique cache id - * @param mixed data to cache - * @param array|string tags for this item - * @param integer number of seconds until the cache expires - * @return boolean + * Get a cache items by key */ - function set($id, $data, $tags = NULL, $lifetime = NULL) + public function get($keys) { - if (is_resource($data)) - throw new Kohana_Exception('cache.resources'); - - // Sanitize the ID - $id = $this->sanitize_id($id); + $single = FALSE; - if ($lifetime === NULL) + if ( ! is_array($keys)) { - // Get the default lifetime - $lifetime = $this->config['lifetime']; + $keys = array($keys); + $single = TRUE; } - return $this->driver->set($id, $data, (array) $tags, $lifetime); + return $this->driver->get($keys, $single); } /** - * Delete a cache item by id. - * - * @param string cache id - * @return boolean + * Get cache items by tags */ - public function delete($id) + public function get_tag($tags) { - // Sanitize the ID - $id = $this->sanitize_id($id); + if ( ! is_array($tags)) + { + $tags = array($tags); + } - return $this->driver->delete($id); + return $this->driver->get_tag($tags); } /** - * Delete all cache items with a given tag. - * - * @param string cache tag name - * @return boolean + * Delete cache item by key */ - public function delete_tag($tag) + public function delete($keys) { - return $this->driver->delete($tag, TRUE); + if ( ! is_array($keys)) + { + $keys = array($keys); + } + + return $this->driver->delete($keys); } /** - * Delete ALL cache items items. - * - * @return boolean + * Delete cache items by tag */ - public function delete_all() + public function delete_tag($tags) { - return $this->driver->delete(TRUE); + if ( ! is_array($tags)) + { + $tags = array($tags); + } + + return $this->driver->delete_tag($tags); } /** - * Replaces troublesome characters with underscores. - * - * @param string cache id - * @return string + * Empty the cache */ - protected function sanitize_id($id) + public function delete_all() { - // Change slashes and spaces to underscores - return str_replace(array('/', '\\', ' '), '_', $id); + return $this->driver->delete_all(); } - -} // End Cache +} // 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 00000000..040d086d --- /dev/null +++ b/system/libraries/Cache_Exception.php @@ -0,0 +1,12 @@ + TRUE, - 'persistent' => FALSE, - 'connection' => '', - 'character_set' => 'utf8', - 'table_prefix' => '', - 'object' => TRUE, - 'cache' => FALSE, - 'escape' => TRUE, - ); - - // Database driver object - protected $driver; - protected $link; - - // Un-compiled parts of the SQL query - protected $select = array(); - protected $set = array(); - protected $from = array(); - protected $join = array(); - protected $where = array(); - protected $orderby = array(); - protected $order = array(); - protected $groupby = array(); - protected $having = array(); - protected $distinct = FALSE; - protected $limit = FALSE; - protected $offset = FALSE; - protected $last_query = ''; - - // Stack of queries for push/pop - protected $query_history = array(); + // 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 mixed configuration array or DSN + * @param string Database name * @return Database_Core */ - public static function & instance($name = 'default', $config = NULL) + public static function instance($name = 'default') { if ( ! isset(Database::$instances[$name])) { - // Create a new instance - Database::$instances[$name] = new Database($config === NULL ? $name : $config); + // 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]; } /** - * Returns the name of a given database instance. + * Constructs a new Database object * - * @param Database instance of Database - * @return string + * @param array Database config array + * @return Database_Core */ - public static function instance_name(Database $db) + 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() { - return array_search($db, Database::$instances, TRUE); + $this->disconnect(); } /** - * Sets up the database configuration, loads the Database_Driver. + * 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 * - * @throws Kohana_Database_Exception + * @param string DSN string + * @return array */ - public function __construct($config = array()) + public static function parse_dsn($dsn) { - if (empty($config)) + $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] === '/') { - // Load the default group - $config = Kohana::config('database.default'); + // Strip leading slash + $db['database'] = substr($connection, 1); } - elseif (is_array($config) AND count($config) > 0) + else { - if ( ! array_key_exists('connection', $config)) + $connection = parse_url('http://'.$connection); + + if (isset($connection['user'])) { - $config = array('connection' => $config); + $db['user'] = $connection['user']; } - } - elseif (is_string($config)) - { - // The config is a DSN string - if (strpos($config, '://') !== FALSE) + + if (isset($connection['pass'])) { - $config = array('connection' => $config); + $db['pass'] = $connection['pass']; } - // The config is a group name - else - { - $name = $config; - // Test the config group name - if (($config = Kohana::config('database.'.$config)) === NULL) - throw new Kohana_Database_Exception('database.undefined_group', $name); + if (isset($connection['port'])) + { + $db['port'] = $connection['port']; } - } - // Merge the default config with the passed config - $this->config = array_merge($this->config, $config); - - if (is_string($this->config['connection'])) - { - // Make sure the connection is valid - if (strpos($this->config['connection'], '://') === FALSE) - throw new Kohana_Database_Exception('database.invalid_dsn', $this->config['connection']); - - // Parse the DSN, creating an array to hold the connection parameters - $db = array - ( - 'type' => FALSE, - 'user' => FALSE, - 'pass' => FALSE, - 'host' => FALSE, - 'port' => FALSE, - 'socket' => FALSE, - 'database' => FALSE - ); - - // Get the protocol and arguments - list ($db['type'], $connection) = explode('://', $this->config['connection'], 2); - - if (strpos($connection, '@') !== FALSE) + if (isset($connection['host'])) { - // Get the username and password - list ($db['pass'], $connection) = explode('@', $connection, 2); - // Check if a password is supplied - $logindata = explode(':', $db['pass'], 2); - $db['pass'] = (count($logindata) > 1) ? $logindata[1] : ''; - $db['user'] = $logindata[0]; - - // Prepare for finding the database - $connection = explode('/', $connection); - - // Find the database name - $db['database'] = array_pop($connection); - - // Reset connection string - $connection = implode('/', $connection); - - // Find the socket - if (preg_match('/^unix\([^)]++\)/', $connection)) + if ($connection['host'] === 'unix(') { - // This one is a little hairy: we explode based on the end of - // the socket, removing the 'unix(' from the connection string - list ($db['socket'], $connection) = explode(')', substr($connection, 5), 2); - } - elseif (strpos($connection, ':') !== FALSE) - { - // Fetch the host and port name - list ($db['host'], $db['port']) = explode(':', $connection, 2); + list($db['socket'], $connection['path']) = explode(')', $connection['path'], 2); } else { - $db['host'] = $connection; + $db['host'] = $connection['host']; } } - else - { - // File connection - $connection = explode('/', $connection); - // Find database file name - $db['database'] = array_pop($connection); - - // Find database directory name - $db['socket'] = implode('/', $connection).'/'; + if (isset($connection['path']) AND $connection['path']) + { + // Strip leading slash + $db['database'] = substr($connection['path'], 1); } - - // Reset the connection array to the database config - $this->config['connection'] = $db; } - // Set driver name - $driver = 'Database_'.ucfirst($this->config['connection']['type']).'_Driver'; - - // Load the driver - if ( ! Kohana::auto_load($driver)) - throw new Kohana_Database_Exception('core.driver_not_found', $this->config['connection']['type'], get_class($this)); - // Initialize the driver - $this->driver = new $driver($this->config); - - // Validate the driver - if ( ! ($this->driver instanceof Database_Driver)) - throw new Kohana_Database_Exception('core.driver_implements', $this->config['connection']['type'], get_class($this), 'Database_Driver'); - - Kohana::log('debug', 'Database Library initialized'); + return $db; } /** - * Simple connect method to get the database queries up and running. + * Returns the last executed query for this database * - * @return void + * @return string */ - public function connect() + public function last_query() { - // A link can be a resource or an object - if ( ! is_resource($this->link) AND ! is_object($this->link)) - { - $this->link = $this->driver->connect(); - if ( ! is_resource($this->link) AND ! is_object($this->link)) - throw new Kohana_Database_Exception('database.connection', $this->driver->show_error()); - - // Clear password after successful connect - $this->config['connection']['pass'] = NULL; - } + return $this->last_query; } /** - * Runs a query into the driver and returns the result. + * Executes the given query, returning the cached version if enabled * - * @param string SQL query to execute - * @return Database_Result + * @param string SQL query + * @return Database_Result */ - public function query($sql = '') + public function query($sql) { - if ($sql == '') return FALSE; - - // No link? Connect! - $this->link or $this->connect(); - // Start the benchmark $start = microtime(TRUE); - if (func_num_args() > 1) //if we have more than one argument ($sql) + if (is_array($this->cache)) { - $argv = func_get_args(); - $binds = (is_array(next($argv))) ? current($argv) : array_slice($argv, 1); - } + $hash = $this->query_hash($sql); + + if (isset($this->cache[$hash])) + { + // Use cached result + $result = $this->cache[$hash]; - // Compile binds if needed - if (isset($binds)) + // It's from cache + $sql .= ' [CACHE]'; + } + else + { + // No cache, execute query and store in cache + $result = $this->cache[$hash] = $this->query_execute($sql); + } + } + else { - $sql = $this->compile_binds($sql, $binds); + // Execute the query, cache is off + $result = $this->query_execute($sql); } - // Fetch the result - $result = $this->driver->query($this->last_query = $sql); - // Stop the benchmark $stop = microtime(TRUE); - if ($this->config['benchmark'] == TRUE) + if ($this->config['benchmark'] === TRUE) { // Benchmark the query Database::$benchmarks[] = array('query' => $sql, 'time' => $stop - $start, 'rows' => count($result)); @@ -271,1187 +285,358 @@ class Database_Core { } /** - * Selects the column names for a database query. + * Performs the query on the cache (and caches it if it's not found) * - * @param string string or array of column names to select - * @return Database_Core This Database object. + * @param string query + * @param int time-to-live (NULL for Cache default) + * @return Database_Cache_Result */ - public function select($sql = '*') + public function query_cache($sql, $ttl) { - if (func_num_args() > 1) + if ( ! $this->cache instanceof Cache) { - $sql = func_get_args(); + throw new Database_Exception('Database :name has not been configured to use the Cache library.'); } - elseif (is_string($sql)) + + // Start the benchmark + $start = microtime(TRUE); + + $hash = $this->query_hash($sql); + + if (($data = $this->cache->get($hash)) !== NULL) { - $sql = explode(',', $sql); + // Found in cache, create result + $result = new Database_Cache_Result($data, $sql, $this->config['object']); + + // It's from the cache + $sql .= ' [CACHE]'; } else { - $sql = (array) $sql; - } - - foreach ($sql as $val) - { - if (($val = trim($val)) === '') continue; + // Run the query and return the full array of rows + $data = $this->query_execute($sql)->as_array(TRUE); - if (strpos($val, '(') === FALSE AND $val !== '*') - { - if (preg_match('/^DISTINCT\s++(.+)$/i', $val, $matches)) - { - // Only prepend with table prefix if table name is specified - $val = (strpos($matches[1], '.') !== FALSE) ? $this->config['table_prefix'].$matches[1] : $matches[1]; + // Set the Cache + $this->cache->set($hash, $data, NULL, $ttl); - $this->distinct = TRUE; - } - else - { - $val = (strpos($val, '.') !== FALSE) ? $this->config['table_prefix'].$val : $val; - } + // Create result + $result = new Database_Cache_Result($data, $sql, $this->config['object']); + } - $val = $this->driver->escape_column($val); - } + // Stop the benchmark + $stop = microtime(TRUE); - $this->select[] = $val; + if ($this->config['benchmark'] === TRUE) + { + // Benchmark the query + Database::$benchmarks[] = array('query' => $sql, 'time' => $stop - $start, 'rows' => count($result)); } - return $this; + return $result; } /** - * Selects the from table(s) for a database query. + * Generates a hash for the given query * - * @param string string or array of tables to select - * @return Database_Core This Database object. + * @param string SQL query string + * @return string */ - public function from($sql) + protected function query_hash($sql) { - if (func_num_args() > 1) - { - $sql = func_get_args(); - } - elseif (is_string($sql)) - { - $sql = explode(',', $sql); - } - else - { - $sql = array($sql); - } - - foreach ($sql as $val) - { - if (is_string($val)) - { - if (($val = trim($val)) === '') continue; - - // TODO: Temporary solution, this should be moved to database driver (AS is checked for twice) - if (stripos($val, ' AS ') !== FALSE) - { - $val = str_ireplace(' AS ', ' AS ', $val); - - list($table, $alias) = explode(' AS ', $val); - - // Attach prefix to both sides of the AS - $val = $this->config['table_prefix'].$table.' AS '.$this->config['table_prefix'].$alias; - } - else - { - $val = $this->config['table_prefix'].$val; - } - } - - $this->from[] = $val; - } - - return $this; + return sha1(str_replace("\n", ' ', trim($sql))); } /** - * Generates the JOIN portion of the query. + * Clears the internal query cache. * - * @param string table name - * @param string|array where key or array of key => value pairs - * @param string where value - * @param string type of join - * @return Database_Core This Database object. + * @param mixed clear cache by SQL statement, NULL for all, or TRUE for last query + * @return Database */ - public function join($table, $key, $value = NULL, $type = '') + public function clear_cache($sql = NULL) { - $join = array(); - - if ( ! empty($type)) + if ($this->cache instanceof Cache) { - $type = strtoupper(trim($type)); - - if ( ! in_array($type, array('LEFT', 'RIGHT', 'OUTER', 'INNER', 'LEFT OUTER', 'RIGHT OUTER'), TRUE)) + // Using cross-request Cache library + if ($sql === TRUE) + { + $this->cache->delete($this->query_hash($this->last_query)); + } + elseif (is_string($sql)) { - $type = ''; + $this->cache->delete($this->query_hash($sql)); } else { - $type .= ' '; + $this->cache->delete_all(); } } - - $cond = array(); - $keys = is_array($key) ? $key : array($key => $value); - foreach ($keys as $key => $value) + elseif (is_array($this->cache)) { - $key = (strpos($key, '.') !== FALSE) ? $this->config['table_prefix'].$key : $key; - - if (is_string($value)) + // Using per-request memory cache + if ($sql === TRUE) { - // Only escape if it's a string - $value = $this->driver->escape_column($this->config['table_prefix'].$value); + unset($this->cache[$this->query_hash($this->last_query)]); } - - $cond[] = $this->driver->where($key, $value, 'AND ', count($cond), FALSE); - } - - if ( ! is_array($this->join)) - { - $this->join = array(); - } - - if ( ! is_array($table)) - { - $table = array($table); - } - - foreach ($table as $t) - { - if (is_string($t)) + elseif (is_string($sql)) { - // TODO: Temporary solution, this should be moved to database driver (AS is checked for twice) - if (stripos($t, ' AS ') !== FALSE) - { - $t = str_ireplace(' AS ', ' AS ', $t); - - list($table, $alias) = explode(' AS ', $t); - - // Attach prefix to both sides of the AS - $t = $this->config['table_prefix'].$table.' AS '.$this->config['table_prefix'].$alias; - } - else - { - $t = $this->config['table_prefix'].$t; - } + unset($this->cache[$this->query_hash($sql)]); + } + else + { + $this->cache = array(); } - - $join['tables'][] = $this->driver->escape_column($t); } - - $join['conditions'] = '('.trim(implode(' ', $cond)).')'; - $join['type'] = $type; - - $this->join[] = $join; - - return $this; } - /** - * Selects the where(s) for a database query. + * Quotes the given value * - * @param string|array key name or array of key => value pairs - * @param string value to match with key - * @param boolean disable quoting of WHERE clause - * @return Database_Core This Database object. + * @param mixed value + * @return mixed */ - public function where($key, $value = NULL, $quote = TRUE) + public function quote($value) { - $quote = (func_num_args() < 2 AND ! is_array($key)) ? -1 : $quote; - if (is_object($key)) + if ( ! $this->config['escape']) + return $value; + + if ($value === NULL) { - $keys = array((string) $key => ''); + return 'NULL'; } - elseif ( ! is_array($key)) + elseif ($value === TRUE) { - $keys = array($key => $value); + return 'TRUE'; } - else + elseif ($value === FALSE) { - $keys = $key; + return 'FALSE'; } - - foreach ($keys as $key => $value) + elseif (is_int($value)) + { + return (int) $value; + } + elseif ($value instanceof Database_Expression) { - $key = (strpos($key, '.') !== FALSE) ? $this->config['table_prefix'].$key : $key; - $this->where[] = $this->driver->where($key, $value, 'AND ', count($this->where), $quote); + return (string) $value; } - return $this; + return '\''.$this->escape($value).'\''; } /** - * Selects the or where(s) for a database query. + * Quotes a table, adding the table prefix + * Reserved characters not allowed in table names for the builder are [ .*] (space, dot, asterisk) * - * @param string|array key name or array of key => value pairs - * @param string value to match with key - * @param boolean disable quoting of WHERE clause - * @return Database_Core This Database object. + * @param string|array table name or array - 'users u' or array('u' => 'users') both valid + * @param string table alias + * @return string */ - public function orwhere($key, $value = NULL, $quote = TRUE) + public function quote_table($table, $alias = NULL) { - $quote = (func_num_args() < 2 AND ! is_array($key)) ? -1 : $quote; - if (is_object($key)) + if (is_array($table)) { - $keys = array((string) $key => ''); + // Using array('u' => 'user') + list($alias, $table) = each($table); } - elseif ( ! is_array($key)) + elseif (strpos(' ', $table) !== FALSE) { - $keys = array($key => $value); + // Using format 'user u' + list($table, $alias) = explode(' ', $table); } - else + + if ($table instanceof Database_Expression) { - $keys = $key; + if ($alias) + { + if ($this->config['escape']) + { + $alias = $this->quote.$alias.$this->quote; + } + + return $table.' AS '.$alias; + } + + return (string) $table; } - foreach ($keys as $key => $value) + if ($this->config['table_prefix']) { - $key = (strpos($key, '.') !== FALSE) ? $this->config['table_prefix'].$key : $key; - $this->where[] = $this->driver->where($key, $value, 'OR ', count($this->where), $quote); + $table = $this->config['table_prefix'].$table; } - return $this; - } + if ($alias) + { + if ($this->config['escape']) + { + $table = $this->quote.$table.$this->quote; + $alias = $this->quote.$alias.$this->quote; + } - /** - * Selects the like(s) for a database query. - * - * @param string|array field name or array of field => match pairs - * @param string like value to match with field - * @param boolean automatically add starting and ending wildcards - * @return Database_Core This Database object. - */ - public function like($field, $match = '', $auto = TRUE) - { - $fields = is_array($field) ? $field : array($field => $match); + return $table.' AS '.$alias; + } - foreach ($fields as $field => $match) + if ($this->config['escape']) { - $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field; - $this->where[] = $this->driver->like($field, $match, $auto, 'AND ', count($this->where)); + $table = $this->quote.$table.$this->quote; } - return $this; + return $table; } /** - * Selects the or like(s) for a database query. + * 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 field name or array of field => match pairs - * @param string like value to match with field - * @param boolean automatically add starting and ending wildcards - * @return Database_Core This Database object. + * @param string|array column name or array('u' => 'COUNT("*")') + * @param string column alias + * @return string */ - public function orlike($field, $match = '', $auto = TRUE) + public function quote_column($column, $alias = NULL) { - $fields = is_array($field) ? $field : array($field => $match); + if ($column === '*') + return $column; - foreach ($fields as $field => $match) + if (is_array($column)) { - $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field; - $this->where[] = $this->driver->like($field, $match, $auto, 'OR ', count($this->where)); + list($alias, $column) = each($column); } - return $this; - } + if ($column instanceof Database_Expression) + { + if ($alias) + { + if ($this->config['escape']) + { + $alias = $this->quote.$alias.$this->quote; + } - /** - * Selects the not like(s) for a database query. - * - * @param string|array field name or array of field => match pairs - * @param string like value to match with field - * @param boolean automatically add starting and ending wildcards - * @return Database_Core This Database object. - */ - public function notlike($field, $match = '', $auto = TRUE) - { - $fields = is_array($field) ? $field : array($field => $match); + return $column.' AS '.$alias; + } + + return (string) $column; + } - foreach ($fields as $field => $match) + if ($this->config['table_prefix'] AND strpos($column, '.') !== FALSE) { - $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field; - $this->where[] = $this->driver->notlike($field, $match, $auto, 'AND ', count($this->where)); + 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; + } } - return $this; - } + 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); + } - /** - * Selects the or not like(s) for a database query. - * - * @param string|array field name or array of field => match pairs - * @param string like value to match with field - * @return Database_Core This Database object. - */ - public function ornotlike($field, $match = '', $auto = TRUE) - { - $fields = is_array($field) ? $field : array($field => $match); + // Replace . with "." + $column = str_replace('.', $this->quote.'.'.$this->quote, $column); - foreach ($fields as $field => $match) - { - $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field; - $this->where[] = $this->driver->notlike($field, $match, $auto, 'OR ', count($this->where)); + // 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; } - return $this; + // Strip double quotes + $column = str_replace('"', '', $column); + + if ($alias) + return $column.' AS '.$alias; + + return $column; } /** - * Selects the like(s) for a database query. + * Get the table prefix * - * @param string|array field name or array of field => match pairs - * @param string like value to match with field - * @return Database_Core This Database object. + * @param string Optional new table prefix to set + * @return string */ - public function regex($field, $match = '') + public function table_prefix($new_prefix = NULL) { - $fields = is_array($field) ? $field : array($field => $match); + $prefix = $this->config['table_prefix']; - foreach ($fields as $field => $match) + if ($new_prefix !== NULL) { - $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field; - $this->where[] = $this->driver->regex($field, $match, 'AND ', count($this->where)); + // Set a new prefix + $this->config['table_prefix'] = $new_prefix; } - return $this; + return $prefix; } /** - * Selects the or like(s) for a database query. + * Fetches SQL type information about a field, in a generic format. * - * @param string|array field name or array of field => match pairs - * @param string like value to match with field - * @return Database_Core This Database object. + * @param string field datatype + * @return array */ - public function orregex($field, $match = '') + protected function sql_type($str) { - $fields = is_array($field) ? $field : array($field => $match); + static $sql_types; - foreach ($fields as $field => $match) + if ($sql_types === NULL) { - $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field; - $this->where[] = $this->driver->regex($field, $match, 'OR ', count($this->where)); + // Load SQL data types + $sql_types = Kohana::config('sql_types'); } - return $this; - } + $str = trim($str); - /** - * Selects the not regex(s) for a database query. - * - * @param string|array field name or array of field => match pairs - * @param string regex value to match with field - * @return Database_Core This Database object. - */ - public function notregex($field, $match = '') - { - $fields = is_array($field) ? $field : array($field => $match); + if (($open = strpos($str, '(')) !== FALSE) + { + // Closing bracket + $close = strpos($str, ')', $open); + + // Length without brackets + $length = substr($str, $open + 1, $close - 1 - $open); - foreach ($fields as $field => $match) + // Type without the length + $type = substr($str, 0, $open).substr($str, $close + 1); + } + else { - $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field; - $this->where[] = $this->driver->notregex($field, $match, 'AND ', count($this->where)); + // No length + $type = $str; } - return $this; - } + if (empty($sql_types[$type])) + throw new Database_Exception('Undefined field type :type', array(':type' => $str)); - /** - * Selects the or not regex(s) for a database query. - * - * @param string|array field name or array of field => match pairs - * @param string regex value to match with field - * @return Database_Core This Database object. - */ - public function ornotregex($field, $match = '') - { - $fields = is_array($field) ? $field : array($field => $match); + // Fetch the field definition + $field = $sql_types[$type]; + + $field['sql_type'] = $type; - foreach ($fields as $field => $match) + if (isset($length)) { - $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field; - $this->where[] = $this->driver->notregex($field, $match, 'OR ', count($this->where)); + // Add the length to the field info + $field['length'] = $length; } - return $this; + return $field; } - /** - * Chooses the column to group by in a select query. - * - * @param string column name to group by - * @return Database_Core This Database object. - */ - public function groupby($by) - { - if ( ! is_array($by)) - { - $by = explode(',', (string) $by); - } - - foreach ($by as $val) - { - $val = trim($val); - - if ($val != '') - { - // Add the table prefix if we are using table.column names - if(strpos($val, '.')) - { - $val = $this->config['table_prefix'].$val; - } - - $this->groupby[] = $this->driver->escape_column($val); - } - } - - return $this; - } - - /** - * Selects the having(s) for a database query. - * - * @param string|array key name or array of key => value pairs - * @param string value to match with key - * @param boolean disable quoting of WHERE clause - * @return Database_Core This Database object. - */ - public function having($key, $value = '', $quote = TRUE) - { - $this->having[] = $this->driver->where($key, $value, 'AND', count($this->having), TRUE); - return $this; - } - - /** - * Selects the or having(s) for a database query. - * - * @param string|array key name or array of key => value pairs - * @param string value to match with key - * @param boolean disable quoting of WHERE clause - * @return Database_Core This Database object. - */ - public function orhaving($key, $value = '', $quote = TRUE) - { - $this->having[] = $this->driver->where($key, $value, 'OR', count($this->having), TRUE); - return $this; - } - - /** - * Chooses which column(s) to order the select query by. - * - * @param string|array column(s) to order on, can be an array, single column, or comma seperated list of columns - * @param string direction of the order - * @return Database_Core This Database object. - */ - public function orderby($orderby, $direction = NULL) - { - if ( ! is_array($orderby)) - { - $orderby = array($orderby => $direction); - } - - foreach ($orderby as $column => $direction) - { - $direction = strtoupper(trim($direction)); - - // Add a direction if the provided one isn't valid - if ( ! in_array($direction, array('ASC', 'DESC', 'RAND()', 'RANDOM()', 'NULL'))) - { - $direction = 'ASC'; - } - - // Add the table prefix if a table.column was passed - if (strpos($column, '.')) - { - $column = $this->config['table_prefix'].$column; - } - - $this->orderby[] = $this->driver->escape_column($column).' '.$direction; - } - - return $this; - } - - /** - * Selects the limit section of a query. - * - * @param integer number of rows to limit result to - * @param integer offset in result to start returning rows from - * @return Database_Core This Database object. - */ - public function limit($limit, $offset = NULL) - { - $this->limit = (int) $limit; - - if ($offset !== NULL OR ! is_int($this->offset)) - { - $this->offset($offset); - } - - return $this; - } - - /** - * Sets the offset portion of a query. - * - * @param integer offset value - * @return Database_Core This Database object. - */ - public function offset($value) - { - $this->offset = (int) $value; - - return $this; - } - - /** - * Allows key/value pairs to be set for inserting or updating. - * - * @param string|array key name or array of key => value pairs - * @param string value to match with key - * @return Database_Core This Database object. - */ - public function set($key, $value = '') - { - if ( ! is_array($key)) - { - $key = array($key => $value); - } - - foreach ($key as $k => $v) - { - // Add a table prefix if the column includes the table. - if (strpos($k, '.')) - $k = $this->config['table_prefix'].$k; - - $this->set[$k] = $this->driver->escape($v); - } - - return $this; - } - - /** - * Compiles the select statement based on the other functions called and runs the query. - * - * @param string table name - * @param string limit clause - * @param string offset clause - * @return Database_Result - */ - public function get($table = '', $limit = NULL, $offset = NULL) - { - if ($table != '') - { - $this->from($table); - } - - if ( ! is_null($limit)) - { - $this->limit($limit, $offset); - } - - $sql = $this->driver->compile_select(get_object_vars($this)); - - $this->reset_select(); - - $result = $this->query($sql); - - $this->last_query = $sql; - - return $result; - } - - /** - * Compiles the select statement based on the other functions called and runs the query. - * - * @param string table name - * @param array where clause - * @param string limit clause - * @param string offset clause - * @return Database_Core This Database object. - */ - public function getwhere($table = '', $where = NULL, $limit = NULL, $offset = NULL) - { - if ($table != '') - { - $this->from($table); - } - - if ( ! is_null($where)) - { - $this->where($where); - } - - if ( ! is_null($limit)) - { - $this->limit($limit, $offset); - } - - $sql = $this->driver->compile_select(get_object_vars($this)); - - $this->reset_select(); - - $result = $this->query($sql); - - return $result; - } - - /** - * Compiles the select statement based on the other functions called and returns the query string. - * - * @param string table name - * @param string limit clause - * @param string offset clause - * @return string sql string - */ - public function compile($table = '', $limit = NULL, $offset = NULL) - { - if ($table != '') - { - $this->from($table); - } - - if ( ! is_null($limit)) - { - $this->limit($limit, $offset); - } - - $sql = $this->driver->compile_select(get_object_vars($this)); - - $this->reset_select(); - - return $sql; - } - - /** - * Compiles an insert string and runs the query. - * - * @param string table name - * @param array array of key/value pairs to insert - * @return Database_Result Query result - */ - public function insert($table = '', $set = NULL) - { - if ( ! is_null($set)) - { - $this->set($set); - } - - if ($this->set == NULL) - throw new Kohana_Database_Exception('database.must_use_set'); - - if ($table == '') - { - if ( ! isset($this->from[0])) - throw new Kohana_Database_Exception('database.must_use_table'); - - $table = $this->from[0]; - } - - // If caching is enabled, clear the cache before inserting - ($this->config['cache'] === TRUE) and $this->clear_cache(); - - $sql = $this->driver->insert($this->config['table_prefix'].$table, array_keys($this->set), array_values($this->set)); - - $this->reset_write(); - - return $this->query($sql); - } - - /** - * Adds an "IN" condition to the where clause - * - * @param string Name of the column being examined - * @param mixed An array or string to match against - * @param bool Generate a NOT IN clause instead - * @return Database_Core This Database object. - */ - public function in($field, $values, $not = FALSE) - { - if (is_array($values)) - { - $escaped_values = array(); - foreach ($values as $v) - { - if (is_numeric($v)) - { - $escaped_values[] = $v; - } - else - { - $escaped_values[] = "'".$this->driver->escape_str($v)."'"; - } - } - $values = implode(",", $escaped_values); - } - - $where = $this->driver->escape_column(((strpos($field,'.') !== FALSE) ? $this->config['table_prefix'] : ''). $field).' '.($not === TRUE ? 'NOT ' : '').'IN ('.$values.')'; - $this->where[] = $this->driver->where($where, '', 'AND ', count($this->where), -1); - - return $this; - } - - /** - * Adds a "NOT IN" condition to the where clause - * - * @param string Name of the column being examined - * @param mixed An array or string to match against - * @return Database_Core This Database object. - */ - public function notin($field, $values) - { - return $this->in($field, $values, TRUE); - } - - /** - * Compiles a merge string and runs the query. - * - * @param string table name - * @param array array of key/value pairs to merge - * @return Database_Result Query result - */ - public function merge($table = '', $set = NULL) - { - if ( ! is_null($set)) - { - $this->set($set); - } - - if ($this->set == NULL) - throw new Kohana_Database_Exception('database.must_use_set'); - - if ($table == '') - { - if ( ! isset($this->from[0])) - throw new Kohana_Database_Exception('database.must_use_table'); - - $table = $this->from[0]; - } - else - { - $table = $this->config['table_prefix'].$table; - } - - $sql = $this->driver->merge($table, array_keys($this->set), array_values($this->set)); - - $this->reset_write(); - return $this->query($sql); - } - - /** - * Compiles an update string and runs the query. - * - * @param string table name - * @param array associative array of update values - * @param array where clause - * @return Database_Result Query result - */ - public function update($table = '', $set = NULL, $where = NULL) - { - if ( is_array($set)) - { - $this->set($set); - } - - if ( ! is_null($where)) - { - $this->where($where); - } - - if ($this->set == FALSE) - throw new Kohana_Database_Exception('database.must_use_set'); - - if ($table == '') - { - if ( ! isset($this->from[0])) - throw new Kohana_Database_Exception('database.must_use_table'); - - $table = $this->from[0]; - } - else - { - $table = $this->config['table_prefix'].$table; - } - - $sql = $this->driver->update($table, $this->set, $this->where); - - $this->reset_write(); - return $this->query($sql); - } - - /** - * Compiles a delete string and runs the query. - * - * @param string table name - * @param array where clause - * @return Database_Result Query result - */ - public function delete($table = '', $where = NULL) - { - if ($table == '') - { - if ( ! isset($this->from[0])) - throw new Kohana_Database_Exception('database.must_use_table'); - - $table = $this->from[0]; - } - else - { - $table = $this->config['table_prefix'].$table; - } - - if (! is_null($where)) - { - $this->where($where); - } - - if (count($this->where) < 1) - throw new Kohana_Database_Exception('database.must_use_where'); - - $sql = $this->driver->delete($table, $this->where); - - $this->reset_write(); - return $this->query($sql); - } - - /** - * Returns the last query run. - * - * @return string SQL - */ - public function last_query() - { - return $this->last_query; - } - - /** - * Count query records. - * - * @param string table name - * @param array where clause - * @return integer - */ - public function count_records($table = FALSE, $where = NULL) - { - if (count($this->from) < 1) - { - if ($table == FALSE) - throw new Kohana_Database_Exception('database.must_use_table'); - - $this->from($table); - } - - if ($where !== NULL) - { - $this->where($where); - } - - $query = $this->select('COUNT(*) AS '.$this->escape_column('records_found'))->get()->result(TRUE); - - $query = $query->current(); - - if ( ! $query) - return 0; - else - return (int) $query->records_found; - } - - /** - * Resets all private select variables. - * - * @return void - */ - protected function reset_select() - { - $this->select = array(); - $this->from = array(); - $this->join = array(); - $this->where = array(); - $this->orderby = array(); - $this->groupby = array(); - $this->having = array(); - $this->distinct = FALSE; - $this->limit = FALSE; - $this->offset = FALSE; - } - - /** - * Resets all private insert and update variables. - * - * @return void - */ - protected function reset_write() - { - $this->set = array(); - $this->from = array(); - $this->where = array(); - } - - /** - * Lists all the tables in the current database. - * - * @return array - */ - public function list_tables() - { - $this->link or $this->connect(); - - return $this->driver->list_tables(); - } - - /** - * See if a table exists in the database. - * - * @param string table name - * @param boolean True to attach table prefix - * @return boolean - */ - public function table_exists($table_name, $prefix = TRUE) - { - if ($prefix) - return in_array($this->config['table_prefix'].$table_name, $this->list_tables()); - else - return in_array($table_name, $this->list_tables()); - } - - /** - * Combine a SQL statement with the bind values. Used for safe queries. - * - * @param string query to bind to the values - * @param array array of values to bind to the query - * @return string - */ - public function compile_binds($sql, $binds) - { - foreach ((array) $binds as $val) - { - // If the SQL contains no more bind marks ("?"), we're done. - if (($next_bind_pos = strpos($sql, '?')) === FALSE) - break; - - // Properly escape the bind value. - $val = $this->driver->escape($val); - - // Temporarily replace possible bind marks ("?"), in the bind value itself, with a placeholder. - $val = str_replace('?', '{%B%}', $val); - - // Replace the first bind mark ("?") with its corresponding value. - $sql = substr($sql, 0, $next_bind_pos).$val.substr($sql, $next_bind_pos + 1); - } - - // Restore placeholders. - return str_replace('{%B%}', '?', $sql); - } - - /** - * Get the field data for a database table, along with the field's attributes. - * - * @param string table name - * @return array - */ - public function field_data($table = '') - { - $this->link or $this->connect(); - - return $this->driver->field_data($this->config['table_prefix'].$table); - } - - /** - * Get the field data for a database table, along with the field's attributes. - * - * @param string table name - * @return array - */ - public function list_fields($table = '') - { - $this->link or $this->connect(); - - return $this->driver->list_fields($this->config['table_prefix'].$table); - } - - /** - * Escapes a value for a query. - * - * @param mixed value to escape - * @return string - */ - public function escape($value) - { - return $this->driver->escape($value); - } - - /** - * Escapes a string for a query. - * - * @param string string to escape - * @return string - */ - public function escape_str($str) - { - return $this->driver->escape_str($str); - } - - /** - * Escapes a table name for a query. - * - * @param string string to escape - * @return string - */ - public function escape_table($table) - { - return $this->driver->escape_table($table); - } - - /** - * Escapes a column name for a query. - * - * @param string string to escape - * @return string - */ - public function escape_column($table) - { - return $this->driver->escape_column($table); - } - - /** - * Returns table prefix of current configuration. - * - * @return string - */ - public function table_prefix() - { - return $this->config['table_prefix']; - } - - /** - * Clears the query cache. - * - * @param string|TRUE clear cache by SQL statement or TRUE for last query - * @return Database_Core This Database object. - */ - public function clear_cache($sql = NULL) - { - if ($sql === TRUE) - { - $this->driver->clear_cache($this->last_query); - } - elseif (is_string($sql)) - { - $this->driver->clear_cache($sql); - } - else - { - $this->driver->clear_cache(); - } - - return $this; - } - - /** - * Pushes existing query space onto the query stack. Use push - * and pop to prevent queries from clashing before they are - * executed - * - * @return Database_Core This Databaes object - */ - public function push() - { - array_push($this->query_history, array( - $this->select, - $this->from, - $this->join, - $this->where, - $this->orderby, - $this->order, - $this->groupby, - $this->having, - $this->distinct, - $this->limit, - $this->offset - )); - - $this->reset_select(); - - return $this; - } - - /** - * Pops from query stack into the current query space. - * - * @return Database_Core This Databaes object - */ - public function pop() - { - if (count($this->query_history) == 0) - { - // No history - return $this; - } - - list( - $this->select, - $this->from, - $this->join, - $this->where, - $this->orderby, - $this->order, - $this->groupby, - $this->having, - $this->distinct, - $this->limit, - $this->offset - ) = array_pop($this->query_history); - - return $this; - } - - /** - * Count the number of records in the last query, without LIMIT or OFFSET applied. - * - * @return integer - */ - public function count_last_query() - { - if ($sql = $this->last_query()) - { - if (stripos($sql, 'LIMIT') !== FALSE) - { - // Remove LIMIT from the SQL - $sql = preg_replace('/\sLIMIT\s+[^a-z]+/i', ' ', $sql); - } - - if (stripos($sql, 'OFFSET') !== FALSE) - { - // Remove OFFSET from the SQL - $sql = preg_replace('/\sOFFSET\s+\d+/i', '', $sql); - } - - // Get the total rows from the last query executed - $result = $this->query - ( - 'SELECT COUNT(*) AS '.$this->escape_column('total_rows').' '. - 'FROM ('.trim($sql).') AS '.$this->escape_table('counted_results') - ); - - // Return the total number of rows from the query - return (int) $result->current()->total_rows; - } - - return FALSE; - } - -} // End Database Class - - -/** - * Sets the code for a Database exception. - */ -class Kohana_Database_Exception extends Kohana_Exception { - - protected $code = E_DATABASE_ERROR; - -} // End Kohana Database Exception +} // End Database diff --git a/system/libraries/Database_Builder.php b/system/libraries/Database_Builder.php new file mode 100644 index 00000000..5a5a058c --- /dev/null +++ b/system/libraries/Database_Builder.php @@ -0,0 +1,938 @@ +db = $db; + } + + /** + * Resets all query components + */ + public 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(); + } + + public function __toString() + { + return $this->compile(); + } + + /** + * 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 = 'SELECT '.$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->having)) + { + $sql .= "\n".'HAVING '.$this->compile_conditions($this->having); + } + + if ( ! empty($this->group_by)) + { + $sql .= "\n".'GROUP BY '.$this->compile_group_by(); + } + + 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 (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 + */ + public 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 + */ + public 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); + } + + /** + * Join tables to the builder + * + * @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; + } + + /** + * Add tables to the FROM portion of the builder + * + * @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 fields to the GROUP BY portion + * + * @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) + * + * @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) + * + * @param mixed Column name or array of columns => vals + * @param string Operation to perform + * @param mixed Value + * @return Database_Builder + */ + public function and_having($columns, $op = '=', $value = NULL) + { + $this->having[] = array('AND' => array($columns, $op, $value)); + return $this; + } + + /** + * Add conditions to the HAVING clause (OR) + * + * @param mixed Column name or array of columns => vals + * @param string Operation to perform + * @param mixed Value + * @return Database_Builder + */ + public function or_having($columns, $op = '=', $value = NULL) + { + $this->having[] = array('OR' => array($columns, $op, $value)); + return $this; + } + + /** + * Add fields to the ORDER BY portion + * + * @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_string($columns)) + { + $columns = array($columns => $direction); + } + + $this->order_by[] = $columns; + + return $this; + } + + /** + * Limit rows returned + * + * @param int Number of rows + * @return Database_Builder + */ + public function limit($number) + { + $this->limit = (int) $number; + + return $this; + } + + /** + * Offset into result set + * + * @param int Offset + * @return Database_Builder + */ + public function offset($number) + { + $this->offset = (int) $number; + + return $this; + } + + public function left_join($table, $keys, $value = NULL) + { + return $this->join($table, $keys, $value, 'LEFT'); + } + + public function right_join($table, $keys, $value = NULL) + { + return $this->join($table, $keys, $value, 'RIGHT'); + } + + public function inner_join($table, $keys, $value = NULL) + { + return $this->join($table, $keys, $value, 'INNER'); + } + + public function outer_join($table, $keys, $value = NULL) + { + return $this->join($table, $keys, $value, 'OUTER'); + } + + public function full_join($table, $keys, $value = NULL) + { + return $this->join($table, $keys, $value, 'FULL'); + } + + public function left_inner_join($table, $keys, $value = NULL) + { + return $this->join($table, $keys, $value, 'LEFT INNER'); + } + + public function right_inner_join($table, $keys, $value = NULL) + { + return $this->join($table, $keys, $value, 'RIGHT INNER'); + } + + public function open($clause = 'WHERE') + { + return $this->and_open($clause); + } + + public function and_open($clause = 'WHERE') + { + if ($clause === 'WHERE') + { + $this->where[] = array('AND' => '('); + } + else + { + $this->having[] = array('AND' => '('); + } + + return $this; + } + + public function or_open($clause = 'WHERE') + { + if ($clause === 'WHERE') + { + $this->where[] = array('OR' => '('); + } + else + { + $this->having[] = array('OR' => '('); + } + + return $this; + } + + public function close($clause = 'WHERE') + { + if ($clause === 'WHERE') + { + $this->where[] = array(')'); + } + else + { + $this->having[] = array(')'); + } + + return $this; + } + + /** + * Add conditions to the WHERE clause (AND) + * + * @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 (AND) + * + * @param mixed Column name or array of columns => vals + * @param string Operation to perform + * @param mixed Value + * @return Database_Builder + */ + public function and_where($columns, $op = '=', $value = NULL) + { + $this->where[] = array('AND' => array($columns, $op, $value)); + return $this; + } + + /** + * Add conditions to the WHERE clause (OR) + * + * @param mixed Column name or array of columns => vals + * @param string Operation to perform + * @param mixed Value + * @return Database_Builder + */ + public function or_where($columns, $op = '=', $value = NULL) + { + $this->where[] = array('OR' => array($columns, $op, $value)); + 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; + } + + /** + * Set values for UPDATE + * + * @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 + * + * @param array Columns + * @return Database_Builder + */ + public function columns($columns) + { + if ( ! is_array($columns)) + { + $columns = func_get_args(); + } + + $this->columns = $columns; + + return $this; + } + + /** + * 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)).')'; + } + + /** + * Values used for INSERT queries + * + * @param array Values + * @return Database_Builder + */ + public function values($values) + { + if ( ! is_array($values)) + { + $values = func_get_args(); + } + + $this->values[] = $values; + + return $this; + } + + /** + * 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 a SELECT query and specify selected columns + * + * @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; + } + + /** + * Create an UPDATE query + * + * @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 + * + * @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 + * + * @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(); + + // 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); + } + } + + /** + * Set caching for the query + * + * @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; + } + +} // End Database_Builder diff --git a/system/libraries/Database_Cache_Result.php b/system/libraries/Database_Cache_Result.php new file mode 100644 index 00000000..12ad48ea --- /dev/null +++ b/system/libraries/Database_Cache_Result.php @@ -0,0 +1,83 @@ +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 00000000..4a9cd83e --- /dev/null +++ b/system/libraries/Database_Exception.php @@ -0,0 +1,17 @@ +expression; + return $this->expression; } - -} // End Database Expr Class \ No newline at end of file +} diff --git a/system/libraries/Database_Mysql.php b/system/libraries/Database_Mysql.php new file mode 100644 index 00000000..cb531194 --- /dev/null +++ b/system/libraries/Database_Mysql.php @@ -0,0 +1,229 @@ +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']); + + // Set the connection type + $connect = ($this->config['persistent'] === TRUE) ? 'mysql_pconnect' : 'mysql_connect'; + + $host = isset($host) ? $host : $socket; + $port = isset($port) ? ':'.$port : ''; + + try + { + // Connect to the database + $this->connection = $connect($host.$port, $user, $pass, TRUE); + } + 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 FROM '.$this->escape($this->config['connection']['database']).' 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 00000000..020360d1 --- /dev/null +++ b/system/libraries/Database_Mysql_Result.php @@ -0,0 +1,176 @@ +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 00000000..523fcb19 --- /dev/null +++ b/system/libraries/Database_Mysqli.php @@ -0,0 +1,92 @@ +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; + + if($this->connection = new mysqli($host, $user, $pass, $database, $port)) { + + if (isset($this->config['character_set'])) + { + // Set the character set + $this->set_charset($this->config['character_set']); + } + + // Clear password after successful connect + $this->db_config['connection']['pass'] = NULL; + + return $this->connection; + } + + // Unable to connect to the database + throw new Database_Exception('#:errno: :error', + array(':error' => $this->connection->connect_error, + ':errno' => $this->connection->connect_errno)); + } + + public function disconnect() + { + return is_object($this->connection) and $this->connection->close(); + } + + 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 00000000..3601aeac --- /dev/null +++ b/system/libraries/Database_Mysqli_Result.php @@ -0,0 +1,177 @@ +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 00000000..d9399d66 --- /dev/null +++ b/system/libraries/Database_Query.php @@ -0,0 +1,95 @@ +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 00000000..cf2056f3 --- /dev/null +++ b/system/libraries/Database_Result.php @@ -0,0 +1,170 @@ +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 index 3d564f99..0fbcfc1b 100644 --- a/system/libraries/Encrypt.php +++ b/system/libraries/Encrypt.php @@ -4,12 +4,12 @@ * using the MCrypt extension. * @see http://php.net/mcrypt * - * $Id: Encrypt.php 4072 2009-03-13 17:20:38Z jheathco $ + * $Id: Encrypt.php 4683 2009-11-14 17:10:53Z isaiah $ * * @package Core * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html + * @copyright (c) 2007-2009 Kohana Team + * @license http://kohanaphp.com/license */ class Encrypt_Core { @@ -44,7 +44,7 @@ class Encrypt_Core { public function __construct($config = FALSE) { if ( ! defined('MCRYPT_ENCRYPT')) - throw new Kohana_Exception('encrypt.requires_mcrypt'); + throw new Kohana_Exception('To use the Encrypt library, mcrypt must be enabled in your PHP installation'); if (is_string($config)) { @@ -52,7 +52,7 @@ class Encrypt_Core { // Test the config group name if (($config = Kohana::config('encryption.'.$config)) === NULL) - throw new Kohana_Exception('encrypt.undefined_group', $name); + throw new Kohana_Exception('The :name: group is not defined in your configuration.', array(':name:' => $name)); } if (is_array($config)) @@ -67,7 +67,7 @@ class Encrypt_Core { } if (empty($config['key'])) - throw new Kohana_Exception('encrypt.no_encryption_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']); @@ -84,7 +84,7 @@ class Encrypt_Core { // Cache the config in the object $this->config = $config; - Kohana::log('debug', 'Encrypt Library initialized'); + Kohana_Log::add('debug', 'Encrypt Library initialized'); } /** @@ -144,16 +144,28 @@ class Encrypt_Core { * Decrypts an encoded string back to its original value. * * @param string encoded string to be decrypted - * @return string decrypted data + * @return string decrypted data or FALSE if decryption fails */ public function decode($data) { // Convert the data back to binary - $data = base64_decode($data); + $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']); diff --git a/system/libraries/Event_Observer.php b/system/libraries/Event_Observer.php deleted file mode 100644 index 086c8a23..00000000 --- a/system/libraries/Event_Observer.php +++ /dev/null @@ -1,70 +0,0 @@ -update($caller); - } - - /** - * Updates the observer subject with a new caller. - * - * @chainable - * @param object Event_Subject - * @return object - */ - public function update(SplSubject $caller) - { - if ( ! ($caller instanceof Event_Subject)) - throw new Kohana_Exception('event.invalid_subject', get_class($caller), get_class($this)); - - // Update the caller - $this->caller = $caller; - - return $this; - } - - /** - * Detaches this observer from the subject. - * - * @chainable - * @return object - */ - public function remove() - { - // Detach this observer from the caller - $this->caller->detach($this); - - return $this; - } - - /** - * Notify the observer of a new message. This function must be defined in - * all observers and must take exactly one parameter of any type. - * - * @param mixed message string, object, or array - * @return void - */ - abstract public function notify($message); - -} // End Event Observer \ No newline at end of file diff --git a/system/libraries/Event_Subject.php b/system/libraries/Event_Subject.php deleted file mode 100644 index d1ccc544..00000000 --- a/system/libraries/Event_Subject.php +++ /dev/null @@ -1,67 +0,0 @@ -listeners[spl_object_hash($obj)] = $obj; - - return $this; - } - - /** - * Detach an observer from the object. - * - * @chainable - * @param object Event_Observer - * @return object - */ - public function detach(SplObserver $obj) - { - // Remove the listener - unset($this->listeners[spl_object_hash($obj)]); - - return $this; - } - - /** - * Notify all attached observers of a new message. - * - * @chainable - * @param mixed message string, object, or array - * @return object - */ - public function notify($message) - { - foreach ($this->listeners as $obj) - { - $obj->notify($message); - } - - return $this; - } - -} // End Event Subject \ No newline at end of file diff --git a/system/libraries/I18n.php b/system/libraries/I18n.php new file mode 100644 index 00000000..96752e51 --- /dev/null +++ b/system/libraries/I18n.php @@ -0,0 +1,103 @@ + $image)); // Disable error reporting, to prevent PHP warnings $ER = error_reporting(0); @@ -85,11 +91,11 @@ class Image_Core { // Make sure that the image is readable and valid if ( ! is_array($image_info) OR count($image_info) < 3) - throw new Kohana_Exception('image.file_unreadable', $image); + 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('image.type_not_allowed', $image); + 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 @@ -102,6 +108,8 @@ class Image_Core { 'mime' => $image_info['mime'] ); + $this->determine_orientation(); + // Load configuration $this->config = (array) $config + Kohana::config('image'); @@ -110,14 +118,40 @@ class Image_Core { // Load the driver if ( ! Kohana::auto_load($driver)) - throw new Kohana_Exception('core.driver_not_found', $this->config['driver'], get_class($this)); + 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('core.driver_implements', $this->config['driver'], get_class($this), '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; } /** @@ -134,7 +168,8 @@ class Image_Core { } else { - throw new Kohana_Exception('core.invalid_property', $property, get_class($this)); + throw new Kohana_Exception('The :property: property does not exist in the :class: class.', + array(':property:' => $property, ':class:' => get_class($this))); } } @@ -153,13 +188,13 @@ class Image_Core { public function resize($width, $height, $master = NULL) { if ( ! $this->valid_size('width', $width)) - throw new Kohana_Exception('image.invalid_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('image.invalid_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('image.invalid_dimensions', __FUNCTION__); + throw new Kohana_Exception('The dimensions specified for :function: are not valid.', array(':function:' => __FUNCTION__)); if ($master === NULL) { @@ -167,7 +202,7 @@ class Image_Core { $master = Image::AUTO; } elseif ( ! $this->valid_size('master', $master)) - throw new Kohana_Exception('image.invalid_master'); + throw new Kohana_Exception('The master dimension specified is not valid.'); $this->actions['resize'] = array ( @@ -176,6 +211,8 @@ class Image_Core { 'master' => $master, ); + $this->determine_orientation(); + return $this; } @@ -194,19 +231,19 @@ class Image_Core { public function crop($width, $height, $top = 'center', $left = 'center') { if ( ! $this->valid_size('width', $width)) - throw new Kohana_Exception('image.invalid_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('image.invalid_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('image.invalid_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('image.invalid_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('image.invalid_dimensions', __FUNCTION__); + throw new Kohana_Exception('The dimensions specified for :function: are not valid.', array(':function:' => __FUNCTION__)); $this->actions['crop'] = array ( @@ -216,6 +253,8 @@ class Image_Core { 'left' => $left, ); + $this->determine_orientation(); + return $this; } @@ -269,7 +308,7 @@ class Image_Core { // Check to make sure the image type is allowed if ( ! isset(Image::$allowed_types[$image_info[2]])) - throw new Kohana_Exception('image.type_not_allowed', $overlay_file); + throw new Kohana_Exception('The specified image, :type:, is not an allowed image type.', array(':type:' => $overlay_file)); $this->actions['composite'] = array ( @@ -293,7 +332,7 @@ class Image_Core { public function flip($direction) { if ($direction !== Image::HORIZONTAL AND $direction !== Image::VERTICAL) - throw new Kohana_Exception('image.invalid_flip'); + throw new Kohana_Exception('The flip direction specified is not valid.'); $this->actions['flip'] = $direction; @@ -335,7 +374,7 @@ class Image_Core { * @param boolean keep or discard image process actions * @return object */ - public function save($new_image = FALSE, $chmod = 0644, $keep_actions = FALSE) + 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']; @@ -348,9 +387,9 @@ class Image_Core { $dir = str_replace('\\', '/', realpath($dir)).'/'; if ( ! is_writable($dir)) - throw new Kohana_Exception('image.directory_unwritable', $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)) + if ($status = $this->driver->process($this->image, $this->actions, $dir, $file, FALSE, $background)) { if ($chmod !== FALSE) { @@ -359,9 +398,11 @@ class Image_Core { } } - // Reset actions. Subsequent save() or render() will not apply previous actions. - if ($keep_actions === FALSE) + if ($keep_actions !== TRUE) + { + // Reset actions. Subsequent save() or render() will not apply previous actions. $this->actions = array(); + } return $status; } @@ -372,7 +413,7 @@ class Image_Core { * @param boolean keep or discard image process actions * @return object */ - public function render($keep_actions = FALSE) + public function render($keep_actions = FALSE, $background = NULL) { $new_image = $this->image['file']; @@ -384,11 +425,13 @@ class Image_Core { $dir = str_replace('\\', '/', realpath($dir)).'/'; // Process the image with the driver - $status = $this->driver->process($this->image, $this->actions, $dir, $file, $render = TRUE); + $status = $this->driver->process($this->image, $this->actions, $dir, $file, TRUE, $background); - // Reset actions. Subsequent save() or render() will not apply previous actions. - if ($keep_actions === FALSE) + if ($keep_actions !== TRUE) + { + // Reset actions. Subsequent save() or render() will not apply previous actions. $this->actions = array(); + } return $status; } diff --git a/system/libraries/Input.php b/system/libraries/Input.php index 0e23c800..83f0ed17 100644 --- a/system/libraries/Input.php +++ b/system/libraries/Input.php @@ -2,12 +2,12 @@ /** * Input library. * - * $Id: Input.php 4346 2009-05-11 17:08:15Z zombor $ + * $Id: Input.php 4680 2009-11-10 01:57:00Z isaiah $ * * @package Core * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html + * @copyright (c) 2007-2009 Kohana Team + * @license http://kohanaphp.com/license */ class Input_Core { @@ -48,6 +48,18 @@ class Input_Core { */ 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 (PHP_SAPI == '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'); @@ -56,15 +68,15 @@ class Input_Core { // magic_quotes_runtime is enabled if (get_magic_quotes_runtime()) { - set_magic_quotes_runtime(0); - Kohana::log('debug', 'Disable magic_quotes_runtime! It is evil and deprecated: http://php.net/magic_quotes'); + @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('debug', 'Disable magic_quotes_gpc! It is evil and deprecated: http://php.net/magic_quotes'); + Kohana_Log::add('debug', 'Disable magic_quotes_gpc! It is evil and deprecated: http://php.net/magic_quotes'); } // register_globals is enabled @@ -93,7 +105,7 @@ class Input_Core { } // Warn the developer about register globals - Kohana::log('debug', 'Disable register_globals! It is evil and deprecated: http://php.net/register_globals'); + Kohana_Log::add('debug', 'Disable register_globals! It is evil and deprecated: http://php.net/register_globals'); } if (is_array($_GET)) @@ -142,7 +154,7 @@ class Input_Core { // Create a singleton Input::$instance = $this; - Kohana::log('debug', 'Global GET, POST and COOKIE data sanitized'); + Kohana_Log::add('debug', 'Global GET, POST and COOKIE data sanitized'); } } @@ -173,7 +185,8 @@ class Input_Core { } /** - * Fetch an item from the $_COOKIE array. + * Fetch an item from the cookie::get() ($_COOKIE won't work with signed + * cookies.) * * @param string key to find * @param mixed default value @@ -182,7 +195,7 @@ class Input_Core { */ public function cookie($key = array(), $default = NULL, $xss_clean = FALSE) { - return $this->search_array($_COOKIE, $key, $default, $xss_clean); + return $this->search_array(cookie::get(), $key, $default, $xss_clean); } /** @@ -300,93 +313,123 @@ class Input_Core { if ($tool === TRUE) { - // NOTE: This is necessary because switch is NOT type-sensative! $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; - switch ($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 | + // +----------------------------------------------------------------------+ + // + // Kohana Modifications: + // * Changed double quotes to single quotes, changed indenting and spacing + // * Removed magic_quotes stuff + // * Increased regex readability: + // * Used delimeters that aren't found in the pattern + // * Removed all unneeded escapes + // * Deleted U modifiers and swapped greediness where needed + // * Increased regex speed: + // * Made capturing parentheses non-capturing where possible + // * Removed parentheses where possible + // * Split up alternation alternatives + // * Made some quantifiers possessive + + // Fix &entity\n; + $data = str_replace(array('&','<','>'), array('&amp;','&lt;','&gt;'), $data); + $data = preg_replace('/(&#*\w+)[\x00-\x20]+;/u', '$1;', $data); + $data = preg_replace('/(&#x*[0-9A-F]+);*/iu', '$1;', $data); + $data = html_entity_decode($data, ENT_COMPAT, 'UTF-8'); + + // Remove any attribute starting with "on" or xmlns + $data = preg_replace('#(<[^>]+?[\x00-\x20"\'])(?:on|xmlns)[^>]*+>#iu', '$1>', $data); + + // Remove javascript: and vbscript: protocols + $data = preg_replace('#([a-z]*)[\x00-\x20]*=[\x00-\x20]*([`\'"]*)[\x00-\x20]*j[\x00-\x20]*a[\x00-\x20]*v[\x00-\x20]*a[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2nojavascript...', $data); + $data = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*v[\x00-\x20]*b[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2novbscript...', $data); + $data = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*-moz-binding[\x00-\x20]*:#u', '$1=$2nomozbinding...', $data); + + // Only works in IE: + $data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?expression[\x00-\x20]*\([^>]*+>#i', '$1>', $data); + $data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?behaviour[\x00-\x20]*\([^>]*+>#i', '$1>', $data); + $data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:*[^>]*+>#iu', '$1>', $data); + + // Remove namespaced elements (we do not need them) + $data = preg_replace('#]*+>#i', '', $data); + + do { - case 'htmlpurifier': - /** - * @todo License should go here, http://htmlpurifier.org/ - */ - if ( ! class_exists('HTMLPurifier_Config', FALSE)) - { - // Load HTMLPurifier - require Kohana::find_file('vendor', 'htmlpurifier/HTMLPurifier.auto', TRUE); - require 'HTMLPurifier.func.php'; - } + // Remove really unwanted tags + $old_data = $data; + $data = preg_replace('#]*+>#i', '', $data); + } + while ($old_data !== $data); - // Set configuration - $config = HTMLPurifier_Config::createDefault(); - $config->set('HTML', 'TidyLevel', 'none'); // Only XSS cleaning now - - // Run HTMLPurifier - $data = HTMLPurifier($data, $config); - break; - default: - // http://svn.bitflux.ch/repos/public/popoon/trunk/classes/externalinput.php - // +----------------------------------------------------------------------+ - // | Copyright (c) 2001-2006 Bitflux GmbH | - // +----------------------------------------------------------------------+ - // | Licensed under the Apache License, Version 2.0 (the "License"); | - // | you may not use this file except in compliance with the License. | - // | You may obtain a copy of the License at | - // | http://www.apache.org/licenses/LICENSE-2.0 | - // | Unless required by applicable law or agreed to in writing, software | - // | distributed under the License is distributed on an "AS IS" BASIS, | - // | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | - // | implied. See the License for the specific language governing | - // | permissions and limitations under the License. | - // +----------------------------------------------------------------------+ - // | Author: Christian Stocker | - // +----------------------------------------------------------------------+ - // - // Kohana Modifications: - // * Changed double quotes to single quotes, changed indenting and spacing - // * Removed magic_quotes stuff - // * Increased regex readability: - // * Used delimeters that aren't found in the pattern - // * Removed all unneeded escapes - // * Deleted U modifiers and swapped greediness where needed - // * Increased regex speed: - // * Made capturing parentheses non-capturing where possible - // * Removed parentheses where possible - // * Split up alternation alternatives - // * Made some quantifiers possessive - - // Fix &entity\n; - $data = str_replace(array('&','<','>'), array('&amp;','&lt;','&gt;'), $data); - $data = preg_replace('/(&#*\w+)[\x00-\x20]+;/u', '$1;', $data); - $data = preg_replace('/(&#x*[0-9A-F]+);*/iu', '$1;', $data); - $data = html_entity_decode($data, ENT_COMPAT, 'UTF-8'); - - // Remove any attribute starting with "on" or xmlns - $data = preg_replace('#(<[^>]+?[\x00-\x20"\'])(?:on|xmlns)[^>]*+>#iu', '$1>', $data); - - // Remove javascript: and vbscript: protocols - $data = preg_replace('#([a-z]*)[\x00-\x20]*=[\x00-\x20]*([`\'"]*)[\x00-\x20]*j[\x00-\x20]*a[\x00-\x20]*v[\x00-\x20]*a[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2nojavascript...', $data); - $data = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*v[\x00-\x20]*b[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2novbscript...', $data); - $data = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*-moz-binding[\x00-\x20]*:#u', '$1=$2nomozbinding...', $data); - - // Only works in IE: - $data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?expression[\x00-\x20]*\([^>]*+>#i', '$1>', $data); - $data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?behaviour[\x00-\x20]*\([^>]*+>#i', '$1>', $data); - $data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:*[^>]*+>#iu', '$1>', $data); - - // Remove namespaced elements (we do not need them) - $data = preg_replace('#]*+>#i', '', $data); - - do - { - // Remove really unwanted tags - $old_data = $data; - $data = preg_replace('#]*+>#i', '', $data); - } - while ($old_data !== $data); - break; + 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; } @@ -399,9 +442,7 @@ class Input_Core { */ public function clean_input_keys($str) { - $chars = PCRE_UNICODE_PROPERTIES ? '\pL' : 'a-zA-Z'; - - if ( ! preg_match('#^['.$chars.'0-9:_.-]++$#uD', $str)) + if ( ! preg_match('#^[\pL0-9:_.-]++$#uD', $str)) { exit('Disallowed key characters in global data.'); } @@ -449,4 +490,43 @@ class Input_Core { 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 00000000..8c7cc787 --- /dev/null +++ b/system/libraries/Kohana_404_Exception.php @@ -0,0 +1,56 @@ + $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 00000000..44ef8af8 --- /dev/null +++ b/system/libraries/Kohana_Log.php @@ -0,0 +1,90 @@ + $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 00000000..fca5b30b --- /dev/null +++ b/system/libraries/Kohana_PHP_Exception.php @@ -0,0 +1,99 @@ +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()) + { + // 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 00000000..95b7bc68 --- /dev/null +++ b/system/libraries/Kohana_User_Exception.php @@ -0,0 +1,30 @@ +code = $title; + } + +} // End Kohana User Exception diff --git a/system/libraries/Model.php b/system/libraries/Model.php index 0c9fd8d6..01d16fdd 100644 --- a/system/libraries/Model.php +++ b/system/libraries/Model.php @@ -2,15 +2,46 @@ /** * Model base class. * - * $Id: Model.php 4007 2009-02-20 01:54:00Z jheathco $ + * $Id: Model.php 4679 2009-11-10 01:45:52Z isaiah $ * * @package Core * @author Kohana Team * @copyright (c) 2007-2009 Kohana Team - * @license http://kohanaphp.com/license.html + * @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'; diff --git a/system/libraries/ORM.php b/system/libraries/ORM.php index 5196ba27..1d1f48f5 100644 --- a/system/libraries/ORM.php +++ b/system/libraries/ORM.php @@ -8,12 +8,12 @@ * [ref-orm]: http://wikipedia.org/wiki/Object-relational_mapping * [ref-act]: http://wikipedia.org/wiki/Active_record * - * $Id: ORM.php 4354 2009-05-15 16:51:37Z kiall $ + * $Id: ORM.php 4682 2009-11-11 20:53:23Z isaiah $ * * @package ORM * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html + * @copyright (c) 2007-2009 Kohana Team + * @license http://kohanaphp.com/license */ class ORM_Core { @@ -22,6 +22,7 @@ class ORM_Core { 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(); @@ -30,9 +31,11 @@ class ORM_Core { protected $object = array(); protected $changed = array(); protected $related = array(); - protected $loaded = FALSE; - protected $saved = FALSE; + protected $_valid = FALSE; + protected $_loaded = FALSE; + protected $_saved = FALSE; protected $sorting; + protected $rules = array(); // Related objects protected $object_relations = array(); @@ -45,6 +48,10 @@ class ORM_Core { 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'; @@ -59,6 +66,7 @@ class ORM_Core { // Database configuration protected $db = 'default'; protected $db_applied = array(); + protected $db_builder; // With calls already applied protected $with_applied = array(); @@ -109,12 +117,15 @@ class ORM_Core { if (is_object($id)) { // Load an object - $this->load_values((array) $id); + $this->_load_values((array) $id); } elseif (!empty($id)) { - // Find an object - $this->find($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; } } @@ -152,6 +163,9 @@ class ORM_Core { // Load column information $this->reload_columns(); + + // Initialize the builder + $this->db_builder = db::build(); } /** @@ -163,7 +177,7 @@ class ORM_Core { public function __sleep() { // Store only information about the object - return array('object_name', 'object', 'changed', 'loaded', 'saved', 'sorting'); + return array('object_name', 'object', 'changed', '_loaded', '_saved', 'sorting'); } /** @@ -194,10 +208,10 @@ class ORM_Core { */ public function __call($method, array $args) { - if (method_exists($this->db, $method)) + if (method_exists($this->db_builder, $method)) { - if (in_array($method, array('query', 'get', 'insert', 'update', 'delete'))) - throw new Kohana_Exception('orm.query_methods_not_allowed'); + 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; @@ -208,7 +222,7 @@ class ORM_Core { if ($method === 'select' AND $num_args > 3) { // Call select() manually to avoid call_user_func_array - $this->db->select($args); + $this->db_builder->select($args); } else { @@ -220,28 +234,28 @@ class ORM_Core { switch ($num_args) { case 0: - if (in_array($method, array('open_paren', 'close_paren', 'enable_cache', 'disable_cache'))) + if (in_array($method, array('open', 'close', 'cache'))) { // Should return ORM, not Database - $this->db->$method(); + $this->db_builder->$method(); } else { // Support for things like reset_select, reset_write, list_tables - return $this->db->$method(); + return $this->db_builder->$method(); } break; case 1: - $this->db->$method($args[0]); + $this->db_builder->$method($args[0]); break; case 2: - $this->db->$method($args[0], $args[1]); + $this->db_builder->$method($args[0], $args[1]); break; case 3: - $this->db->$method($args[0], $args[1], $args[2]); + $this->db_builder->$method($args[0], $args[1], $args[2]); break; case 4: - $this->db->$method($args[0], $args[1], $args[2], $args[3]); + $this->db_builder->$method($args[0], $args[1], $args[2], $args[3]); break; default: // Here comes the snail... @@ -254,7 +268,8 @@ class ORM_Core { } else { - throw new Kohana_Exception('core.invalid_method', $method, get_class($this)); + throw new Kohana_Exception('Invalid method :method called in :class', + array(':method' => $method, ':class' => get_class($this))); } } @@ -268,6 +283,13 @@ class ORM_Core { { 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])) @@ -276,16 +298,29 @@ class ORM_Core { } 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) OR ! array_key_exists($this->foreign_key($column), $model->object)) + if (in_array($model->object_name, $this->belongs_to)) { - // Foreign key lies in this table (this model belongs_to target model) OR an invalid has_one relationship - $where = array($model->table_name.'.'.$model->primary_key => $this->object[$this->foreign_key($column)]); + 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 { @@ -296,10 +331,10 @@ class ORM_Core { // one<>alias:one relationship return $this->related[$column] = $model->find($where); } - elseif (isset($this->has_many[$column])) + elseif (isset($this->has_many_through[$column])) { // Load the "middle" model - $through = ORM::factory(inflector::singular($this->has_many[$column])); + $through = ORM::factory(inflector::singular($this->has_many_through[$column])); // Load the "end" model $model = ORM::factory(inflector::singular($column)); @@ -308,21 +343,27 @@ class ORM_Core { // User-defined foreign keys must be defined in the 'through' model $join_table = $through->table_name; $join_col1 = $through->foreign_key($model->object_name, $join_table); - $join_col2 = $model->table_name.'.'.$model->primary_key; + $join_col2 = $model->foreign_key(TRUE); // one<>alias:many relationship - return $this->related[$column] = $model + return $model ->join($join_table, $join_col1, $join_col2) - ->where($through->foreign_key($this->object_name, $join_table), $this->object[$this->primary_key]) - ->find_all(); + ->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 $this->related[$column] = $model - ->where($this->foreign_key($column, $model->table_name), $this->object[$this->primary_key]) - ->find_all(); + return $model->where($this->foreign_key($column, $model->table_name), '=', $this->primary_key_value); } elseif (in_array($column, $this->has_and_belongs_to_many)) { @@ -332,16 +373,12 @@ class ORM_Core { if ($this->has($model, TRUE)) { // many<>many relationship - return $this->related[$column] = $model - ->in($model->table_name.'.'.$model->primary_key, $this->changed_relations[$column]) - ->find_all(); + return $model->where($model->foreign_key(TRUE), 'IN', $this->changed_relations[$column]); } else { // empty many<>many relationship - return $this->related[$column] = $model - ->where($model->table_name.'.'.$model->primary_key, NULL) - ->find_all(); + return $model->where($model->foreign_key(TRUE), 'IS', NULL); } } elseif (isset($this->ignored_columns[$column])) @@ -350,10 +387,9 @@ class ORM_Core { } elseif (in_array($column, array ( - 'object_name', 'object_plural', // Object + 'object_name', 'object_plural','_valid', // Object 'primary_key', 'primary_val', 'table_name', 'table_columns', // Table - 'loaded', 'saved', // Status - 'has_one', 'belongs_to', 'has_many', 'has_and_belongs_to_many', 'load_with' // Relationships + 'has_one', 'belongs_to', 'has_many', 'has_many_through', 'has_and_belongs_to_many', 'load_with' // Relationships ))) { // Model meta information @@ -361,8 +397,32 @@ class ORM_Core { } else { - throw new Kohana_Exception('core.invalid_property', $column, get_class($this)); + 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; } /** @@ -386,7 +446,7 @@ class ORM_Core { $this->changed[$column] = $column; // Object is no longer saved - $this->saved = FALSE; + $this->_saved = FALSE; } $this->object[$column] = $this->load_type($column, $value); @@ -413,8 +473,33 @@ class ORM_Core { } else { - throw new Kohana_Exception('core.invalid_property', $column, get_class($this)); + 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; } /** @@ -446,7 +531,7 @@ class ORM_Core { */ public function __toString() { - return (string) $this->object[$this->primary_key]; + return (string) $this->primary_key_value; } /** @@ -464,6 +549,15 @@ class ORM_Core { $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; } @@ -471,8 +565,8 @@ class ORM_Core { * Binds another one-to-one object to this model. One-to-one objects * can be nested using 'object1:object2' syntax * - * @param string $target_path - * @return void + * @param string target model to bind to + * @return void */ public function with($target_path) { @@ -521,19 +615,19 @@ class ORM_Core { // 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 - $select = array_keys($target->object); - foreach ($select as $i => $column) + foreach (array_keys($target->object) as $column) { // Add the prefix so that load_result can determine the relationship - $select[$i] = $target_path.'.'.$column.' AS '.$target_path.':'.$column; + $select[$target_path.':'.$column] = $target_path.'.'.$column; } - // Select all of the prefixed keys in the object - $this->db->select($select); + $this->db_builder->select($select); - if (in_array($target->object_name, $parent->belongs_to) OR ! isset($target->object[$parent->foreign_key($target_name)])) + 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); @@ -546,11 +640,12 @@ class ORM_Core { $join_col1 = $parent->foreign_key($target_name, $target_path); } - // This allows for models to use different table prefixes (sharing the same database) - $join_table = new Database_Expression($target->db->table_prefix().$target->table_name.' AS '.$this->db->table_prefix().$target_path); + // 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 - $this->db->join($join_table, $join_col1, $join_col2, 'LEFT'); + // Use Database_Expression to disable prefixing + $this->db_builder->join(new Database_Expression($join_table), $join_col1, $join_col2, 'LEFT'); return $this; } @@ -560,25 +655,26 @@ class ORM_Core { * * @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) + public function find($id = NULL, $ignore_changed = FALSE) { if ($id !== NULL) { if (is_array($id)) { // Search for all clauses - $this->db->where($id); + $this->db_builder->where($id); } else { // Search for a specific column - $this->db->where($this->table_name.'.'.$this->unique_key($id), $id); + $this->db_builder->where($this->table_name.'.'.$this->unique_key($id), '=', $id); } } - return $this->load_result(); + return $this->load_result(FALSE, $ignore_changed); } /** @@ -627,68 +723,52 @@ class ORM_Core { } // Return a select list from the results - return $this->select($key, $val)->find_all()->select_list($key, $val); + return $this->select($this->table_name.'.'.$key, $this->table_name.'.'.$val)->find_all()->select_list($key, $val); } /** - * Validates the current object. This method should generally be called - * via the model, after the $_POST Validation object has been created. + * 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 - * @return boolean + * @param boolean Save on validate + * @return ORM + * @chainable */ - public function validate(Validation $array, $save = FALSE) + public function validate(Validation $array = NULL) { - $safe_array = $array->safe_array(); + if ($array === NULL) + $array = new Validation($this->object); - if ( ! $array->submitted()) + if (count($this->rules) > 0) { - foreach ($safe_array as $key => $value) + foreach ($this->rules as $field => $parameters) { - // Get the value from this object - $value = $this->$key; - - if (is_object($value) AND $value instanceof ORM_Iterator) - { - // Convert the value to an array of primary keys - $value = $value->primary_key_array(); + 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; + } } - - // Pre-fill data - $array[$key] = $value; } } - // Validate the array - if ($status = $array->validate()) + if (($this->_valid = $array->validate()) === FALSE) { - // Grab only set fields (excludes missing data, unlike safe_array) - $fields = $array->as_array(); - - foreach ($fields as $key => $value) - { - if (isset($safe_array[$key])) - { - // Set new data, ignoring any missing fields or fields without rules - $this->$key = $value; - } - } - - if ($save === TRUE OR is_string($save)) - { - // Save this object - $this->save(); - - if (is_string($save)) - { - // Redirect to the saved page - url::redirect($save); - } - } + ORM_Validation_Exception::handle_validation($this->table_name, $array); } // Return validation status - return $status; + return $this; } /** @@ -701,6 +781,10 @@ class ORM_Core { { if ( ! empty($this->changed)) { + // Require model validation before saving + if (!$this->_valid) + $this->validate(); + $data = array(); foreach ($this->changed as $column) { @@ -708,41 +792,64 @@ class ORM_Core { $data[$column] = $this->object[$column]; } - if ($this->loaded === TRUE) + if ( ! $this->empty_primary_key() AND ! isset($this->changed[$this->primary_key])) { - $query = $this->db - ->where($this->primary_key, $this->object[$this->primary_key]) - ->update($this->table_name, $data); + // 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; + $this->_saved = TRUE; } else { - $query = $this->db - ->insert($this->table_name, $data); + 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 ($query->count() > 0) + if ($result->count() > 0) { if (empty($this->object[$this->primary_key])) { // Load the insert id as the primary key - $this->object[$this->primary_key] = $query->insert_id(); + $this->object[$this->primary_key] = $result->insert_id(); } // Object is now loaded and saved - $this->loaded = $this->saved = TRUE; + $this->_loaded = $this->_saved = TRUE; } } - if ($this->saved === TRUE) + if ($this->saved() === TRUE) { // All changes have been saved $this->changed = array(); } } - if ($this->saved === TRUE AND ! empty($this->changed_relations)) + if ($this->saved() === TRUE AND ! empty($this->changed_relations)) { foreach ($this->changed_relations as $column => $values) { @@ -782,20 +889,19 @@ class ORM_Core { foreach ($added as $id) { // Insert the new relationship - $this->db->insert($join_table, array - ( - $object_fk => $this->object[$this->primary_key], - $related_fk => $id, - )); + db::insert($join_table) + ->columns($object_fk, $related_fk) + ->values($this->primary_key_value, $id) + ->execute($this->db); } } if ( ! empty($removed)) { - $this->db - ->where($object_fk, $this->object[$this->primary_key]) - ->in($related_fk, $removed) - ->delete($join_table); + 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 @@ -811,18 +917,21 @@ class ORM_Core { * relationships that have been created with other objects. * * @chainable + * @param mixed id to delete * @return ORM */ public function delete($id = NULL) { - if ($id === NULL AND $this->loaded) + if ($id === NULL) { // Use the the primary key value - $id = $this->object[$this->primary_key]; + $id = $this->primary_key_value; } // Delete this object - $this->db->where($this->primary_key, $id)->delete($this->table_name); + db::delete($this->table_name) + ->where($this->primary_key, '=', $id) + ->execute($this->db); return $this->clear(); } @@ -840,12 +949,15 @@ class ORM_Core { if (is_array($ids)) { // Delete only given ids - $this->db->in($this->primary_key, $ids); + db::delete($this->table_name) + ->where($this->primary_key, 'IN', $ids) + ->execute($this->db); } - elseif (is_null($ids)) + elseif ($ids === NULL) { // Delete all records - $this->db->where('1=1'); + db::delete($this->table_name) + ->execute($this->db); } else { @@ -853,9 +965,6 @@ class ORM_Core { return $this; } - // Delete all objects - $this->db->delete($this->table_name); - return $this->clear(); } @@ -872,7 +981,7 @@ class ORM_Core { $values = array_combine($columns, array_fill(0, count($columns), NULL)); // Replace the current object with an empty one - $this->load_values($values); + $this->_load_values($values); return $this; } @@ -941,6 +1050,12 @@ class ORM_Core { $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 @@ -1020,40 +1135,22 @@ class ORM_Core { public function count_all() { // Return the total number of records in a table - return $this->db->count_records($this->table_name); + return $this->db_builder->count_records($this->table_name); } /** * Proxy method to Database list_fields. * - * @param string table name or NULL to use this table - * @return array - */ - public function list_fields($table = NULL) - { - if ($table === NULL) - { - $table = $this->table_name; - } - - // Proxy to database - return $this->db->list_fields($table); - } - - /** - * Proxy method to Database field_data. - * - * @param string table name * @return array */ - public function field_data($table) + public function list_fields() { // Proxy to database - return $this->db->field_data($table); + return $this->db->list_fields($this->table_name); } /** - * Proxy method to Database field_data. + * Proxy method to Database clear_cache. * * @chainable * @param string SQL query to clear @@ -1203,14 +1300,63 @@ class ORM_Core { * @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)) { - // Replace the object and reset the object status - $this->object = $this->changed = $this->related = array(); + 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); + $this->_loaded = $this->_saved = ($values[$this->primary_key] !== NULL); } // Related objects @@ -1220,13 +1366,16 @@ class ORM_Core { { if (strpos($column, ':') === FALSE) { - if (isset($this->table_columns[$column])) + if ( ! $ignore_changed OR ! isset($this->changed[$column])) { - // The type of the value can be determined, convert the value - $value = $this->load_type($column, $value); - } + if (isset($this->table_columns[$column])) + { + // The type of the value can be determined, convert the value + $value = $this->load_type($column, $value); + } - $this->object[$column] = $value; + $this->object[$column] = $value; + } } else { @@ -1241,13 +1390,14 @@ class ORM_Core { 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); + $this->related[$object] = $this->related_object($object)->_load_values($values); } } return $this; } + /** * Loads a value according to the types defined by the column metadata. * @@ -1264,7 +1414,7 @@ class ORM_Core { // Load column data $column = $this->table_columns[$column]; - if ($value === NULL AND ! empty($column['null'])) + if ($value === NULL AND ! empty($column['nullable'])) return $value; if ( ! empty($column['binary']) AND ! empty($column['exact']) AND (int) $column['length'] === 1) @@ -1276,7 +1426,7 @@ class ORM_Core { switch ($column['type']) { case 'int': - if ($value === '' AND ! empty($column['null'])) + if ($value === '' AND ! empty($column['nullable'])) { // Forms will only submit strings, so empty integer values must be null $value = NULL; @@ -1295,9 +1445,7 @@ class ORM_Core { $value = (float) $value; break; case 'boolean': - if ($value === "t") $value = true; // For PgSQL - else if ($value === "f") $value = false; // For PgSQL - else $value = (bool) $value; + $value = (bool) $value; break; case 'string': $value = (string) $value; @@ -1313,21 +1461,24 @@ class ORM_Core { * * @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) + 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->limit(1); + $this->db_builder->limit(1); } if ( ! isset($this->db_applied['select'])) { // Select all columns by default - $this->db->select($this->table_name.'.*'); + $this->db_builder->select($this->table_name.'.*'); } if ( ! empty($this->load_with)) @@ -1348,7 +1499,7 @@ class ORM_Core { } } - if ( ! isset($this->db_applied['orderby']) AND ! empty($this->sorting)) + if ( ! isset($this->db_applied['order_by']) AND ! empty($this->sorting)) { $sorting = array(); foreach ($this->sorting as $column => $direction) @@ -1364,11 +1515,11 @@ class ORM_Core { } // Apply the user-defined sorting - $this->db->orderby($sorting); + $this->db_builder->order_by($sorting); } // Load the result - $result = $this->db->get($this->table_name); + $result = $this->db_builder->execute($this->db); if ($array === TRUE) { @@ -1379,7 +1530,7 @@ class ORM_Core { if ($result->count() === 1) { // Load object values - $this->load_values($result->result(FALSE)->current()); + $this->_load_values($result->as_array()->current(), $ignore_changed); } else { @@ -1399,20 +1550,14 @@ class ORM_Core { */ protected function load_relations($table, ORM $model) { - // Save the current query chain (otherwise the next call will clash) - $this->db->push(); - - $query = $this->db - ->select($model->foreign_key(NULL).' AS id') + $result = db::select(array('id' => $model->foreign_key(NULL))) ->from($table) - ->where($this->foreign_key(NULL, $table), $this->object[$this->primary_key]) - ->get() - ->result(TRUE); - - $this->db->pop(); + ->where($this->foreign_key(NULL, $table), '=', $this->primary_key_value) + ->execute($this->db) + ->as_object(); $relations = array(); - foreach ($query as $row) + foreach ($result as $row) { $relations[] = $row->id; } diff --git a/system/libraries/ORM_Iterator.php b/system/libraries/ORM_Iterator.php index 41aa8065..403d1e93 100644 --- a/system/libraries/ORM_Iterator.php +++ b/system/libraries/ORM_Iterator.php @@ -2,12 +2,12 @@ /** * Object Relational Mapping (ORM) result iterator. * -* $Id: ORM_Iterator.php 3769 2008-12-15 00:48:56Z zombor $ +* $Id: ORM_Iterator.php 4679 2009-11-10 01:45:52Z isaiah $ * * @package ORM * @author Kohana Team -* @copyright (c) 2007-2008 Kohana Team -* @license http://kohanaphp.com/license.html +* @copyright (c) 2007-2009 Kohana Team +* @license http://kohanaphp.com/license */ class ORM_Iterator_Core implements Iterator, ArrayAccess, Countable { @@ -26,27 +26,67 @@ class ORM_Iterator_Core implements Iterator, ArrayAccess, Countable { $this->primary_key = $model->primary_key; $this->primary_val = $model->primary_val; - // Database result - $this->result = $result->result(TRUE); + // Database result (make sure rows are returned as arrays) + $this->result = $result; } /** - * Returns an array of the results as ORM objects. + * 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() + public function as_array($objects = TRUE, $key = NULL) { $array = array(); - if ($results = $this->result->result_array()) - { - // Import class name - $class = $this->class_name; + // Import class name + $class = $this->class_name; - foreach ($results as $obj) + if ($objects) + { + // Generate an array of objects + foreach ($this->result as $data) { - $array[] = new $class($obj); + 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; } } @@ -90,7 +130,7 @@ class ORM_Iterator_Core implements Iterator, ArrayAccess, Countable { } $array = array(); - foreach ($this->result->result_array() as $row) + foreach ($this->result as $row) { $array[$row->$key] = $row->$val; } diff --git a/system/libraries/ORM_Tree.php b/system/libraries/ORM_Tree.php deleted file mode 100644 index cdb09fd0..00000000 --- a/system/libraries/ORM_Tree.php +++ /dev/null @@ -1,76 +0,0 @@ -related[$column])) - { - // Load child model - $model = ORM::factory(inflector::singular($this->ORM_Tree_children)); - - if (array_key_exists($this->ORM_Tree_parent_key, $this->object)) - { - // Find children of this parent - $model->where($model->primary_key, $this->object[$this->ORM_Tree_parent_key])->find(); - } - - $this->related[$column] = $model; - } - - return $this->related[$column]; - } - elseif ($column === 'children') - { - if (empty($this->related[$column])) - { - $model = ORM::factory(inflector::singular($this->ORM_Tree_children)); - - if ($this->ORM_Tree_children === $this->table_name) - { - // Load children within this table - $this->related[$column] = $model - ->where($this->ORM_Tree_parent_key, $this->object[$this->primary_key]) - ->find_all(); - } - else - { - // Find first selection of children - $this->related[$column] = $model - ->where($this->foreign_key(), $this->object[$this->primary_key]) - ->where($this->ORM_Tree_parent_key, NULL) - ->find_all(); - } - } - - return $this->related[$column]; - } - - return parent::__get($column); - } - -} // End ORM Tree \ No newline at end of file diff --git a/system/libraries/ORM_Validation_Exception.php b/system/libraries/ORM_Validation_Exception.php new file mode 100644 index 00000000..95f96c3b --- /dev/null +++ b/system/libraries/ORM_Validation_Exception.php @@ -0,0 +1,26 @@ +$table)); + $exception->validation = $array; + throw $exception; + } +} // End ORM_Validation_Exception \ No newline at end of file diff --git a/system/libraries/ORM_Versioned.php b/system/libraries/ORM_Versioned.php deleted file mode 100644 index 078fe16a..00000000 --- a/system/libraries/ORM_Versioned.php +++ /dev/null @@ -1,143 +0,0 @@ -last_version = 1 + ($this->last_version === NULL ? $this->object['version'] : $this->last_version); - $this->__set('version', $this->last_version); - - parent::save(); - - if ($this->saved) - { - $data = array(); - foreach ($this->object as $key => $value) - { - if ($key === 'id') - continue; - - $data[$key] = $value; - } - $data[$this->foreign_key()] = $this->id; - - $this->db->insert($this->table_name.'_versions', $data); - } - - return $this; - } - - /** - * Loads previous version from current object - * - * @chainable - * @return ORM - */ - public function previous() - { - if ( ! $this->loaded) - return $this; - - $this->last_version = ($this->last_version === NULL) ? $this->object['version'] : $this->last_version; - $version = $this->last_version - 1; - - $query = $this->db - ->where($this->foreign_key(), $this->object[$this->primary_key]) - ->where('version', $version) - ->limit(1) - ->get($this->table_name.'_versions'); - - if ($query->count()) - { - $this->load_values($query->result(FALSE)->current()); - } - - return $this; - } - - /** - * Restores the object with data from stored version - * - * @param integer version number you want to restore - * @return ORM - */ - public function restore($version) - { - if ( ! $this->loaded) - return $this; - - $query = $this->db - ->where($this->foreign_key(), $this->object[$this->primary_key]) - ->where('version', $version) - ->limit(1) - ->get($this->table_name.'_versions'); - - if ($query->count()) - { - $row = $query->result(FALSE)->current(); - - foreach ($row as $key => $value) - { - if ($key === $this->primary_key OR $key === $this->foreign_key()) - { - // Do not overwrite the primary key - continue; - } - - if ($key === 'version') - { - // Always use the current version - $value = $this->version; - } - - $this->__set($key, $value); - } - - $this->save(); - } - - return $this; - } - - /** - * Overloads ORM::delete() to delete all versioned entries of current object - * and the object itself - * - * @param integer id of the object you want to delete - * @return ORM - */ - public function delete($id = NULL) - { - if ($id === NULL) - { - // Use the current object id - $id = $this->object[$this->primary_key]; - } - - if ($status = parent::delete($id)) - { - $this->db->where($this->foreign_key(), $id)->delete($this->table_name.'_versions'); - } - - return $status; - } - -} \ No newline at end of file diff --git a/system/libraries/Pagination.php b/system/libraries/Pagination.php deleted file mode 100644 index a8f7bb19..00000000 --- a/system/libraries/Pagination.php +++ /dev/null @@ -1,236 +0,0 @@ -initialize($config); - - Kohana::log('debug', 'Pagination Library initialized'); - } - - /** - * Sets config values. - * - * @throws Kohana_Exception - * @param array configuration settings - * @return void - */ - public function initialize($config = array()) - { - // Load config group - if (isset($config['group'])) - { - // Load and validate config group - if ( ! is_array($group_config = Kohana::config('pagination.'.$config['group']))) - throw new Kohana_Exception('pagination.undefined_group', $config['group']); - - // All pagination config groups inherit default config group - if ($config['group'] !== 'default') - { - // Load and validate default config group - if ( ! is_array($default_config = Kohana::config('pagination.default'))) - throw new Kohana_Exception('pagination.undefined_group', 'default'); - - // Merge config group with default config group - $group_config += $default_config; - } - - // Merge custom config items with config group - $config += $group_config; - } - - // Assign config values to the object - foreach ($config as $key => $value) - { - if (property_exists($this, $key)) - { - $this->$key = $value; - } - } - - // Clean view directory - $this->directory = trim($this->directory, '/').'/'; - - // Build generic URL with page in query string - if ($this->query_string !== '') - { - // Extract current page - $this->current_page = isset($_GET[$this->query_string]) ? (int) $_GET[$this->query_string] : 1; - - // Insert {page} placeholder - $_GET[$this->query_string] = '{page}'; - - // Create full URL - $base_url = ($this->base_url === '') ? Router::$current_uri : $this->base_url; - $this->url = url::site($base_url).'?'.str_replace('%7Bpage%7D', '{page}', http_build_query($_GET)); - - // Reset page number - $_GET[$this->query_string] = $this->current_page; - } - - // Build generic URL with page as URI segment - else - { - // Use current URI if no base_url set - $this->url = ($this->base_url === '') ? Router::$segments : explode('/', trim($this->base_url, '/')); - - // Convert uri 'label' to corresponding integer if needed - if (is_string($this->uri_segment)) - { - if (($key = array_search($this->uri_segment, $this->url)) === FALSE) - { - // If uri 'label' is not found, auto add it to base_url - $this->url[] = $this->uri_segment; - $this->uri_segment = count($this->url) + 1; - } - else - { - $this->uri_segment = $key + 2; - } - } - - // Insert {page} placeholder - $this->url[$this->uri_segment - 1] = '{page}'; - - // Create full URL - $this->url = url::site(implode('/', $this->url)).Router::$query_string; - - // Extract current page - $this->current_page = URI::instance()->segment($this->uri_segment); - } - - // Core pagination values - $this->total_items = (int) max(0, $this->total_items); - $this->items_per_page = (int) max(1, $this->items_per_page); - $this->total_pages = (int) ceil($this->total_items / $this->items_per_page); - $this->current_page = (int) min(max(1, $this->current_page), max(1, $this->total_pages)); - $this->current_first_item = (int) min((($this->current_page - 1) * $this->items_per_page) + 1, $this->total_items); - $this->current_last_item = (int) min($this->current_first_item + $this->items_per_page - 1, $this->total_items); - - // If there is no first/last/previous/next page, relative to the - // current page, value is set to FALSE. Valid page number otherwise. - $this->first_page = ($this->current_page === 1) ? FALSE : 1; - $this->last_page = ($this->current_page >= $this->total_pages) ? FALSE : $this->total_pages; - $this->previous_page = ($this->current_page > 1) ? $this->current_page - 1 : FALSE; - $this->next_page = ($this->current_page < $this->total_pages) ? $this->current_page + 1 : FALSE; - - // SQL values - $this->sql_offset = (int) ($this->current_page - 1) * $this->items_per_page; - $this->sql_limit = sprintf(' LIMIT %d OFFSET %d ', $this->items_per_page, $this->sql_offset); - } - - /** - * Generates the HTML for the chosen pagination style. - * - * @param string pagination style - * @return string pagination html - */ - public function render($style = NULL) - { - // Hide single page pagination - if ($this->auto_hide === TRUE AND $this->total_pages <= 1) - return ''; - - if ($style === NULL) - { - // Use default style - $style = $this->style; - } - - // Return rendered pagination view - return View::factory($this->directory.$style, get_object_vars($this))->render(); - } - - /** - * Magically converts Pagination object to string. - * - * @return string pagination html - */ - public function __toString() - { - return $this->render(); - } - - /** - * Magically gets a pagination variable. - * - * @param string variable key - * @return mixed variable value if the key is found - * @return void if the key is not found - */ - public function __get($key) - { - if (isset($this->$key)) - return $this->$key; - } - - /** - * Adds a secondary interface for accessing properties, e.g. $pagination->total_pages(). - * Note that $pagination->total_pages is the recommended way to access properties. - * - * @param string function name - * @return string - */ - public function __call($func, $args = NULL) - { - return $this->__get($func); - } - -} // End Pagination Class \ No newline at end of file diff --git a/system/libraries/Profiler.php b/system/libraries/Profiler.php index 9da053fb..b7a5ecae 100644 --- a/system/libraries/Profiler.php +++ b/system/libraries/Profiler.php @@ -8,100 +8,115 @@ * POST Data - The name and values of any POST data submitted to the current page. * Cookie Data - All cookies sent for the current request. * - * $Id: Profiler.php 4383 2009-06-03 00:17:24Z ixmatus $ + * $Id: Profiler.php 4679 2009-11-10 01:45:52Z isaiah $ * * @package Profiler * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html + * @copyright (c) 2007-2009 Kohana Team + * @license http://kohanaphp.com/license */ class Profiler_Core { - protected $profiles = array(); - protected $show; + protected static $profiles = array(); + protected static $show; - public function __construct() + /** + * Enable the profiler. + * + * @return void + */ + public static function enable() { // Add all built in profiles to event - Event::add('profiler.run', array($this, 'benchmarks')); - Event::add('profiler.run', array($this, 'database')); - Event::add('profiler.run', array($this, 'session')); - Event::add('profiler.run', array($this, 'post')); - Event::add('profiler.run', array($this, 'cookies')); + 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($this, 'render')); + Event::add('system.display', array('Profiler', 'render')); + + Kohana_Log::add('debug', 'Profiler library enabled'); - Kohana::log('debug', 'Profiler Library initialized'); } /** - * Magic __call method. Creates a new profiler section object. + * Disables the profiler for this page only. + * Best used when profiler is autoloaded. * - * @param string input type - * @param string input name - * @return object + * @return void */ - public function __call($method, $args) + public static function disable() { - if ( ! $this->show OR (is_array($this->show) AND ! in_array($args[0], $this->show))) - return FALSE; - - // Class name - $class = 'Profiler_'.ucfirst($method); - - $class = new $class(); - - $this->profiles[$args[0]] = $class; + // Removes itself from the event queue + Event::clear('system.display', array('Profiler', 'render')); + } - return $class; + /** + * 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; } /** - * Disables the profiler for this page only. - * Best used when profiler is autoloaded. + * Add a new profile. * - * @return void + * @param object profile object + * @return boolean + * @throws Kohana_Exception */ - public function disable() + public static function add($profile) { - // Removes itself from the event queue - Event::clear('system.display', array($this, 'render')); + if (is_object($profile)) + { + Profiler::$profiles[] = $profile; + return TRUE; + } + + throw new Kohana_Exception('The profile must be an object'); } /** - * Render the profiler. Output is added to the bottom of the page by default. + * Render the profiler. * - * @param boolean return the output if TRUE + * @param boolean return the output instead of adding it to bottom of page * @return void|string */ - public function render($return = FALSE) + public static function render($return = FALSE) { $start = microtime(TRUE); + // Determine the profiles that should be shown $get = isset($_GET['profiler']) ? explode(',', $_GET['profiler']) : array(); - $this->show = empty($get) ? Kohana::config('profiler.show') : $get; + Profiler::$show = empty($get) ? Kohana::config('profiler.show') : $get; - Event::run('profiler.run', $this); + Event::run('profiler.run'); + + // Don't display if there's no profiles + if (empty(Profiler::$profiles)) + return $output; $styles = ''; - foreach ($this->profiles as $profile) + foreach (Profiler::$profiles as $profile) { $styles .= $profile->styles(); } - // Don't display if there's no profiles - if (empty($this->profiles)) - return; - // Load the profiler view $data = array ( - 'profiles' => $this->profiles, - 'styles' => $styles, + 'profiles' => Profiler::$profiles, + 'styles' => $styles, 'execution_time' => microtime(TRUE) - $start ); - $view = new View('kohana_profiler', $data); + $view = new View('profiler/profiler', $data); // Return rendered view if $return is TRUE if ($return === TRUE) @@ -125,16 +140,17 @@ class Profiler_Core { * * @return void */ - public function benchmarks() + public static function benchmarks() { - if ( ! $table = $this->table('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', 'Time', 'Count', 'Memory'), 'kp-title', 'background-color: #FFE0E0'); + $table->add_row(array(__('Benchmarks'), __('Count'), __('Time'), __('Memory')), 'kp-title', 'background-color: #FFE0E0'); $benchmarks = Benchmark::get(TRUE); @@ -147,14 +163,20 @@ class Profiler_Core { // Clean unique id from system benchmark names $name = ucwords(str_replace(array('_', '-'), ' ', str_replace(SYSTEM_BENCHMARK.'_', '', $name))); - $data = array($name, number_format($benchmark['time'], 3), $benchmark['count'], number_format($benchmark['memory'] / 1024 / 1024, 2).'MB'); + $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); } /** @@ -162,31 +184,37 @@ class Profiler_Core { * * @return void */ - public function database() + public static function database() { - if ( ! $table = $this->table('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'); - - $queries = Database::$benchmarks; + $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'], 3), $query['rows']); + $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, 3), $total_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); } /** @@ -194,16 +222,17 @@ class Profiler_Core { * * @return void */ - public function session() + public static function session() { if (empty($_SESSION)) return; - if ( ! $table = $this->table('session')) + 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'); + $table->add_row(array(__('Session'), __('Value')), 'kp-title', 'background-color: #CCE8FB'); text::alternate(); foreach($_SESSION as $name => $value) @@ -217,6 +246,8 @@ class Profiler_Core { $class = text::alternate('', 'kp-altrow'); $table->add_row($data, $class); } + + Profiler::add($table); } /** @@ -224,16 +255,17 @@ class Profiler_Core { * * @return void */ - public function post() + public static function post() { if (empty($_POST)) return; - if ( ! $table = $this->table('post')) + 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'); + $table->add_row(array(__('POST'), __('Value')), 'kp-title', 'background-color: #E0E0FF'); text::alternate(); foreach($_POST as $name => $value) @@ -242,6 +274,8 @@ class Profiler_Core { $class = text::alternate('', 'kp-altrow'); $table->add_row($data, $class); } + + Profiler::add($table); } /** @@ -249,16 +283,17 @@ class Profiler_Core { * * @return void */ - public function cookies() + public static function cookies() { if (empty($_COOKIE)) return; - if ( ! $table = $this->table('cookies')) + 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'); + $table->add_row(array(__('Cookies'), __('Value')), 'kp-title', 'background-color: #FFF4D7'); text::alternate(); foreach($_COOKIE as $name => $value) @@ -267,5 +302,7 @@ class Profiler_Core { $class = text::alternate('', 'kp-altrow'); $table->add_row($data, $class); } + + Profiler::add($table); } -} \ No newline at end of file +} diff --git a/system/libraries/Profiler_Table.php b/system/libraries/Profiler_Table.php index a0058a58..fdb62aef 100644 --- a/system/libraries/Profiler_Table.php +++ b/system/libraries/Profiler_Table.php @@ -2,12 +2,12 @@ /** * Provides a table layout for sections in the Profiler library. * - * $Id$ + * $Id: Profiler_Table.php 4679 2009-11-10 01:45:52Z isaiah $ * * @package Profiler * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html + * @copyright (c) 2007-2009 Kohana Team + * @license http://kohanaphp.com/license */ class Profiler_Table_Core { @@ -26,7 +26,7 @@ class Profiler_Table_Core { if ( ! $styles_output) { $styles_output = TRUE; - return file_get_contents(Kohana::find_file('views', 'kohana_profiler_table', FALSE, 'css')); + return file_get_contents(Kohana::find_file('views', 'profiler/table', FALSE, 'css')); } return ''; @@ -64,6 +64,6 @@ class Profiler_Table_Core { { $data['rows'] = $this->rows; $data['columns'] = $this->columns; - return View::factory('kohana_profiler_table', $data)->render(); + 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 index ef0e1e47..82dbb9b5 100644 --- a/system/libraries/Router.php +++ b/system/libraries/Router.php @@ -2,12 +2,12 @@ /** * Router * - * $Id: Router.php 4391 2009-06-04 03:10:12Z zombor $ + * $Id: Router.php 4679 2009-11-10 01:45:52Z isaiah $ * * @package Core * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html + * @copyright (c) 2007-2009 Kohana Team + * @license http://kohanaphp.com/license */ class Router_Core { @@ -38,7 +38,7 @@ class Router_Core { if ( ! empty($_SERVER['QUERY_STRING'])) { // Set the query string to the current query string - Router::$query_string = '?'.trim($_SERVER['QUERY_STRING'], '&/'); + Router::$query_string = '?'.trim(urldecode($_SERVER['QUERY_STRING']), '&/'); } if (Router::$routes === NULL) @@ -53,8 +53,8 @@ class Router_Core { if (Router::$current_uri === '') { // Make sure the default route is set - if ( ! isset(Router::$routes['_default'])) - throw new Kohana_Exception('core.no_default_route'); + 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']; @@ -63,32 +63,34 @@ class Router_Core { $default_route = TRUE; } - // Make sure the URL is not tainted with HTML characters - Router::$current_uri = html::specialchars(Router::$current_uri, FALSE); - // Remove all dot-paths from the URI, they are not valid Router::$current_uri = preg_replace('#\.[\s./]*/#', '', Router::$current_uri); - // At this point segments, rsegments, and current URI are all the same - Router::$segments = Router::$rsegments = Router::$current_uri = trim(Router::$current_uri, '/'); - - // Set the complete URI - Router::$complete_uri = Router::$current_uri.Router::$query_string; + // At this point routed URI and current URI are the same + Router::$routed_uri = Router::$current_uri = trim(Router::$current_uri, '/'); - // Explode the segments by slashes - Router::$segments = ($default_route === TRUE OR Router::$segments === '') ? array() : explode('/', Router::$segments); - - if ($default_route === FALSE AND count(Router::$routes) > 1) + if ($default_route === TRUE) { - // Custom routing - Router::$rsegments = Router::routed_uri(Router::$current_uri); + Router::$complete_uri = Router::$query_string; + Router::$current_uri = ''; + Router::$segments = array(); } + else + { + Router::$complete_uri = Router::$current_uri.Router::$query_string; - // The routed URI is now complete - Router::$routed_uri = Router::$rsegments; + // Explode the segments by slashes + Router::$segments = explode('/', Router::$current_uri); - // Routed segments will never be empty - Router::$rsegments = explode('/', Router::$rsegments); + 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 = ''; @@ -187,7 +189,7 @@ class Router_Core { parse_str($query, $_GET); // Convert $_GET to UTF-8 - $_GET = utf8::clean($_GET); + $_GET = Input::clean($_GET); } } } @@ -202,28 +204,37 @@ class Router_Core { // Remove the URI from $_SERVER['QUERY_STRING'] $_SERVER['QUERY_STRING'] = preg_replace('~\bkohana_uri\b[^&]*+&?~', '', $_SERVER['QUERY_STRING']); } - elseif (isset($_SERVER['PATH_INFO']) AND $_SERVER['PATH_INFO']) - { - Router::$current_uri = $_SERVER['PATH_INFO']; - } - elseif (isset($_SERVER['ORIG_PATH_INFO']) AND $_SERVER['ORIG_PATH_INFO']) - { - Router::$current_uri = $_SERVER['ORIG_PATH_INFO']; - } - elseif (isset($_SERVER['PHP_SELF']) AND $_SERVER['PHP_SELF']) - { - Router::$current_uri = $_SERVER['PHP_SELF']; - } - - if (($strpos_fc = strpos(Router::$current_uri, KOHANA)) !== FALSE) + else { - // Remove the front controller from the current uri - Router::$current_uri = (string) substr(Router::$current_uri, $strpos_fc + strlen(KOHANA)); + 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) diff --git a/system/libraries/Session.php b/system/libraries/Session.php index 51acce00..9764a35c 100644 --- a/system/libraries/Session.php +++ b/system/libraries/Session.php @@ -2,12 +2,12 @@ /** * Session library. * - * $Id: Session.php 4493 2009-07-27 20:05:41Z ixmatus $ + * $Id: Session.php 4679 2009-11-10 01:45:52Z isaiah $ * * @package Core * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html + * @copyright (c) 2007-2009 Kohana Team + * @license http://kohanaphp.com/license */ class Session_Core { @@ -32,18 +32,24 @@ class Session_Core { /** * Singleton instance of Session. + * + * @param string Force a specific session_id */ - public static function instance() + public static function instance($session_id = NULL) { if (Session::$instance == NULL) { // Create a new instance - new Session; + 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. */ @@ -51,8 +57,10 @@ class Session_Core { /** * On first session instance creation, sets up the driver and creates session. + * + * @param string Force a specific session_id */ - protected function __construct() + protected function __construct($session_id = NULL) { $this->input = Input::instance(); @@ -71,7 +79,7 @@ class Session_Core { ini_set('session.gc_maxlifetime', (Session::$config['expiration'] == 0) ? 86400 : Session::$config['expiration']); // Create a new session - $this->create(); + $this->create(NULL, $session_id); if (Session::$config['regenerate'] > 0 AND ($_SESSION['total_hits'] % Session::$config['regenerate']) === 0) { @@ -84,18 +92,15 @@ class Session_Core { cookie::set(Session::$config['name'], $_SESSION['session_id'], Session::$config['expiration']); } - // Close the session just before sending the headers, so that + // Close the session on system shutdown (run before sending the headers), so that // the session cookie(s) can be written. - Event::add('system.send_headers', array($this, 'write_close')); - - // Make sure that sessions are closed before exiting - register_shutdown_function(array($this, 'write_close')); + Event::add('system.shutdown', array($this, 'write_close')); // Singleton instance Session::$instance = $this; } - Kohana::log('debug', 'Session Library initialized'); + Kohana_Log::add('debug', 'Session Library initialized'); } /** @@ -112,9 +117,10 @@ class Session_Core { * 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) + public function create($vars = NULL, $session_id = NULL) { // Destroy any current sessions $this->destroy(); @@ -126,14 +132,16 @@ class Session_Core { // Load the driver if ( ! Kohana::auto_load($driver)) - throw new Kohana_Exception('core.driver_not_found', Session::$config['driver'], get_class($this)); + 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('core.driver_implements', Session::$config['driver'], get_class($this), '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 @@ -149,7 +157,7 @@ class Session_Core { // Validate the session name if ( ! preg_match('~^(?=.*[a-z])[a-z0-9_]++$~iD', Session::$config['name'])) - throw new Kohana_Exception('session.invalid_session_name', Session::$config['name']); + 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']); @@ -164,6 +172,20 @@ class Session_Core { 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(); @@ -176,7 +198,7 @@ class Session_Core { $_SESSION['total_hits'] = 0; $_SESSION['_kf_flash_'] = array(); - $_SESSION['user_agent'] = Kohana::$user_agent; + $_SESSION['user_agent'] = request::user_agent(); $_SESSION['ip_address'] = $this->input->ip_address(); } @@ -196,7 +218,7 @@ class Session_Core { { // Check user agent for consistency case 'user_agent': - if ($_SESSION[$valid] !== Kohana::$user_agent) + if ($_SESSION[$valid] !== request::user_agent()) return $this->create(); break; @@ -253,7 +275,7 @@ class Session_Core { if (isset($_COOKIE[$name])) { // Change the cookie value to match the new session id to prevent "lag" - $_COOKIE[$name] = $_SESSION['session_id']; + cookie::set($name, $_SESSION['session_id']); } } @@ -467,7 +489,7 @@ class Session_Core { * 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() diff --git a/system/libraries/URI.php b/system/libraries/URI.php index d9ccdcf7..4df54fc0 100644 --- a/system/libraries/URI.php +++ b/system/libraries/URI.php @@ -2,12 +2,12 @@ /** * URI library. * - * $Id: URI.php 4072 2009-03-13 17:20:38Z jheathco $ + * $Id: URI.php 4679 2009-11-10 01:45:52Z isaiah $ * * @package Core * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html + * @copyright (c) 2007-2009 Kohana Team + * @license http://kohanaphp.com/license */ class URI_Core extends Router { diff --git a/system/libraries/Validation.php b/system/libraries/Validation.php index 5a48bfc5..f340e63c 100644 --- a/system/libraries/Validation.php +++ b/system/libraries/Validation.php @@ -2,12 +2,12 @@ /** * Validation library. * - * $Id: Validation.php 4120 2009-03-25 19:22:31Z jheathco $ + * $Id: Validation.php 4679 2009-11-10 01:45:52Z isaiah $ * * @package Validation * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html + * @copyright (c) 2007-2009 Kohana Team + * @license http://kohanaphp.com/license */ class Validation_Core extends ArrayObject { @@ -247,7 +247,7 @@ class Validation_Core extends ArrayObject { $name = $callback; } - throw new Kohana_Exception('validation.not_callable', $name); + throw new Kohana_Exception('Callback %name% used for Validation is not callable', array('%name%' => $name)); } return $callback; @@ -349,8 +349,23 @@ class Validation_Core extends ArrayObject { // 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 @@ -361,6 +376,10 @@ class Validation_Core extends ArrayObject { $args = str_replace('\,', ',', $args); } } + else + { + $rule[1] = $rule_name; + } if ($rule === 'is_array') { @@ -372,7 +391,7 @@ class Validation_Core extends ArrayObject { $rule = $this->callback($rule); // Add the rule, with args, to the field - $this->rules[$field][] = array($rule, $args); + $this->rules[$field][] = array($rule, $args, $false_rule); } return $this; @@ -429,36 +448,7 @@ class Validation_Core extends ArrayObject { $object = $this; } - // Get all field names - $fields = $this->field_names(); - - // Copy the array from the object, to optimize multiple sets - $array = $this->getArrayCopy(); - - foreach ($fields as $field) - { - if ($field === '*') - { - // Ignore wildcard - continue; - } - - if ( ! isset($array[$field])) - { - if (isset($this->array_fields[$field])) - { - // This field must be an array - $array[$field] = array(); - } - else - { - $array[$field] = NULL; - } - } - } - - // Swap the array back into the object - $this->exchangeArray($array); + $array = $this->safe_array(); // Get all defined field names $fields = array_keys($array); @@ -471,12 +461,12 @@ class Validation_Core extends ArrayObject { { foreach ($fields as $f) { - $this[$f] = is_array($this[$f]) ? array_map($callback, $this[$f]) : call_user_func($callback, $this[$f]); + $array[$f] = is_array($array[$f]) ? array_map($callback, $array[$f]) : call_user_func($callback, $array[$f]); } } else { - $this[$field] = is_array($this[$field]) ? array_map($callback, $this[$field]) : call_user_func($callback, $this[$field]); + $array[$field] = is_array($array[$field]) ? array_map($callback, $array[$field]) : call_user_func($callback, $array[$field]); } } } @@ -488,8 +478,8 @@ class Validation_Core extends ArrayObject { { foreach ($callbacks as $callback) { - // Separate the callback and arguments - list ($callback, $args) = $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; @@ -508,31 +498,20 @@ class Validation_Core extends ArrayObject { continue; } - if (empty($this[$f]) AND ! in_array($rule, $this->empty_rules)) + if (empty($array[$f]) AND ! in_array($rule, $this->empty_rules)) { // This rule does not need to be processed on empty fields continue; } - if ($args === NULL) - { - if ( ! call_user_func($callback, $this[$f])) - { - $this->errors[$f] = $rule; + $result = ($args === NULL) ? call_user_func($callback, $array[$f]) : call_user_func($callback, $array[$f], $args); - // Stop validating this field when an error is found - continue; - } - } - else + if (($result == $is_false)) { - if ( ! call_user_func($callback, $this[$f], $args)) - { - $this->errors[$f] = $rule; + $this->add_error($f, $rule, $args); - // Stop validating this field when an error is found - continue; - } + // Stop validating this field when an error is found + continue; } } } @@ -544,31 +523,21 @@ class Validation_Core extends ArrayObject { break; } - if ( ! in_array($rule, $this->empty_rules) AND ! $this->required($this[$field])) + if ( ! in_array($rule, $this->empty_rules) AND ! $this->required($array[$field])) { // This rule does not need to be processed on empty fields continue; } - if ($args === NULL) - { - if ( ! call_user_func($callback, $this[$field])) - { - $this->errors[$field] = $rule; + // Results of our test + $result = ($args === NULL) ? call_user_func($callback, $array[$field]) : call_user_func($callback, $array[$field], $args); - // Stop validating this field when an error is found - break; - } - } - else + if (($result == $is_false)) { - if ( ! call_user_func($callback, $this[$field], $args)) - { - $this->errors[$field] = $rule; + $this->add_error($field, $rule, $args); - // Stop validating this field when an error is found - break; - } + // Stop validating this field when an error is found + break; } } } @@ -616,16 +585,19 @@ class Validation_Core extends ArrayObject { { foreach ($fields as $f) { - $this[$f] = is_array($this[$f]) ? array_map($callback, $this[$f]) : call_user_func($callback, $this[$f]); + $array[$f] = is_array($array[$f]) ? array_map($callback, $array[$f]) : call_user_func($callback, $array[$f]); } } else { - $this[$field] = is_array($this[$field]) ? array_map($callback, $this[$field]) : call_user_func($callback, $this[$field]); + $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(); } @@ -636,11 +608,12 @@ class Validation_Core extends ArrayObject { * @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) + public function add_error($field, $name, $args = NULL) { - $this->errors[$field] = $name; + $this->errors[$field] = array($name, $args); return $this; } @@ -688,14 +661,19 @@ class Validation_Core extends ArrayObject { /** * Return the errors array. * - * @param boolean load errors from a lang file + * @param boolean load errors from a message file * @return array */ public function errors($file = NULL) { if ($file === NULL) { - return $this->errors; + $errors = array(); + foreach($this->errors as $field => $error) + { + $errors[$field] = $error[0]; + } + return $errors; } else { @@ -704,12 +682,12 @@ class Validation_Core extends ArrayObject { foreach ($this->errors as $input => $error) { // Key for this input error - $key = "$file.$input.$error"; + $key = "$file.$input.$error[0]"; - if (($errors[$input] = Kohana::lang($key)) === $key) + if (($errors[$input] = Kohana::message('validation/'.$key, $error[1])) === $key) { // Get the default error message - $errors[$input] = Kohana::lang("$file.$input.default"); + $errors[$input] = Kohana::message("$file.$input.default"); } } @@ -772,7 +750,7 @@ class Validation_Core extends ArrayObject { if ( ! is_string($str)) return FALSE; - $size = utf8::strlen($str); + $size = mb_strlen($str); $status = FALSE; if (count($length) > 1) @@ -823,4 +801,4 @@ class Validation_Core extends ArrayObject { return ! preg_match('![^'.implode('', $chars).']!u', $value); } -} // End Validation +} // End Validation \ No newline at end of file diff --git a/system/libraries/View.php b/system/libraries/View.php index 2b8471c6..d7e5e774 100644 --- a/system/libraries/View.php +++ b/system/libraries/View.php @@ -3,12 +3,12 @@ * Loads and displays Kohana view files. Can also handle output of some binary * files, such as image, Javascript, and CSS files. * - * $Id: View.php 4072 2009-03-13 17:20:38Z jheathco $ + * $Id: View.php 4679 2009-11-10 01:45:52Z isaiah $ * * @package Core * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html + * @copyright (c) 2007-2009 Kohana Team + * @license http://kohanaphp.com/license */ class View_Core { @@ -18,7 +18,6 @@ class View_Core { // View variable storage protected $kohana_local_data = array(); - protected static $kohana_global_data = array(); /** * Creates a new View using the given parameters. @@ -88,7 +87,7 @@ class View_Core { { // Check if the filetype is allowed by the configuration if ( ! in_array($type, Kohana::config('view.allowed_filetypes'))) - throw new Kohana_Exception('core.invalid_filetype', $type); + 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); @@ -152,13 +151,13 @@ class View_Core { foreach ($key as $property) { // Set the result to an associative array - $result[$property] = (array_key_exists($property, $this->kohana_local_data) OR array_key_exists($property, View::$kohana_global_data)) ? TRUE : FALSE; + $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) OR array_key_exists($key, View::$kohana_global_data)) ? TRUE : FALSE; + $result = (array_key_exists($key, $this->kohana_local_data)) ? TRUE : FALSE; } // Return the result @@ -179,28 +178,6 @@ class View_Core { return $this; } - /** - * Sets a view global variable. - * - * @param string|array name of variable or an array of variables - * @param mixed value when using a named variable - * @return void - */ - public static function set_global($name, $value = NULL) - { - if (is_array($name)) - { - foreach ($name as $key => $value) - { - View::$kohana_global_data[$key] = $value; - } - } - else - { - View::$kohana_global_data[$name] = $value; - } - } - /** * Magically sets a view variable. * @@ -223,13 +200,18 @@ class View_Core { public function &__get($key) { if (isset($this->kohana_local_data[$key])) + { return $this->kohana_local_data[$key]; - - if (isset(View::$kohana_global_data[$key])) - return View::$kohana_global_data[$key]; - - if (isset($this->$key)) + } + elseif (isset($this->$key)) + { return $this->$key; + } + else + { + throw new Kohana_Exception('Undefined view variable: :var', + array(':var' => $key)); + } } /** @@ -245,8 +227,8 @@ class View_Core { } catch (Exception $e) { - // Display the exception using its internal __toString method - return (string) $e; + Kohana_Exception::handle($e); + return (string) ''; } } @@ -255,21 +237,27 @@ class View_Core { * * @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) + public function render($print = FALSE, $renderer = FALSE, $modifier = FALSE) { if (empty($this->kohana_filename)) - throw new Kohana_Exception('core.view_set_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 = array_merge(View::$kohana_global_data, $this->kohana_local_data); + $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); + } - // Load the view in the controller for access to $this - $output = Kohana::$instance->_kohana_load_view($this->kohana_filename, $data); + $output = $this->load_view($this->kohana_filename, $data); if ($renderer !== FALSE AND is_callable($renderer, TRUE)) { @@ -306,4 +294,36 @@ class View_Core { return $output; } -} // End View \ No newline at end of file + + /** + * 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 index 7c5e3c31..97415096 100644 --- a/system/libraries/drivers/Cache.php +++ b/system/libraries/drivers/Cache.php @@ -1,40 +1,42 @@ config = $config; + $this->config['directory'] = str_replace('\\', '/', realpath($this->config['directory'])).'/'; - // Make sure the cache directory is writable - if ( ! is_dir($directory) OR ! is_writable($directory)) - throw new Kohana_Exception('cache.unwritable', $directory); - - // Directory is valid - $this->directory = $directory; + if ( ! is_dir($this->config['directory']) OR ! is_writable($this->config['directory'])) + throw new Cache_Exception('The configured cache directory, :directory:, is not writable.', array(':directory:' => $this->config['directory'])); } /** * Finds an array of files matching the given id or tag. * - * @param string cache id or tag + * @param string cache key or tag * @param bool search for tags * @return array of filenames matching the id or tag */ - public function exists($id, $tag = FALSE) + public function exists($keys, $tag = FALSE) { - if ($id === TRUE) + if ($keys === TRUE) { // Find all the files - return glob($this->directory.'*~*~*'); + return glob($this->config['directory'].'*~*~*'); } elseif ($tag === TRUE) { // Find all the files that have the tag name - $paths = glob($this->directory.'*~*'.$id.'*~*'); + $paths = array(); + + foreach ( (array) $keys as $tag) + { + $paths = array_merge($paths, glob($this->config['directory'].'*~*'.$tag.'*~*')); + } // Find all tags matching the given tag $files = array(); + foreach ($paths as $path) { // Split the files @@ -60,12 +59,16 @@ class Cache_File_Driver implements Cache_Driver { continue; // Split the tags by plus signs, used to separate tags - $tags = explode('+', $tags[1]); + $item_tags = explode('+', $tags[1]); - if (in_array($tag, $tags)) + // Check each supplied tag, and match aginst the tags on each item. + foreach ($keys as $tag) { - // Add the file to the array, it has the requested tag - $files[] = $path; + if (in_array($tag, $item_tags)) + { + // Add the file to the array, it has the requested tag + $files[] = $path; + } } } @@ -73,98 +76,68 @@ class Cache_File_Driver implements Cache_Driver { } else { - // Find the file matching the given id - return glob($this->directory.$id.'~*'); + $paths = array(); + + foreach ( (array) $keys as $key) + { + // Find the file matching the given key + $paths = array_merge($paths, glob($this->config['directory'].str_replace(array('/', '\\', ' '), '_', $key).'~*')); + } + + return $paths; } } - /** - * Sets a cache item to the given data, tags, and lifetime. - * - * @param string cache id to set - * @param string data in the cache - * @param array cache tags - * @param integer lifetime - * @return bool - */ - public function set($id, $data, array $tags = NULL, $lifetime) + public function set($items, $tags = NULL, $lifetime = NULL) { - // Remove old cache files - $this->delete($id); - - // Cache File driver expects unix timestamp if ($lifetime !== 0) { + // File driver expects unix timestamp $lifetime += time(); } - if ( ! empty($tags)) + + if ( ! is_null($tags) AND ! empty($tags)) { // Convert the tags into a string list - $tags = implode('+', $tags); + $tags = implode('+', (array) $tags); } - // Write out a serialized cache - return (bool) file_put_contents($this->directory.$id.'~'.$tags.'~'.$lifetime, serialize($data)); - } + $success = TRUE; - /** - * Finds an array of ids for a given tag. - * - * @param string tag name - * @return array of ids that match the tag - */ - public function find($tag) - { - // An array will always be returned - $result = array(); - - if ($paths = $this->exists($tag, TRUE)) + foreach ($items as $key => $value) { - // Length of directory name - $offset = strlen($this->directory); + if (is_resource($value)) + throw new Cache_Exception('Caching of resources is impossible, because resources cannot be serialised.'); - // Find all the files with the given tag - foreach ($paths as $path) - { - // Get the id from the filename - list($id, $junk) = explode('~', basename($path), 2); + // Remove old cache file + $this->delete($key); - if (($data = $this->get($id)) !== FALSE) - { - // Add the result to the array - $result[$id] = $data; - } + if ( ! (bool) file_put_contents($this->config['directory'].str_replace(array('/', '\\', ' '), '_', $key).'~'.$tags.'~'.$lifetime, serialize($value))) + { + $success = FALSE; } } - return $result; + return $success; } - /** - * Fetches a cache item. This will delete the item if it is expired or if - * the hash does not match the stored hash. - * - * @param string cache id - * @return mixed|NULL - */ - public function get($id) + public function get($keys, $single = FALSE) { - if ($file = $this->exists($id)) + $items = array(); + + if ($files = $this->exists($keys)) { - // Use the first file - $file = current($file); + // Turn off errors while reading the files + $ER = error_reporting(0); - // Validate that the cache has not expired - if ($this->expired($file)) - { - // Remove this cache, it has expired - $this->delete($id); - } - else + foreach ($files as $file) { - // Turn off errors while reading the file - $ER = error_reporting(0); + // Validate that the item has not expired + if ($this->expired($file)) + continue; + + list($key, $junk) = explode('~', basename($file), 2); if (($data = file_get_contents($file)) !== FALSE) { @@ -173,80 +146,102 @@ class Cache_File_Driver implements Cache_Driver { } else { - // Delete the data - unset($data); + $data = NULL; } - // Turn errors back on - error_reporting($ER); + $items[$key] = $data; } + + // Turn errors back on + error_reporting($ER); } - // Return NULL if there is no data - return isset($data) ? $data : NULL; + // Reutrn a single item if only one key was requested + if ($single) + { + return (count($items) > 0) ? current($items) : NULL; + } + else + { + return $items; + } } /** - * Deletes a cache item by id or tag - * - * @param string cache id or tag, or TRUE for "all items" - * @param boolean use tags - * @return boolean + * Get cache items by tag + */ + public function get_tag($tags) + { + // An array will always be returned + $result = array(); + + if ($paths = $this->exists($tags, TRUE)) + { + // Find all the files with the given tag + foreach ($paths as $path) + { + // Get the id from the filename + list($key, $junk) = explode('~', basename($path), 2); + + if (($data = $this->get($key)) !== FALSE) + { + // Add the result to the array + $result[$key] = $data; + } + } + } + + return $result; + } + + /** + * Delete cache items by keys or tags */ - public function delete($id, $tag = FALSE) + public function delete($keys, $tag = FALSE) { - $files = $this->exists($id, $tag); + $success = TRUE; - if (empty($files)) - return FALSE; + $paths = $this->exists($keys, $tag); // Disable all error reporting while deleting $ER = error_reporting(0); - foreach ($files as $file) + foreach ($paths as $path) { // Remove the cache file - if ( ! unlink($file)) - Kohana::log('error', 'Cache: Unable to delete cache file: '.$file); + if ( ! unlink($path)) + { + Kohana::log('error', 'Cache: Unable to delete cache file: '.$path); + $success = FALSE; + } } // Turn on error reporting again error_reporting($ER); - return TRUE; + return $success; } /** - * Deletes all cache files that are older than the current time. - * - * @return void + * Delete cache items by tag */ - public function delete_expired() + public function delete_tag($tags) { - if ($files = $this->exists(TRUE)) - { - // Disable all error reporting while deleting - $ER = error_reporting(0); - - foreach ($files as $file) - { - if ($this->expired($file)) - { - // The cache file has already expired, delete it - if ( ! unlink($file)) - Kohana::log('error', 'Cache: Unable to delete cache file: '.$file); - } - } + return $this->delete($tags, TRUE); + } - // Turn on error reporting again - error_reporting($ER); - } + /** + * Empty the cache + */ + public function delete_all() + { + return $this->delete(TRUE); } /** * Check if a cache file has expired by filename. * - * @param string filename + * @param string|array array of filenames * @return bool */ protected function expired($file) @@ -257,5 +252,4 @@ class Cache_File_Driver implements Cache_Driver { // Expirations of 0 are "never expire" return ($expires !== 0 AND $expires <= time()); } - -} // End Cache File Driver \ No newline at end of file +} // End Cache Memcache Driver diff --git a/system/libraries/drivers/Cache/Memcache.php b/system/libraries/drivers/Cache/Memcache.php index d801de9c..636191d4 100644 --- a/system/libraries/drivers/Cache/Memcache.php +++ b/system/libraries/drivers/Cache/Memcache.php @@ -2,190 +2,128 @@ /** * Memcache-based Cache driver. * - * $Id: Memcache.php 4102 2009-03-19 12:55:54Z Shadowhand $ + * $Id$ * * @package Cache * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html + * @copyright (c) 2007-2009 Kohana Team + * @license http://kohanaphp.com/license */ -class Cache_Memcache_Driver implements Cache_Driver { - - const TAGS_KEY = 'memcache_tags_array'; - - // Cache backend object and flags +class Cache_Memcache_Driver extends Cache_Driver { + protected $config; protected $backend; protected $flags; - // Tags array - protected static $tags; - - // Have the tags been changed? - protected static $tags_changed = FALSE; - - public function __construct() + public function __construct($config) { if ( ! extension_loaded('memcache')) - throw new Kohana_Exception('cache.extension_not_loaded', 'memcache'); + throw new Kohana_Exception('The memcache PHP extension must be loaded to use this driver.'); + ini_set('memcache.allow_failover', (isset($config['allow_failover']) AND $config['allow_failover']) ? TRUE : FALSE); + + $this->config = $config; $this->backend = new Memcache; - $this->flags = Kohana::config('cache_memcache.compression') ? MEMCACHE_COMPRESSED : FALSE; - $servers = Kohana::config('cache_memcache.servers'); + $this->flags = (isset($config['compression']) AND $config['compression']) ? MEMCACHE_COMPRESSED : FALSE; - foreach ($servers as $server) + foreach ($config['servers'] as $server) { // Make sure all required keys are set - $server += array('host' => '127.0.0.1', 'port' => 11211, 'persistent' => FALSE); + $server += array('host' => '127.0.0.1', + 'port' => 11211, + 'persistent' => FALSE, + 'weight' => 1, + 'timeout' => 1, + 'retry_interval' => 15 + ); // Add the server to the pool - $this->backend->addServer($server['host'], $server['port'], (bool) $server['persistent']) - or Kohana::log('error', 'Cache: Connection failed: '.$server['host']); - } - - // Load tags - self::$tags = $this->backend->get(self::TAGS_KEY); - - if ( ! is_array(self::$tags)) - { - // Create a new tags array - self::$tags = array(); - - // Tags have been created - self::$tags_changed = TRUE; + $this->backend->addServer($server['host'], $server['port'], (bool) $server['persistent'], (int) $server['weight'], (int) $server['timeout'], (int) $server['retry_interval'], TRUE, array($this,'_memcache_failure_callback')); } } - public function __destruct() + public function _memcache_failure_callback($host, $port) { - if (self::$tags_changed === TRUE) - { - // Save the tags - $this->backend->set(self::TAGS_KEY, self::$tags, $this->flags, 0); - - // Tags are now unchanged - self::$tags_changed = FALSE; - } + $this->backend->setServerParams($host, $port, 1, -1, FALSE); + Kohana_Log::add('error', __('Cache: Memcache server down: :host:::port:',array(':host:' => $host,':port:' => $port))); } - public function find($tag) + public function set($items, $tags = NULL, $lifetime = NULL) { - if (isset(self::$tags[$tag]) AND $results = $this->backend->get(self::$tags[$tag])) - { - // Return all the found caches - return $results; - } - else + if ($lifetime !== 0) { - // No matching tags - return array(); + // Memcache driver expects unix timestamp + $lifetime += time(); } - } - public function get($id) - { - return (($return = $this->backend->get($id)) === FALSE) ? NULL : $return; - } + if ($tags !== NULL) + throw new Cache_Exception('Memcache driver does not support tags'); - public function set($id, $data, array $tags = NULL, $lifetime) - { - if ( ! empty($tags)) + foreach ($items as $key => $value) { - // Tags will be changed - self::$tags_changed = TRUE; + if (is_resource($value)) + throw new Cache_Exception('Caching of resources is impossible, because resources cannot be serialised.'); - foreach ($tags as $tag) + if ( ! $this->backend->set($key, $value, $this->flags, $lifetime)) { - // Add the id to each tag - self::$tags[$tag][$id] = $id; + return FALSE; } } - if ($lifetime !== 0) - { - // Memcache driver expects unix timestamp - $lifetime += time(); - } - - // Set a new value - return $this->backend->set($id, $data, $this->flags, $lifetime); + return TRUE; } - public function delete($id, $tag = FALSE) + public function get($keys, $single = FALSE) { - // Tags will be changed - self::$tags_changed = TRUE; + $items = $this->backend->get($keys); - if ($id === TRUE) + if ($single) { - if ($status = $this->backend->flush()) - { - // Remove all tags, all items have been deleted - self::$tags = array(); - - // We must sleep after flushing, or overwriting will not work! - // @see http://php.net/manual/en/function.memcache-flush.php#81420 - sleep(1); - } - - return $status; - } - elseif ($tag === TRUE) - { - if (isset(self::$tags[$id])) - { - foreach (self::$tags[$id] as $_id) - { - // Delete each id in the tag - $this->backend->delete($_id); - } - - // Delete the tag - unset(self::$tags[$id]); - } - - return TRUE; + return ($items === FALSE OR count($items) > 0) ? current($items) : NULL; } else { - foreach (self::$tags as $tag => $_ids) - { - if (isset(self::$tags[$tag][$id])) - { - // Remove the id from the tags - unset(self::$tags[$tag][$id]); - } - } - - return $this->backend->delete($id); + return ($items === FALSE) ? array() : $items; } } - public function delete_expired() + /** + * Get cache items by tag + */ + public function get_tag($tags) { - // Tags will be changed - self::$tags_changed = TRUE; + throw new Cache_Exception('Memcache driver does not support tags'); + } - foreach (self::$tags as $tag => $_ids) + /** + * Delete cache item by key + */ + public function delete($keys) + { + foreach ($keys as $key) { - foreach ($_ids as $id) + if ( ! $this->backend->delete($key)) { - if ( ! $this->backend->get($id)) - { - // This id has disappeared, delete it from the tags - unset(self::$tags[$tag][$id]); - } - } - - if (empty(self::$tags[$tag])) - { - // The tag no longer has any valid ids - unset(self::$tags[$tag]); + return FALSE; } } - // Memcache handles garbage collection internally return TRUE; } + /** + * Delete cache items by tag + */ + public function delete_tag($tags) + { + throw new Cache_Exception('Memcache driver does not support tags'); + } + + /** + * Empty the cache + */ + public function delete_all() + { + return $this->backend->flush(); + } } // End Cache Memcache Driver diff --git a/system/libraries/drivers/Cache/Sqlite.php b/system/libraries/drivers/Cache/Sqlite.php deleted file mode 100644 index 9458d851..00000000 --- a/system/libraries/drivers/Cache/Sqlite.php +++ /dev/null @@ -1,257 +0,0 @@ -db = new SQLiteDatabase($filename, '0666', $error); - - // Throw an exception if there's an error - if ( ! empty($error)) - throw new Kohana_Exception('cache.driver_error', sqlite_error_string($error)); - - $query = "SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'caches'"; - $tables = $this->db->query($query, SQLITE_BOTH, $error); - - // Throw an exception if there's an error - if ( ! empty($error)) - throw new Kohana_Exception('cache.driver_error', sqlite_error_string($error)); - - if ($tables->numRows() == 0) - { - Kohana::log('error', 'Cache: Initializing new SQLite cache database'); - - // Issue a CREATE TABLE command - $this->db->unbufferedQuery(Kohana::config('cache_sqlite.schema')); - } - } - - /** - * Checks if a cache id is already set. - * - * @param string cache id - * @return boolean - */ - public function exists($id) - { - // Find the id that matches - $query = "SELECT id FROM caches WHERE id = '$id'"; - - return ($this->db->query($query)->numRows() > 0); - } - - /** - * Sets a cache item to the given data, tags, and lifetime. - * - * @param string cache id to set - * @param string data in the cache - * @param array cache tags - * @param integer lifetime - * @return bool - */ - public function set($id, $data, array $tags = NULL, $lifetime) - { - // Serialize and escape the data - $data = sqlite_escape_string(serialize($data)); - - if ( ! empty($tags)) - { - // Escape the tags, adding brackets so the tag can be explicitly matched - $tags = sqlite_escape_string('<'.implode('>,<', $tags).'>'); - } - - // Cache Sqlite driver expects unix timestamp - if ($lifetime !== 0) - { - $lifetime += time(); - } - - $query = $this->exists($id) - ? "UPDATE caches SET tags = '$tags', expiration = '$lifetime', cache = '$data' WHERE id = '$id'" - : "INSERT INTO caches VALUES('$id', '$tags', '$lifetime', '$data')"; - - // Run the query - $this->db->unbufferedQuery($query, SQLITE_BOTH, $error); - - if ( ! empty($error)) - { - self::log_error($error); - return FALSE; - } - else - { - return TRUE; - } - } - - /** - * Finds an array of ids for a given tag. - * - * @param string tag name - * @return array of ids that match the tag - */ - public function find($tag) - { - $query = "SELECT id,cache FROM caches WHERE tags LIKE '%<{$tag}>%'"; - $query = $this->db->query($query, SQLITE_BOTH, $error); - - // An array will always be returned - $result = array(); - - if ( ! empty($error)) - { - self::log_error($error); - } - elseif ($query->numRows() > 0) - { - // Disable notices for unserializing - $ER = error_reporting(~E_NOTICE); - - while ($row = $query->fetchObject()) - { - // Add each cache to the array - $result[$row->id] = unserialize($row->cache); - } - - // Turn notices back on - error_reporting($ER); - } - - return $result; - } - - /** - * Fetches a cache item. This will delete the item if it is expired or if - * the hash does not match the stored hash. - * - * @param string cache id - * @return mixed|NULL - */ - public function get($id) - { - $query = "SELECT id, expiration, cache FROM caches WHERE id = '$id' LIMIT 0, 1"; - $query = $this->db->query($query, SQLITE_BOTH, $error); - - if ( ! empty($error)) - { - self::log_error($error); - } - elseif ($cache = $query->fetchObject()) - { - // Make sure the expiration is valid and that the hash matches - if ($cache->expiration != 0 AND $cache->expiration <= time()) - { - // Cache is not valid, delete it now - $this->delete($cache->id); - } - else - { - // Disable notices for unserializing - $ER = error_reporting(~E_NOTICE); - - // Return the valid cache data - $data = $cache->cache; - - // Turn notices back on - error_reporting($ER); - } - } - - // No valid cache found - return NULL; - } - - /** - * Deletes a cache item by id or tag - * - * @param string cache id or tag, or TRUE for "all items" - * @param bool delete a tag - * @return bool - */ - public function delete($id, $tag = FALSE) - { - if ($id === TRUE) - { - // Delete all caches - $where = '1'; - } - elseif ($tag === TRUE) - { - // Delete by tag - $where = "tags LIKE '%<{$id}>%'"; - } - else - { - // Delete by id - $where = "id = '$id'"; - } - - $this->db->unbufferedQuery('DELETE FROM caches WHERE '.$where, SQLITE_BOTH, $error); - - if ( ! empty($error)) - { - self::log_error($error); - return FALSE; - } - else - { - return TRUE; - } - } - - /** - * Deletes all cache files that are older than the current time. - */ - public function delete_expired() - { - // Delete all expired caches - $query = 'DELETE FROM caches WHERE expiration != 0 AND expiration <= '.time(); - - $this->db->unbufferedQuery($query); - - return TRUE; - } - -} // End Cache SQLite Driver \ No newline at end of file diff --git a/system/libraries/drivers/Cache/Xcache.php b/system/libraries/drivers/Cache/Xcache.php index 6254bbb6..ad11e6d7 100644 --- a/system/libraries/drivers/Cache/Xcache.php +++ b/system/libraries/drivers/Cache/Xcache.php @@ -1,85 +1,128 @@ config = $config; } - public function set($id, $data, array $tags = NULL, $lifetime) + public function set($items, $tags = NULL, $lifetime = NULL) { - if ( ! empty($tags)) + if ($tags !== NULL) { - Kohana::log('error', 'Cache: tags are unsupported by the Xcache driver'); + Kohana_Log::add('debug', __('Cache: XCache driver does not support tags')); } - return xcache_set($id, $data, $lifetime); - } + foreach ($items as $key => $value) + { + if (is_resource($value)) + throw new Cache_Exception('Caching of resources is impossible, because resources cannot be serialised.'); - public function find($tag) - { - Kohana::log('error', 'Cache: tags are unsupported by the Xcache driver'); - return FALSE; + if ( ! xcache_set($key, $value, $lifetime)) + { + return FALSE; + } + } + + return TRUE; } - public function delete($id, $tag = FALSE) + public function get($keys, $single = FALSE) { - if ($tag !== FALSE) - { - Kohana::log('error', 'Cache: tags are unsupported by the Xcache driver'); - return TRUE; - } - elseif ($id !== TRUE) + $items = array(); + + foreach ($keys as $key) { if (xcache_isset($id)) - return xcache_unset($id); + { + $items[$key] = xcache_get($id); + } + else + { + $items[$key] = NULL; + } + } - return FALSE; + if ($single) + { + return ($items === FALSE OR count($items) > 0) ? current($items) : NULL; } else { - // Do the login - $this->auth(); - $result = TRUE; - for ($i = 0, $max = xcache_count(XC_TYPE_VAR); $i < $max; $i++) + return ($items === FALSE) ? array() : $items; + } + } + + /** + * Get cache items by tag + */ + public function get_tag($tags) + { + Kohana_Log::add('debug', __('Cache: XCache driver does not support tags')); + return NULL; + } + + /** + * Delete cache item by key + */ + public function delete($keys) + { + foreach ($keys as $key) + { + if ( ! xcache_unset($key)) { - if (xcache_clear_cache(XC_TYPE_VAR, $i) !== NULL) - { - $result = FALSE; - break; - } + return FALSE; } - - // Undo the login - $this->auth(TRUE); - return $result; } return TRUE; } - public function delete_expired() + /** + * Delete cache items by tag + */ + public function delete_tag($tags) { - return TRUE; + Kohana_Log::add('debug', __('Cache: XCache driver does not support tags')); + return NULL; + } + + /** + * Empty the cache + */ + public function delete_all() + { + $this->auth(); + $result = TRUE; + + for ($i = 0, $max = xcache_count(XC_TYPE_VAR); $i < $max; $i++) + { + if (xcache_clear_cache(XC_TYPE_VAR, $i) !== NULL) + { + $result = FALSE; + break; + } + } + + // Undo the login + $this->auth(TRUE); + + return $result; } private function auth($reverse = FALSE) @@ -111,9 +154,8 @@ class Cache_Xcache_Driver implements Cache_Driver { $backup[$key] = $value; } - $_SERVER[$key] = Kohana::config('cache_xcache.'.$key); + $_SERVER[$key] = $this->config->{$key}; } } } - -} // End Cache Xcache Driver +} // End Cache XCache Driver diff --git a/system/libraries/drivers/Captcha.php b/system/libraries/drivers/Captcha.php deleted file mode 100644 index a4343e19..00000000 --- a/system/libraries/drivers/Captcha.php +++ /dev/null @@ -1,227 +0,0 @@ -response = $this->generate_challenge(); - - // Store the correct Captcha response in a session - Event::add('system.post_controller', array($this, 'update_response_session')); - } - - /** - * Generate a new Captcha challenge. - * - * @return string the challenge answer - */ - abstract public function generate_challenge(); - - /** - * Output the Captcha challenge. - * - * @param boolean html output - * @return mixed the rendered Captcha (e.g. an image, riddle, etc.) - */ - abstract public function render($html); - - /** - * Stores the response for the current Captcha challenge in a session so it is available - * on the next page load for Captcha::valid(). This method is called after controller - * execution (in the system.post_controller event) in order not to overwrite itself too soon. - * - * @return void - */ - public function update_response_session() - { - Session::instance()->set('captcha_response', sha1(strtoupper($this->response))); - } - - /** - * Validates a Captcha response from a user. - * - * @param string captcha response - * @return boolean - */ - public function valid($response) - { - return (sha1(strtoupper($response)) === Session::instance()->get('captcha_response')); - } - - /** - * Returns the image type. - * - * @param string filename - * @return string|FALSE image type ("png", "gif" or "jpeg") - */ - public function image_type($filename) - { - switch (strtolower(substr(strrchr($filename, '.'), 1))) - { - case 'png': - return 'png'; - - case 'gif': - return 'gif'; - - case 'jpg': - case 'jpeg': - // Return "jpeg" and not "jpg" because of the GD2 function names - return 'jpeg'; - - default: - return FALSE; - } - } - - /** - * Creates an image resource with the dimensions specified in config. - * If a background image is supplied, the image dimensions are used. - * - * @throws Kohana_Exception if no GD2 support - * @param string path to the background image file - * @return void - */ - public function image_create($background = NULL) - { - // Check for GD2 support - if ( ! function_exists('imagegd2')) - throw new Kohana_Exception('captcha.requires_GD2'); - - // Create a new image (black) - $this->image = imagecreatetruecolor(Captcha::$config['width'], Captcha::$config['height']); - - // Use a background image - if ( ! empty($background)) - { - // Create the image using the right function for the filetype - $function = 'imagecreatefrom'.$this->image_type($background); - $this->background_image = $function($background); - - // Resize the image if needed - if (imagesx($this->background_image) !== Captcha::$config['width'] - OR imagesy($this->background_image) !== Captcha::$config['height']) - { - imagecopyresampled - ( - $this->image, $this->background_image, 0, 0, 0, 0, - Captcha::$config['width'], Captcha::$config['height'], - imagesx($this->background_image), imagesy($this->background_image) - ); - } - - // Free up resources - imagedestroy($this->background_image); - } - } - - /** - * Fills the background with a gradient. - * - * @param resource gd image color identifier for start color - * @param resource gd image color identifier for end color - * @param string direction: 'horizontal' or 'vertical', 'random' by default - * @return void - */ - public function image_gradient($color1, $color2, $direction = NULL) - { - $directions = array('horizontal', 'vertical'); - - // Pick a random direction if needed - if ( ! in_array($direction, $directions)) - { - $direction = $directions[array_rand($directions)]; - - // Switch colors - if (mt_rand(0, 1) === 1) - { - $temp = $color1; - $color1 = $color2; - $color2 = $temp; - } - } - - // Extract RGB values - $color1 = imagecolorsforindex($this->image, $color1); - $color2 = imagecolorsforindex($this->image, $color2); - - // Preparations for the gradient loop - $steps = ($direction === 'horizontal') ? Captcha::$config['width'] : Captcha::$config['height']; - - $r1 = ($color1['red'] - $color2['red']) / $steps; - $g1 = ($color1['green'] - $color2['green']) / $steps; - $b1 = ($color1['blue'] - $color2['blue']) / $steps; - - if ($direction === 'horizontal') - { - $x1 =& $i; - $y1 = 0; - $x2 =& $i; - $y2 = Captcha::$config['height']; - } - else - { - $x1 = 0; - $y1 =& $i; - $x2 = Captcha::$config['width']; - $y2 =& $i; - } - - // Execute the gradient loop - for ($i = 0; $i <= $steps; $i++) - { - $r2 = $color1['red'] - floor($i * $r1); - $g2 = $color1['green'] - floor($i * $g1); - $b2 = $color1['blue'] - floor($i * $b1); - $color = imagecolorallocate($this->image, $r2, $g2, $b2); - - imageline($this->image, $x1, $y1, $x2, $y2, $color); - } - } - - /** - * Returns the img html element or outputs the image to the browser. - * - * @param boolean html output - * @return mixed html string or void - */ - public function image_render($html) - { - // Output html element - if ($html) - return 'Captcha'; - - // Send the correct HTTP header - header('Content-Type: image/'.$this->image_type); - - // Pick the correct output function - $function = 'image'.$this->image_type; - $function($this->image); - - // Free up resources - imagedestroy($this->image); - } - -} // End Captcha Driver \ No newline at end of file diff --git a/system/libraries/drivers/Captcha/Alpha.php b/system/libraries/drivers/Captcha/Alpha.php deleted file mode 100644 index 27795804..00000000 --- a/system/libraries/drivers/Captcha/Alpha.php +++ /dev/null @@ -1,92 +0,0 @@ -image - $this->image_create(Captcha::$config['background']); - - // Add a random gradient - if (empty(Captcha::$config['background'])) - { - $color1 = imagecolorallocate($this->image, mt_rand(0, 100), mt_rand(0, 100), mt_rand(0, 100)); - $color2 = imagecolorallocate($this->image, mt_rand(0, 100), mt_rand(0, 100), mt_rand(0, 100)); - $this->image_gradient($color1, $color2); - } - - // Add a few random circles - for ($i = 0, $count = mt_rand(10, Captcha::$config['complexity'] * 3); $i < $count; $i++) - { - $color = imagecolorallocatealpha($this->image, mt_rand(0, 255), mt_rand(0, 255), mt_rand(0, 255), mt_rand(80, 120)); - $size = mt_rand(5, Captcha::$config['height'] / 3); - imagefilledellipse($this->image, mt_rand(0, Captcha::$config['width']), mt_rand(0, Captcha::$config['height']), $size, $size, $color); - } - - // Calculate character font-size and spacing - $default_size = min(Captcha::$config['width'], Captcha::$config['height'] * 2) / strlen($this->response); - $spacing = (int) (Captcha::$config['width'] * 0.9 / strlen($this->response)); - - // Background alphabetic character attributes - $color_limit = mt_rand(96, 160); - $chars = 'ABEFGJKLPQRTVY'; - - // Draw each Captcha character with varying attributes - for ($i = 0, $strlen = strlen($this->response); $i < $strlen; $i++) - { - // Use different fonts if available - $font = Captcha::$config['fontpath'].Captcha::$config['fonts'][array_rand(Captcha::$config['fonts'])]; - - $angle = mt_rand(-40, 20); - // Scale the character size on image height - $size = $default_size / 10 * mt_rand(8, 12); - $box = imageftbbox($size, $angle, $font, $this->response[$i]); - - // Calculate character starting coordinates - $x = $spacing / 4 + $i * $spacing; - $y = Captcha::$config['height'] / 2 + ($box[2] - $box[5]) / 4; - - // Draw captcha text character - // Allocate random color, size and rotation attributes to text - $color = imagecolorallocate($this->image, mt_rand(150, 255), mt_rand(200, 255), mt_rand(0, 255)); - - // Write text character to image - imagefttext($this->image, $size, $angle, $x, $y, $color, $font, $this->response[$i]); - - // Draw "ghost" alphabetic character - $text_color = imagecolorallocatealpha($this->image, mt_rand($color_limit + 8, 255), mt_rand($color_limit + 8, 255), mt_rand($color_limit + 8, 255), mt_rand(70, 120)); - $char = $chars[mt_rand(0, 14)]; - imagettftext($this->image, $size * 2, mt_rand(-45, 45), ($x - (mt_rand(5, 10))), ($y + (mt_rand(5, 10))), $text_color, $font, $char); - } - - // Output - return $this->image_render($html); - } - -} // End Captcha Alpha Driver Class \ No newline at end of file diff --git a/system/libraries/drivers/Captcha/Basic.php b/system/libraries/drivers/Captcha/Basic.php deleted file mode 100644 index d212e72c..00000000 --- a/system/libraries/drivers/Captcha/Basic.php +++ /dev/null @@ -1,81 +0,0 @@ -image - $this->image_create(Captcha::$config['background']); - - // Add a random gradient - if (empty(Captcha::$config['background'])) - { - $color1 = imagecolorallocate($this->image, mt_rand(200, 255), mt_rand(200, 255), mt_rand(150, 255)); - $color2 = imagecolorallocate($this->image, mt_rand(200, 255), mt_rand(200, 255), mt_rand(150, 255)); - $this->image_gradient($color1, $color2); - } - - // Add a few random lines - for ($i = 0, $count = mt_rand(5, Captcha::$config['complexity'] * 4); $i < $count; $i++) - { - $color = imagecolorallocatealpha($this->image, mt_rand(0, 255), mt_rand(0, 255), mt_rand(100, 255), mt_rand(50, 120)); - imageline($this->image, mt_rand(0, Captcha::$config['width']), 0, mt_rand(0, Captcha::$config['width']), Captcha::$config['height'], $color); - } - - // Calculate character font-size and spacing - $default_size = min(Captcha::$config['width'], Captcha::$config['height'] * 2) / (strlen($this->response) + 1); - $spacing = (int) (Captcha::$config['width'] * 0.9 / strlen($this->response)); - - // Draw each Captcha character with varying attributes - for ($i = 0, $strlen = strlen($this->response); $i < $strlen; $i++) - { - // Use different fonts if available - $font = Captcha::$config['fontpath'].Captcha::$config['fonts'][array_rand(Captcha::$config['fonts'])]; - - // Allocate random color, size and rotation attributes to text - $color = imagecolorallocate($this->image, mt_rand(0, 150), mt_rand(0, 150), mt_rand(0, 150)); - $angle = mt_rand(-40, 20); - - // Scale the character size on image height - $size = $default_size / 10 * mt_rand(8, 12); - $box = imageftbbox($size, $angle, $font, $this->response[$i]); - - // Calculate character starting coordinates - $x = $spacing / 4 + $i * $spacing; - $y = Captcha::$config['height'] / 2 + ($box[2] - $box[5]) / 4; - - // Write text character to image - imagefttext($this->image, $size, $angle, $x, $y, $color, $font, $this->response[$i]); - } - - // Output - return $this->image_render($html); - } - -} // End Captcha Basic Driver Class \ No newline at end of file diff --git a/system/libraries/drivers/Captcha/Black.php b/system/libraries/drivers/Captcha/Black.php deleted file mode 100644 index 6a2e2226..00000000 --- a/system/libraries/drivers/Captcha/Black.php +++ /dev/null @@ -1,72 +0,0 @@ -image_create(Captcha::$config['background']); - - // Add random white/gray arcs, amount depends on complexity setting - $count = (Captcha::$config['width'] + Captcha::$config['height']) / 2; - $count = $count / 5 * min(10, Captcha::$config['complexity']); - for ($i = 0; $i < $count; $i++) - { - imagesetthickness($this->image, mt_rand(1, 2)); - $color = imagecolorallocatealpha($this->image, 255, 255, 255, mt_rand(0, 120)); - imagearc($this->image, mt_rand(-Captcha::$config['width'], Captcha::$config['width']), mt_rand(-Captcha::$config['height'], Captcha::$config['height']), mt_rand(-Captcha::$config['width'], Captcha::$config['width']), mt_rand(-Captcha::$config['height'], Captcha::$config['height']), mt_rand(0, 360), mt_rand(0, 360), $color); - } - - // Use different fonts if available - $font = Captcha::$config['fontpath'].Captcha::$config['fonts'][array_rand(Captcha::$config['fonts'])]; - - // Draw the character's white shadows - $size = (int) min(Captcha::$config['height'] / 2, Captcha::$config['width'] * 0.8 / strlen($this->response)); - $angle = mt_rand(-15 + strlen($this->response), 15 - strlen($this->response)); - $x = mt_rand(1, Captcha::$config['width'] * 0.9 - $size * strlen($this->response)); - $y = ((Captcha::$config['height'] - $size) / 2) + $size; - $color = imagecolorallocate($this->image, 255, 255, 255); - imagefttext($this->image, $size, $angle, $x + 1, $y + 1, $color, $font, $this->response); - - // Add more shadows for lower complexities - (Captcha::$config['complexity'] < 10) and imagefttext($this->image, $size, $angle, $x - 1, $y - 1, $color, $font , $this->response); - (Captcha::$config['complexity'] < 8) and imagefttext($this->image, $size, $angle, $x - 2, $y + 2, $color, $font , $this->response); - (Captcha::$config['complexity'] < 6) and imagefttext($this->image, $size, $angle, $x + 2, $y - 2, $color, $font , $this->response); - (Captcha::$config['complexity'] < 4) and imagefttext($this->image, $size, $angle, $x + 3, $y + 3, $color, $font , $this->response); - (Captcha::$config['complexity'] < 2) and imagefttext($this->image, $size, $angle, $x - 3, $y - 3, $color, $font , $this->response); - - // Finally draw the foreground characters - $color = imagecolorallocate($this->image, 0, 0, 0); - imagefttext($this->image, $size, $angle, $x, $y, $color, $font, $this->response); - - // Output - return $this->image_render($html); - } - -} // End Captcha Black Driver Class \ No newline at end of file diff --git a/system/libraries/drivers/Captcha/Math.php b/system/libraries/drivers/Captcha/Math.php deleted file mode 100644 index 4ac20248..00000000 --- a/system/libraries/drivers/Captcha/Math.php +++ /dev/null @@ -1,61 +0,0 @@ -math_exercice = implode(' + ', $numbers).' = '; - - // Return the answer - return array_sum($numbers); - } - - /** - * Outputs the Captcha riddle. - * - * @param boolean html output - * @return mixed - */ - public function render($html) - { - return $this->math_exercice; - } - -} // End Captcha Math Driver Class \ No newline at end of file diff --git a/system/libraries/drivers/Captcha/Riddle.php b/system/libraries/drivers/Captcha/Riddle.php deleted file mode 100644 index 765eeaad..00000000 --- a/system/libraries/drivers/Captcha/Riddle.php +++ /dev/null @@ -1,47 +0,0 @@ -riddle = $riddle[0]; - - // Return the answer - return $riddle[1]; - } - - /** - * Outputs the Captcha riddle. - * - * @param boolean html output - * @return mixed - */ - public function render($html) - { - return $this->riddle; - } - -} // End Captcha Riddle Driver Class \ No newline at end of file diff --git a/system/libraries/drivers/Captcha/Word.php b/system/libraries/drivers/Captcha/Word.php deleted file mode 100644 index 856bd9b4..00000000 --- a/system/libraries/drivers/Captcha/Word.php +++ /dev/null @@ -1,37 +0,0 @@ -cache_lifetime = $cache_setting; + // Restore the cached configuration + $this->config = $this->load_cache(); + + if (count($this->config) > 0) + $this->loaded = TRUE; + + // Add the save cache method to system.shutshut event + Event::add('system.shutdown', array($this, 'save_cache')); + } + + } + + /** + * Gets a value from config. If required is TRUE + * then get will throw an exception if value cannot + * be loaded. + * + * @param string key the setting to get + * @param bool slash remove trailing slashes + * @param bool required is setting required? + * @return mixed + * @access public + */ + public function get($key, $slash = FALSE, $required = FALSE) + { + // Get the group name from the key + $group = explode('.', $key, 2); + $group = $group[0]; + + // Check for existing value and load it dynamically if required + if ( ! isset($this->config[$group])) + $this->config[$group] = $this->load($group, $required); + + // Get the value of the key string + $value = Kohana::key_string($this->config, $key); + + if ($slash === TRUE AND is_string($value) AND $value !== '') + { + // Force the value to end with "/" + $value = rtrim($value, '/').'/'; + } + + if (($required === TRUE) AND ($value === null)) + throw new Kohana_Config_Exception('Value not found in config driver'); + + $this->loaded = TRUE; + return $value; + } + + /** + * Sets a new value to the configuration + * + * @param string key + * @param mixed value + * @return bool + * @access public + */ + public function set($key, $value) + { + // Do this to make sure that the config array is already loaded + $this->get($key); + + if (substr($key, 0, 7) === 'routes.') + { + // Routes cannot contain sub keys due to possible dots in regex + $keys = explode('.', $key, 2); + } + else + { + // Convert dot-noted key string to an array + $keys = explode('.', $key); + } + + // Used for recursion + $conf =& $this->config; + $last = count($keys) - 1; + + foreach ($keys as $i => $k) + { + if ($i === $last) + { + $conf[$k] = $value; + } + else + { + $conf =& $conf[$k]; + } + } + + if (substr($key,0,12) === 'core.modules') + { + // Reprocess the include paths + Kohana::include_paths(TRUE); + } + + // Set config to changed + return $this->changed = TRUE; + } + + /** + * Clear the configuration + * + * @param string group + * @return bool + * @access public + */ + public function clear($group) + { + // Remove the group from config + unset($this->config[$group]); + + // Set config to changed + return $this->changed = TRUE; + } + + /** + * Checks whether the setting exists in + * config + * + * @param string $key + * @return bool + * @access public + */ + public function setting_exists($key) + { + return $this->get($key) === NULL; + } + + /** + * Loads a configuration group based on the setting + * + * @param string group + * @param bool required + * @return array + * @access public + * @abstract + */ + abstract public function load($group, $required = FALSE); + + /** + * Loads the cached version of this configuration driver + * + * @return array + * @access public + */ + public function load_cache() + { + // Load the cache for this configuration + $cached_config = Kohana::cache($this->cache_name, $this->cache_lifetime); + + // If the configuration wasn't loaded from the cache + if ($cached_config === NULL) + $cached_config = array(); + + // Return the cached config + return $cached_config; + } + + /** + * Saves a cached version of this configuration driver + * + * @return bool + * @access public + */ + public function save_cache() + { + // If this configuration has changed + if ($this->get('core.internal_cache') !== FALSE AND $this->changed) + { + $data = $this->config; + + // Save the cache + return Kohana::cache_save($this->cache_name, $data, $this->cache_lifetime); + } + + return TRUE; + } +} // End Kohana_Config_Driver \ No newline at end of file diff --git a/system/libraries/drivers/Config/Array.php b/system/libraries/drivers/Config/Array.php new file mode 100644 index 00000000..b2ca19ba --- /dev/null +++ b/system/libraries/drivers/Config/Array.php @@ -0,0 +1,83 @@ +escape_table($table).' WHERE '.implode(' ', $where); - } - - /** - * Builds an UPDATE query. - * - * @param string table name - * @param array key => value pairs - * @param array where clause - * @return string - */ - public function update($table, $values, $where) - { - foreach ($values as $key => $val) - { - $valstr[] = $this->escape_column($key).' = '.$val; - } - return 'UPDATE '.$this->escape_table($table).' SET '.implode(', ', $valstr).' WHERE '.implode(' ',$where); - } - - /** - * Set the charset using 'SET NAMES '. - * - * @param string character set to use - */ - public function set_charset($charset) - { - throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__); - } - - /** - * Wrap the tablename in backticks, has support for: table.field syntax. - * - * @param string table name - * @return string - */ - abstract public function escape_table($table); - - /** - * Escape a column/field name, has support for special commands. - * - * @param string column name - * @return string - */ - abstract public function escape_column($column); - - /** - * Builds a WHERE portion of a query. - * - * @param mixed key - * @param string value - * @param string type - * @param int number of where clauses - * @param boolean escape the value - * @return string - */ - public function where($key, $value, $type, $num_wheres, $quote) - { - $prefix = ($num_wheres == 0) ? '' : $type; - - if ($quote === -1) - { - $value = ''; - } - else - { - if ($value === NULL) - { - if ( ! $this->has_operator($key)) - { - $key .= ' IS'; - } - - $value = ' NULL'; - } - elseif (is_bool($value)) - { - if ( ! $this->has_operator($key)) - { - $key .= ' ='; - } - - $value = ($value == TRUE) ? ' TRUE' : ' FALSE'; - } - else - { - if ( ! $this->has_operator($key) AND ! empty($key)) - { - $key = $this->escape_column($key).' ='; - } - else - { - preg_match('/^(.+?)([<>!=]+|\bIS(?:\s+NULL))\s*$/i', $key, $matches); - if (isset($matches[1]) AND isset($matches[2])) - { - $key = $this->escape_column(trim($matches[1])).' '.trim($matches[2]); - } - } - - $value = ' '.(($quote == TRUE) ? $this->escape($value) : $value); - } - } - - return $prefix.$key.$value; - } - - /** - * Builds a LIKE portion of a query. - * - * @param mixed field name - * @param string value to match with field - * @param boolean add wildcards before and after the match - * @param string clause type (AND or OR) - * @param int number of likes - * @return string - */ - public function like($field, $match, $auto, $type, $num_likes) - { - $prefix = ($num_likes == 0) ? '' : $type; - - $match = $this->escape_str($match); - - if ($auto === TRUE) - { - // Add the start and end quotes - $match = '%'.str_replace('%', '\\%', $match).'%'; - } - - return $prefix.' '.$this->escape_column($field).' LIKE \''.$match . '\''; - } - - /** - * Builds a NOT LIKE portion of a query. - * - * @param mixed field name - * @param string value to match with field - * @param string clause type (AND or OR) - * @param int number of likes - * @return string - */ - public function notlike($field, $match, $auto, $type, $num_likes) - { - $prefix = ($num_likes == 0) ? '' : $type; - - $match = $this->escape_str($match); - - if ($auto === TRUE) - { - // Add the start and end quotes - $match = '%'.$match.'%'; - } - - return $prefix.' '.$this->escape_column($field).' NOT LIKE \''.$match.'\''; - } - - /** - * Builds a REGEX portion of a query. - * - * @param string field name - * @param string value to match with field - * @param string clause type (AND or OR) - * @param integer number of regexes - * @return string - */ - public function regex($field, $match, $type, $num_regexs) - { - throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__); - } - - /** - * Builds a NOT REGEX portion of a query. - * - * @param string field name - * @param string value to match with field - * @param string clause type (AND or OR) - * @param integer number of regexes - * @return string - */ - public function notregex($field, $match, $type, $num_regexs) - { - throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__); - } - - /** - * Builds an INSERT query. - * - * @param string table name - * @param array keys - * @param array values - * @return string - */ - public function insert($table, $keys, $values) - { - // Escape the column names - foreach ($keys as $key => $value) - { - $keys[$key] = $this->escape_column($value); - } - return 'INSERT INTO '.$this->escape_table($table).' ('.implode(', ', $keys).') VALUES ('.implode(', ', $values).')'; - } - - /** - * Builds a MERGE portion of a query. - * - * @param string table name - * @param array keys - * @param array values - * @return string - */ - public function merge($table, $keys, $values) - { - throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__); - } - - /** - * Builds a LIMIT portion of a query. - * - * @param integer limit - * @param integer offset - * @return string - */ - abstract public function limit($limit, $offset = 0); - - /** - * Creates a prepared statement. - * - * @param string SQL query - * @return Database_Stmt - */ - public function stmt_prepare($sql = '') - { - throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__); - } - - /** - * Compiles the SELECT statement. - * Generates a query string based on which functions were used. - * Should not be called directly, the get() function calls it. - * - * @param array select query values - * @return string - */ - abstract public function compile_select($database); - - /** - * Determines if the string has an arithmetic operator in it. - * - * @param string string to check - * @return boolean - */ - public function has_operator($str) - { - return (bool) preg_match('/[<>!=]|\sIS(?:\s+NOT\s+)?\b|BETWEEN/i', trim($str)); - } - - /** - * Escapes any input value. - * - * @param mixed value to escape - * @return string - */ - public function escape($value) - { - if ( ! $this->db_config['escape']) - return $value; - - switch (gettype($value)) - { - case 'string': - $value = '\''.$this->escape_str($value).'\''; - break; - case 'boolean': - $value = ($value == TRUE) ? 'TRUE' : 'FALSE'; - break; - case 'double': - // Convert to non-locale aware float to prevent possible commas - $value = sprintf('%F', $value); - break; - default: - $value = ($value === NULL) ? 'NULL' : $value; - break; - } - - return (string) $value; - } - - /** - * Escapes a string for a query. - * - * @param mixed value to escape - * @return string - */ - abstract public function escape_str($str); - - /** - * Lists all tables in the database. - * - * @return array - */ - abstract public function list_tables(); - - /** - * Lists all fields in a table. - * - * @param string table name - * @return array - */ - abstract function list_fields($table); - - /** - * Returns the last database error. - * - * @return string - */ - abstract public function show_error(); - - /** - * Returns field data about a table. - * - * @param string table name - * @return array - */ - abstract public function field_data($table); - - /** - * Fetches SQL type information about a field, in a generic format. - * - * @param string field datatype - * @return array - */ - protected function sql_type($str) - { - static $sql_types; - - if ($sql_types === NULL) - { - // Load SQL data types - $sql_types = Kohana::config('sql_types'); - } - - $str = strtolower(trim($str)); - - if (($open = strpos($str, '(')) !== FALSE) - { - // Find closing bracket - $close = strpos($str, ')', $open) - 1; - - // Find the type without the size - $type = substr($str, 0, $open); - } - else - { - // No length - $type = $str; - } - - empty($sql_types[$type]) and exit - ( - 'Unknown field type: '.$type.'. '. - 'Please report this: http://trac.kohanaphp.com/newticket' - ); - - // Fetch the field definition - $field = $sql_types[$type]; - - switch ($field['type']) - { - case 'string': - case 'float': - if (isset($close)) - { - // Add the length to the field info - $field['length'] = substr($str, $open + 1, $close - $open); - } - break; - case 'int': - // Add unsigned value - $field['unsigned'] = (strpos($str, 'unsigned') !== FALSE); - break; - } - - return $field; - } - - /** - * Clears the internal query cache. - * - * @param string SQL query - */ - public function clear_cache($sql = NULL) - { - if (empty($sql)) - { - $this->query_cache = array(); - } - else - { - unset($this->query_cache[$this->query_hash($sql)]); - } - - Kohana::log('debug', 'Database cache cleared: '.get_class($this)); - } - - /** - * Creates a hash for an SQL query string. Replaces newlines with spaces, - * trims, and hashes. - * - * @param string SQL query - * @return string - */ - protected function query_hash($sql) - { - return sha1(str_replace("\n", ' ', trim($sql))); - } - -} // End Database Driver Interface - -/** - * Database_Result - * - */ -abstract class Database_Result implements ArrayAccess, Iterator, Countable { - - // Result resource, insert id, and SQL - protected $result; - protected $insert_id; - protected $sql; - - // Current and total rows - protected $current_row = 0; - protected $total_rows = 0; - - // Fetch function and return type - protected $fetch_type; - protected $return_type; - - /** - * Returns the SQL used to fetch the result. - * - * @return string - */ - public function sql() - { - return $this->sql; - } - - /** - * Returns the insert id from the result. - * - * @return mixed - */ - public function insert_id() - { - return $this->insert_id; - } - - /** - * Prepares the query result. - * - * @param boolean return rows as objects - * @param mixed type - * @return Database_Result - */ - abstract function result($object = TRUE, $type = FALSE); - - /** - * Builds an array of query results. - * - * @param boolean return rows as objects - * @param mixed type - * @return array - */ - abstract function result_array($object = NULL, $type = FALSE); - - /** - * Gets the fields of an already run query. - * - * @return array - */ - abstract public function list_fields(); - - /** - * Seek to an offset in the results. - * - * @return boolean - */ - abstract public function seek($offset); - - /** - * Countable: count - */ - public function count() - { - return $this->total_rows; - } - - /** - * ArrayAccess: offsetExists - */ - public function offsetExists($offset) - { - if ($this->total_rows > 0) - { - $min = 0; - $max = $this->total_rows - 1; - - return ! ($offset < $min OR $offset > $max); - } - - return FALSE; - } - - /** - * ArrayAccess: offsetGet - */ - public function offsetGet($offset) - { - if ( ! $this->seek($offset)) - return FALSE; - - // Return the row by calling the defined fetching callback - return call_user_func($this->fetch_type, $this->result, $this->return_type); - } - - /** - * ArrayAccess: offsetSet - * - * @throws Kohana_Database_Exception - */ - final public function offsetSet($offset, $value) - { - throw new Kohana_Database_Exception('database.result_read_only'); - } - - /** - * ArrayAccess: offsetUnset - * - * @throws Kohana_Database_Exception - */ - final public function offsetUnset($offset) - { - throw new Kohana_Database_Exception('database.result_read_only'); - } - - /** - * Iterator: current - */ - public function current() - { - return $this->offsetGet($this->current_row); - } - - /** - * Iterator: key - */ - public function key() - { - return $this->current_row; - } - - /** - * Iterator: next - */ - public function next() - { - ++$this->current_row; - return $this; - } - - /** - * Iterator: prev - */ - public function prev() - { - --$this->current_row; - return $this; - } - - /** - * Iterator: rewind - */ - public function rewind() - { - $this->current_row = 0; - return $this; - } - - /** - * Iterator: valid - */ - public function valid() - { - return $this->offsetExists($this->current_row); - } - -} // End Database Result Interface diff --git a/system/libraries/drivers/Database/Mssql.php b/system/libraries/drivers/Database/Mssql.php deleted file mode 100644 index 8b5ed50b..00000000 --- a/system/libraries/drivers/Database/Mssql.php +++ /dev/null @@ -1,462 +0,0 @@ -db_config = $config; - - Kohana::log('debug', 'MSSQL Database Driver Initialized'); - } - - /** - * Closes the database connection. - */ - public function __destruct() - { - is_resource($this->link) and mssql_close($this->link); - } - - /** - * Make the connection - * - * @return return connection - */ - public function connect() - { - // Check if link already exists - if (is_resource($this->link)) - return $this->link; - - // Import the connect variables - extract($this->db_config['connection']); - - // Persistent connections enabled? - $connect = ($this->db_config['persistent'] == TRUE) ? 'mssql_pconnect' : 'mssql_connect'; - - // Build the connection info - $host = isset($host) ? $host : $socket; - - // Windows uses a comma instead of a colon - $port = (isset($port) AND is_string($port)) ? (KOHANA_IS_WIN ? ',' : ':').$port : ''; - - // Make the connection and select the database - if (($this->link = $connect($host.$port, $user, $pass, TRUE)) AND mssql_select_db($database, $this->link)) - { - /* This is being removed so I can use it, will need to come up with a more elegant workaround in the future... - * - if ($charset = $this->db_config['character_set']) - { - $this->set_charset($charset); - } - */ - - // Clear password after successful connect - $this->db_config['connection']['pass'] = NULL; - - return $this->link; - } - - return FALSE; - } - - public function query($sql) - { - // Only cache if it's turned on, and only cache if it's not a write statement - if ($this->db_config['cache'] AND ! preg_match('#\b(?:INSERT|UPDATE|REPLACE|SET)\b#i', $sql)) - { - $hash = $this->query_hash($sql); - - if ( ! isset($this->query_cache[$hash])) - { - // Set the cached object - $this->query_cache[$hash] = new Mssql_Result(mssql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql); - } - else - { - // Rewind cached result - $this->query_cache[$hash]->rewind(); - } - - // Return the cached query - return $this->query_cache[$hash]; - } - - return new Mssql_Result(mssql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql); - } - - public function escape_table($table) - { - if (stripos($table, ' AS ') !== FALSE) - { - // Force 'AS' to uppercase - $table = str_ireplace(' AS ', ' AS ', $table); - - // Runs escape_table on both sides of an AS statement - $table = array_map(array($this, __FUNCTION__), explode(' AS ', $table)); - - // Re-create the AS statement - return implode(' AS ', $table); - } - return '['.str_replace('.', '[.]', $table).']'; - } - - public function escape_column($column) - { - if (!$this->db_config['escape']) - return $column; - - if ($column == '*') - return $column; - - // This matches any functions we support to SELECT. - if ( preg_match('/(avg|count|sum|max|min)\(\s*(.*)\s*\)(\s*as\s*(.+)?)?/i', $column, $matches)) - { - if ( count($matches) == 3) - { - return $matches[1].'('.$this->escape_column($matches[2]).')'; - } - else if ( count($matches) == 5) - { - return $matches[1].'('.$this->escape_column($matches[2]).') AS '.$this->escape_column($matches[2]); - } - } - - // This matches any modifiers we support to SELECT. - if ( ! preg_match('/\b(?:rand|all|distinct(?:row)?|high_priority|sql_(?:small_result|b(?:ig_result|uffer_result)|no_cache|ca(?:che|lc_found_rows)))\s/i', $column)) - { - if (stripos($column, ' AS ') !== FALSE) - { - // Force 'AS' to uppercase - $column = str_ireplace(' AS ', ' AS ', $column); - - // Runs escape_column on both sides of an AS statement - $column = array_map(array($this, __FUNCTION__), explode(' AS ', $column)); - - // Re-create the AS statement - return implode(' AS ', $column); - } - - return preg_replace('/[^.*]+/', '[$0]', $column); - } - - $parts = explode(' ', $column); - $column = ''; - - for ($i = 0, $c = count($parts); $i < $c; $i++) - { - // The column is always last - if ($i == ($c - 1)) - { - $column .= preg_replace('/[^.*]+/', '[$0]', $parts[$i]); - } - else // otherwise, it's a modifier - { - $column .= $parts[$i].' '; - } - } - return $column; - } - - /** - * Limit in SQL Server 2000 only uses the keyword - * 'TOP'; 2007 may have an offset keyword, but - * I am unsure - for pagination style limit,offset - * functionality, a fancy query needs to be built. - * - * @param unknown_type $limit - * @return unknown - */ - public function limit($limit, $offset=null) - { - return 'TOP '.$limit; - } - - public function compile_select($database) - { - $sql = ($database['distinct'] == TRUE) ? 'SELECT DISTINCT ' : 'SELECT '; - $sql .= (count($database['select']) > 0) ? implode(', ', $database['select']) : '*'; - - if (count($database['from']) > 0) - { - // Escape the tables - $froms = array(); - foreach ($database['from'] as $from) - $froms[] = $this->escape_column($from); - $sql .= "\nFROM "; - $sql .= implode(', ', $froms); - } - - if (count($database['join']) > 0) - { - foreach($database['join'] AS $join) - { - $sql .= "\n".$join['type'].'JOIN '.implode(', ', $join['tables']).' ON '.$join['conditions']; - } - } - - if (count($database['where']) > 0) - { - $sql .= "\nWHERE "; - } - - $sql .= implode("\n", $database['where']); - - if (count($database['groupby']) > 0) - { - $sql .= "\nGROUP BY "; - $sql .= implode(', ', $database['groupby']); - } - - if (count($database['having']) > 0) - { - $sql .= "\nHAVING "; - $sql .= implode("\n", $database['having']); - } - - if (count($database['orderby']) > 0) - { - $sql .= "\nORDER BY "; - $sql .= implode(', ', $database['orderby']); - } - - if (is_numeric($database['limit'])) - { - $sql .= "\n"; - $sql .= $this->limit($database['limit']); - } - - return $sql; - } - - public function escape_str($str) - { - if (!$this->db_config['escape']) - return $str; - - is_resource($this->link) or $this->connect(); - //mssql_real_escape_string($str, $this->link); <-- this function doesn't exist - - $characters = array('/\x00/', '/\x1a/', '/\n/', '/\r/', '/\\\/', '/\'/'); - $replace = array('\\\x00', '\\x1a', '\\n', '\\r', '\\\\', "''"); - return preg_replace($characters, $replace, $str); - } - - public function list_tables() - { - $sql = 'SHOW TABLES FROM ['.$this->db_config['connection']['database'].']'; - $result = $this->query($sql)->result(FALSE, MSSQL_ASSOC); - - $retval = array(); - foreach ($result as $row) - { - $retval[] = current($row); - } - - return $retval; - } - - public function show_error() - { - return mssql_get_last_message($this->link); - } - - public function list_fields($table) - { - $result = array(); - - foreach ($this->field_data($table) as $row) - { - // Make an associative array - $result[$row->Field] = $this->sql_type($row->Type); - } - - return $result; - } - - public function field_data($table) - { - $query = $this->query("SELECT COLUMN_NAME AS Field, DATA_TYPE as Type FROM INFORMATION_SCHEMA.Columns WHERE TABLE_NAME = '".$this->escape_table($table)."'", $this->link); - - return $query->result_array(TRUE); - } -} - -/** - * MSSQL Result - */ -class Mssql_Result extends Database_Result { - - // Fetch function and return type - protected $fetch_type = 'mssql_fetch_object'; - protected $return_type = MSSQL_ASSOC; - - /** - * Sets up the result variables. - * - * @param resource query result - * @param resource database link - * @param boolean return objects or arrays - * @param string SQL query that was run - */ - public function __construct($result, $link, $object = TRUE, $sql) - { - $this->result = $result; - - // If the query is a resource, it was a SELECT, SHOW, DESCRIBE, EXPLAIN query - if (is_resource($result)) - { - $this->current_row = 0; - $this->total_rows = mssql_num_rows($this->result); - $this->fetch_type = ($object === TRUE) ? 'mssql_fetch_object' : 'mssql_fetch_array'; - } - elseif (is_bool($result)) - { - if ($result == FALSE) - { - // SQL error - throw new Kohana_Database_Exception('database.error', mssql_get_last_message($link).' - '.$sql); - } - else - { - // Its an DELETE, INSERT, REPLACE, or UPDATE querys - $last_id = mssql_query('SELECT @@IDENTITY AS last_id', $link); - $result = mssql_fetch_assoc($last_id); - $this->insert_id = $result['last_id']; - $this->total_rows = mssql_rows_affected($link); - } - } - - // Set result type - $this->result($object); - - // Store the SQL - $this->sql = $sql; - } - - /** - * Destruct, the cleanup crew! - */ - public function __destruct() - { - if (is_resource($this->result)) - { - mssql_free_result($this->result); - } - } - - public function result($object = TRUE, $type = MSSQL_ASSOC) - { - $this->fetch_type = ((bool) $object) ? 'mssql_fetch_object' : 'mssql_fetch_array'; - - // This check has to be outside the previous statement, because we do not - // know the state of fetch_type when $object = NULL - // NOTE - The class set by $type must be defined before fetching the result, - // autoloading is disabled to save a lot of stupid overhead. - if ($this->fetch_type == 'mssql_fetch_object') - { - $this->return_type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass'; - } - else - { - $this->return_type = $type; - } - - return $this; - } - - public function as_array($object = NULL, $type = MSSQL_ASSOC) - { - return $this->result_array($object, $type); - } - - public function result_array($object = NULL, $type = MSSQL_ASSOC) - { - $rows = array(); - - if (is_string($object)) - { - $fetch = $object; - } - elseif (is_bool($object)) - { - if ($object === TRUE) - { - $fetch = 'mssql_fetch_object'; - - // NOTE - The class set by $type must be defined before fetching the result, - // autoloading is disabled to save a lot of stupid overhead. - $type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass'; - } - else - { - $fetch = 'mssql_fetch_array'; - } - } - else - { - // Use the default config values - $fetch = $this->fetch_type; - - if ($fetch == 'mssql_fetch_object') - { - $type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass'; - } - } - - if (mssql_num_rows($this->result)) - { - // Reset the pointer location to make sure things work properly - mssql_data_seek($this->result, 0); - - while ($row = $fetch($this->result, $type)) - { - $rows[] = $row; - } - } - - return isset($rows) ? $rows : array(); - } - - public function list_fields() - { - $field_names = array(); - while ($field = mssql_fetch_field($this->result)) - { - $field_names[] = $field->name; - } - - return $field_names; - } - - public function seek($offset) - { - if ( ! $this->offsetExists($offset)) - return FALSE; - - return mssql_data_seek($this->result, $offset); - } - -} // End mssql_Result Class diff --git a/system/libraries/drivers/Database/Mysql.php b/system/libraries/drivers/Database/Mysql.php deleted file mode 100644 index d5222f50..00000000 --- a/system/libraries/drivers/Database/Mysql.php +++ /dev/null @@ -1,496 +0,0 @@ -db_config = $config; - - Kohana::log('debug', 'MySQL Database Driver Initialized'); - } - - /** - * Closes the database connection. - */ - public function __destruct() - { - is_resource($this->link) and mysql_close($this->link); - } - - public function connect() - { - // Check if link already exists - if (is_resource($this->link)) - return $this->link; - - // Import the connect variables - extract($this->db_config['connection']); - - // Persistent connections enabled? - $connect = ($this->db_config['persistent'] == TRUE) ? 'mysql_pconnect' : 'mysql_connect'; - - // Build the connection info - $host = isset($host) ? $host : $socket; - $port = isset($port) ? ':'.$port : ''; - - // Make the connection and select the database - if (($this->link = $connect($host.$port, $user, $pass, TRUE)) AND mysql_select_db($database, $this->link)) - { - if ($charset = $this->db_config['character_set']) - { - $this->set_charset($charset); - } - - // Clear password after successful connect - $this->db_config['connection']['pass'] = NULL; - - return $this->link; - } - - return FALSE; - } - - public function query($sql) - { - // Only cache if it's turned on, and only cache if it's not a write statement - if ($this->db_config['cache'] AND ! preg_match('#\b(?:INSERT|UPDATE|REPLACE|SET|DELETE|TRUNCATE)\b#i', $sql)) - { - $hash = $this->query_hash($sql); - - if ( ! isset($this->query_cache[$hash])) - { - // Set the cached object - $this->query_cache[$hash] = new Mysql_Result(mysql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql); - } - else - { - // Rewind cached result - $this->query_cache[$hash]->rewind(); - } - - // Return the cached query - return $this->query_cache[$hash]; - } - - return new Mysql_Result(mysql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql); - } - - public function set_charset($charset) - { - $this->query('SET NAMES '.$this->escape_str($charset)); - } - - public function escape_table($table) - { - if (!$this->db_config['escape']) - return $table; - - if (stripos($table, ' AS ') !== FALSE) - { - // Force 'AS' to uppercase - $table = str_ireplace(' AS ', ' AS ', $table); - - // Runs escape_table on both sides of an AS statement - $table = array_map(array($this, __FUNCTION__), explode(' AS ', $table)); - - // Re-create the AS statement - return implode(' AS ', $table); - } - return '`'.str_replace('.', '`.`', $table).'`'; - } - - public function escape_column($column) - { - if (!$this->db_config['escape']) - return $column; - - if ($column == '*') - return $column; - - // This matches any functions we support to SELECT. - if ( preg_match('/(avg|count|sum|max|min)\(\s*(.*)\s*\)(\s*as\s*(.+)?)?/i', $column, $matches)) - { - if ( count($matches) == 3) - { - return $matches[1].'('.$this->escape_column($matches[2]).')'; - } - else if ( count($matches) == 5) - { - return $matches[1].'('.$this->escape_column($matches[2]).') AS '.$this->escape_column($matches[2]); - } - } - - // This matches any modifiers we support to SELECT. - if ( ! preg_match('/\b(?:rand|all|distinct(?:row)?|high_priority|sql_(?:small_result|b(?:ig_result|uffer_result)|no_cache|ca(?:che|lc_found_rows)))\s/i', $column)) - { - if (stripos($column, ' AS ') !== FALSE) - { - // Force 'AS' to uppercase - $column = str_ireplace(' AS ', ' AS ', $column); - - // Runs escape_column on both sides of an AS statement - $column = array_map(array($this, __FUNCTION__), explode(' AS ', $column)); - - // Re-create the AS statement - return implode(' AS ', $column); - } - - return preg_replace('/[^.*]+/', '`$0`', $column); - } - - $parts = explode(' ', $column); - $column = ''; - - for ($i = 0, $c = count($parts); $i < $c; $i++) - { - // The column is always last - if ($i == ($c - 1)) - { - $column .= preg_replace('/[^.*]+/', '`$0`', $parts[$i]); - } - else // otherwise, it's a modifier - { - $column .= $parts[$i].' '; - } - } - return $column; - } - - public function regex($field, $match, $type, $num_regexs) - { - $prefix = ($num_regexs == 0) ? '' : $type; - - return $prefix.' '.$this->escape_column($field).' REGEXP \''.$this->escape_str($match).'\''; - } - - public function notregex($field, $match, $type, $num_regexs) - { - $prefix = $num_regexs == 0 ? '' : $type; - - return $prefix.' '.$this->escape_column($field).' NOT REGEXP \''.$this->escape_str($match) . '\''; - } - - public function merge($table, $keys, $values) - { - // Escape the column names - foreach ($keys as $key => $value) - { - $keys[$key] = $this->escape_column($value); - } - return 'REPLACE INTO '.$this->escape_table($table).' ('.implode(', ', $keys).') VALUES ('.implode(', ', $values).')'; - } - - public function limit($limit, $offset = 0) - { - return 'LIMIT '.$offset.', '.$limit; - } - - public function compile_select($database) - { - $sql = ($database['distinct'] == TRUE) ? 'SELECT DISTINCT ' : 'SELECT '; - $sql .= (count($database['select']) > 0) ? implode(', ', $database['select']) : '*'; - - if (count($database['from']) > 0) - { - // Escape the tables - $froms = array(); - foreach ($database['from'] as $from) - { - $froms[] = $this->escape_column($from); - } - $sql .= "\nFROM ("; - $sql .= implode(', ', $froms).")"; - } - - if (count($database['join']) > 0) - { - foreach($database['join'] AS $join) - { - $sql .= "\n".$join['type'].'JOIN '.implode(', ', $join['tables']).' ON '.$join['conditions']; - } - } - - if (count($database['where']) > 0) - { - $sql .= "\nWHERE "; - } - - $sql .= implode("\n", $database['where']); - - if (count($database['groupby']) > 0) - { - $sql .= "\nGROUP BY "; - $sql .= implode(', ', $database['groupby']); - } - - if (count($database['having']) > 0) - { - $sql .= "\nHAVING "; - $sql .= implode("\n", $database['having']); - } - - if (count($database['orderby']) > 0) - { - $sql .= "\nORDER BY "; - $sql .= implode(', ', $database['orderby']); - } - - if (is_numeric($database['limit'])) - { - $sql .= "\n"; - $sql .= $this->limit($database['limit'], $database['offset']); - } - - return $sql; - } - - public function escape_str($str) - { - if (!$this->db_config['escape']) - return $str; - - is_resource($this->link) or $this->connect(); - - return mysql_real_escape_string($str, $this->link); - } - - public function list_tables() - { - $tables = array(); - - if ($query = $this->query('SHOW TABLES FROM '.$this->escape_table($this->db_config['connection']['database']))) - { - foreach ($query->result(FALSE) as $row) - { - $tables[] = current($row); - } - } - - return $tables; - } - - public function show_error() - { - return mysql_error($this->link); - } - - public function list_fields($table) - { - $result = NULL; - - foreach ($this->field_data($table) as $row) - { - // Make an associative array - $result[$row->Field] = $this->sql_type($row->Type); - - if ($row->Key === 'PRI' AND $row->Extra === 'auto_increment') - { - // For sequenced (AUTO_INCREMENT) tables - $result[$row->Field]['sequenced'] = TRUE; - } - - if ($row->Null === 'YES') - { - // Set NULL status - $result[$row->Field]['null'] = TRUE; - } - } - - if (!isset($result)) - throw new Kohana_Database_Exception('database.table_not_found', $table); - - return $result; - } - - public function field_data($table) - { - $result = $this->query('SHOW COLUMNS FROM '.$this->escape_table($table)); - - return $result->result_array(TRUE); - } - -} // End Database_Mysql_Driver Class - -/** - * MySQL Result - */ -class Mysql_Result extends Database_Result { - - // Fetch function and return type - protected $fetch_type = 'mysql_fetch_object'; - protected $return_type = MYSQL_ASSOC; - - /** - * Sets up the result variables. - * - * @param resource query result - * @param resource database link - * @param boolean return objects or arrays - * @param string SQL query that was run - */ - public function __construct($result, $link, $object = TRUE, $sql) - { - $this->result = $result; - - // If the query is a resource, it was a SELECT, SHOW, DESCRIBE, EXPLAIN query - if (is_resource($result)) - { - $this->current_row = 0; - $this->total_rows = mysql_num_rows($this->result); - $this->fetch_type = ($object === TRUE) ? 'mysql_fetch_object' : 'mysql_fetch_array'; - } - elseif (is_bool($result)) - { - if ($result == FALSE) - { - // SQL error - throw new Kohana_Database_Exception('database.error', mysql_error($link).' - '.$sql); - } - else - { - // Its an DELETE, INSERT, REPLACE, or UPDATE query - $this->insert_id = mysql_insert_id($link); - $this->total_rows = mysql_affected_rows($link); - } - } - - // Set result type - $this->result($object); - - // Store the SQL - $this->sql = $sql; - } - - /** - * Destruct, the cleanup crew! - */ - public function __destruct() - { - if (is_resource($this->result)) - { - mysql_free_result($this->result); - } - } - - public function result($object = TRUE, $type = MYSQL_ASSOC) - { - $this->fetch_type = ((bool) $object) ? 'mysql_fetch_object' : 'mysql_fetch_array'; - - // This check has to be outside the previous statement, because we do not - // know the state of fetch_type when $object = NULL - // NOTE - The class set by $type must be defined before fetching the result, - // autoloading is disabled to save a lot of stupid overhead. - if ($this->fetch_type == 'mysql_fetch_object' AND $object === TRUE) - { - $this->return_type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass'; - } - else - { - $this->return_type = $type; - } - - return $this; - } - - public function as_array($object = NULL, $type = MYSQL_ASSOC) - { - return $this->result_array($object, $type); - } - - public function result_array($object = NULL, $type = MYSQL_ASSOC) - { - $rows = array(); - - if (is_string($object)) - { - $fetch = $object; - } - elseif (is_bool($object)) - { - if ($object === TRUE) - { - $fetch = 'mysql_fetch_object'; - - $type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass'; - } - else - { - $fetch = 'mysql_fetch_array'; - } - } - else - { - // Use the default config values - $fetch = $this->fetch_type; - - if ($fetch == 'mysql_fetch_object') - { - $type = (is_string($this->return_type) AND Kohana::auto_load($this->return_type)) ? $this->return_type : 'stdClass'; - } - } - - if (mysql_num_rows($this->result)) - { - // Reset the pointer location to make sure things work properly - mysql_data_seek($this->result, 0); - - while ($row = $fetch($this->result, $type)) - { - $rows[] = $row; - } - } - - return isset($rows) ? $rows : array(); - } - - public function list_fields() - { - $field_names = array(); - while ($field = mysql_fetch_field($this->result)) - { - $field_names[] = $field->name; - } - - return $field_names; - } - - public function seek($offset) - { - if ($this->offsetExists($offset) AND mysql_data_seek($this->result, $offset)) - { - // Set the current row to the offset - $this->current_row = $offset; - - return TRUE; - } - else - { - return FALSE; - } - } - -} // End Mysql_Result Class diff --git a/system/libraries/drivers/Database/Mysqli.php b/system/libraries/drivers/Database/Mysqli.php deleted file mode 100644 index 0dd9f05c..00000000 --- a/system/libraries/drivers/Database/Mysqli.php +++ /dev/null @@ -1,358 +0,0 @@ -db_config = $config; - - Kohana::log('debug', 'MySQLi Database Driver Initialized'); - } - - /** - * Closes the database connection. - */ - public function __destruct() - { - is_object($this->link) and $this->link->close(); - } - - public function connect() - { - // Check if link already exists - if (is_object($this->link)) - return $this->link; - - // Import the connect variables - extract($this->db_config['connection']); - - // Build the connection info - $host = isset($host) ? $host : $socket; - - // Make the connection and select the database - if ($this->link = new mysqli($host, $user, $pass, $database, $port)) - { - if ($charset = $this->db_config['character_set']) - { - $this->set_charset($charset); - } - - // Clear password after successful connect - $this->db_config['connection']['pass'] = NULL; - - return $this->link; - } - - return FALSE; - } - - public function query($sql) - { - // Only cache if it's turned on, and only cache if it's not a write statement - if ($this->db_config['cache'] AND ! preg_match('#\b(?:INSERT|UPDATE|REPLACE|SET|DELETE|TRUNCATE)\b#i', $sql)) - { - $hash = $this->query_hash($sql); - - if ( ! isset($this->query_cache[$hash])) - { - // Set the cached object - $this->query_cache[$hash] = new Kohana_Mysqli_Result($this->link, $this->db_config['object'], $sql); - } - else - { - // Rewind cached result - $this->query_cache[$hash]->rewind(); - } - - // Return the cached query - return $this->query_cache[$hash]; - } - - return new Kohana_Mysqli_Result($this->link, $this->db_config['object'], $sql); - } - - public function set_charset($charset) - { - if ($this->link->set_charset($charset) === FALSE) - throw new Kohana_Database_Exception('database.error', $this->show_error()); - } - - public function escape_str($str) - { - if (!$this->db_config['escape']) - return $str; - - is_object($this->link) or $this->connect(); - - return $this->link->real_escape_string($str); - } - - public function show_error() - { - return $this->link->error; - } - -} // End Database_Mysqli_Driver Class - -/** - * MySQLi Result - */ -class Kohana_Mysqli_Result extends Database_Result { - - // Database connection - protected $link; - - // Data fetching types - protected $fetch_type = 'mysqli_fetch_object'; - protected $return_type = MYSQLI_ASSOC; - - /** - * Sets up the result variables. - * - * @param object database link - * @param boolean return objects or arrays - * @param string SQL query that was run - */ - public function __construct($link, $object = TRUE, $sql) - { - $this->link = $link; - - if ( ! $this->link->multi_query($sql)) - { - // SQL error - throw new Kohana_Database_Exception('database.error', $this->link->error.' - '.$sql); - } - else - { - $this->result = $this->link->store_result(); - - // If the query is an object, it was a SELECT, SHOW, DESCRIBE, EXPLAIN query - if (is_object($this->result)) - { - $this->current_row = 0; - $this->total_rows = $this->result->num_rows; - $this->fetch_type = ($object === TRUE) ? 'fetch_object' : 'fetch_array'; - } - elseif ($this->link->error) - { - // SQL error - throw new Kohana_Database_Exception('database.error', $this->link->error.' - '.$sql); - } - else - { - // Its an DELETE, INSERT, REPLACE, or UPDATE query - $this->insert_id = $this->link->insert_id; - $this->total_rows = $this->link->affected_rows; - } - } - - // Set result type - $this->result($object); - - // Store the SQL - $this->sql = $sql; - } - - /** - * Magic __destruct function, frees the result. - */ - public function __destruct() - { - if (is_object($this->result)) - { - $this->result->free_result(); - - // this is kinda useless, but needs to be done to avoid the "Commands out of sync; you - // can't run this command now" error. Basically, we get all results after the first one - // (the one we actually need) and free them. - if (is_resource($this->link) AND $this->link->more_results()) - { - do - { - if ($result = $this->link->store_result()) - { - $result->free_result(); - } - } while ($this->link->next_result()); - } - } - } - - public function result($object = TRUE, $type = MYSQLI_ASSOC) - { - $this->fetch_type = ((bool) $object) ? 'fetch_object' : 'fetch_array'; - - // This check has to be outside the previous statement, because we do not - // know the state of fetch_type when $object = NULL - // NOTE - The class set by $type must be defined before fetching the result, - // autoloading is disabled to save a lot of stupid overhead. - if ($this->fetch_type == 'fetch_object') - { - $this->return_type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass'; - } - else - { - $this->return_type = $type; - } - - return $this; - } - - public function as_array($object = NULL, $type = MYSQLI_ASSOC) - { - return $this->result_array($object, $type); - } - - public function result_array($object = NULL, $type = MYSQLI_ASSOC) - { - $rows = array(); - - if (is_string($object)) - { - $fetch = $object; - } - elseif (is_bool($object)) - { - if ($object === TRUE) - { - $fetch = 'fetch_object'; - - // NOTE - The class set by $type must be defined before fetching the result, - // autoloading is disabled to save a lot of stupid overhead. - $type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass'; - } - else - { - $fetch = 'fetch_array'; - } - } - else - { - // Use the default config values - $fetch = $this->fetch_type; - - if ($fetch == 'fetch_object') - { - $type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass'; - } - } - - if ($this->result->num_rows) - { - // Reset the pointer location to make sure things work properly - $this->result->data_seek(0); - - while ($row = $this->result->$fetch($type)) - { - $rows[] = $row; - } - } - - return isset($rows) ? $rows : array(); - } - - public function list_fields() - { - $field_names = array(); - while ($field = $this->result->fetch_field()) - { - $field_names[] = $field->name; - } - - return $field_names; - } - - public function seek($offset) - { - if ($this->offsetExists($offset) AND $this->result->data_seek($offset)) - { - // Set the current row to the offset - $this->current_row = $offset; - - return TRUE; - } - - return FALSE; - } - - public function offsetGet($offset) - { - if ( ! $this->seek($offset)) - return FALSE; - - // Return the row - $fetch = $this->fetch_type; - return $this->result->$fetch($this->return_type); - } - -} // End Mysqli_Result Class - -/** - * MySQLi Prepared Statement (experimental) - */ -class Kohana_Mysqli_Statement { - - protected $link = NULL; - protected $stmt; - protected $var_names = array(); - protected $var_values = array(); - - public function __construct($sql, $link) - { - $this->link = $link; - - $this->stmt = $this->link->prepare($sql); - - return $this; - } - - public function __destruct() - { - $this->stmt->close(); - } - - // Sets the bind parameters - public function bind_params($param_types, $params) - { - $this->var_names = array_keys($params); - $this->var_values = array_values($params); - call_user_func_array(array($this->stmt, 'bind_param'), array_merge($param_types, $var_names)); - - return $this; - } - - public function bind_result($params) - { - call_user_func_array(array($this->stmt, 'bind_result'), $params); - } - - // Runs the statement - public function execute() - { - foreach ($this->var_names as $key => $name) - { - $$name = $this->var_values[$key]; - } - $this->stmt->execute(); - return $this->stmt; - } -} diff --git a/system/libraries/drivers/Database/Pdosqlite.php b/system/libraries/drivers/Database/Pdosqlite.php deleted file mode 100644 index c2d1bb21..00000000 --- a/system/libraries/drivers/Database/Pdosqlite.php +++ /dev/null @@ -1,486 +0,0 @@ - - */ - -class Database_Pdosqlite_Driver extends Database_Driver { - - // Database connection link - protected $link; - protected $db_config; - - /* - * Constructor: __construct - * Sets up the config for the class. - * - * Parameters: - * config - database configuration - * - */ - public function __construct($config) - { - $this->db_config = $config; - - Kohana::log('debug', 'PDO:Sqlite Database Driver Initialized'); - } - - public function connect() - { - // Import the connect variables - extract($this->db_config['connection']); - - try - { - $this->link = new PDO('sqlite:'.$socket.$database, $user, $pass, - array(PDO::ATTR_PERSISTENT => $this->db_config['persistent'])); - - $this->link->setAttribute(PDO::ATTR_CASE, PDO::CASE_NATURAL); - //$this->link->query('PRAGMA count_changes=1;'); - - if ($charset = $this->db_config['character_set']) - { - $this->set_charset($charset); - } - } - catch (PDOException $e) - { - throw new Kohana_Database_Exception('database.error', $e->getMessage()); - } - - // Clear password after successful connect - $this->db_config['connection']['pass'] = NULL; - - return $this->link; - } - - public function query($sql) - { - try - { - $sth = $this->link->prepare($sql); - } - catch (PDOException $e) - { - throw new Kohana_Database_Exception('database.error', $e->getMessage()); - } - return new Pdosqlite_Result($sth, $this->link, $this->db_config['object'], $sql); - } - - public function set_charset($charset) - { - $this->link->query('PRAGMA encoding = '.$this->escape_str($charset)); - } - - public function escape_table($table) - { - if ( ! $this->db_config['escape']) - return $table; - - return '`'.str_replace('.', '`.`', $table).'`'; - } - - public function escape_column($column) - { - if ( ! $this->db_config['escape']) - return $column; - - if ($column == '*') - return $column; - - // This matches any functions we support to SELECT. - if ( preg_match('/(avg|count|sum|max|min)\(\s*(.*)\s*\)(\s*as\s*(.+)?)?/i', $column, $matches)) - { - if ( count($matches) == 3) - { - return $matches[1].'('.$this->escape_column($matches[2]).')'; - } - else if ( count($matches) == 5) - { - return $matches[1].'('.$this->escape_column($matches[2]).') AS '.$this->escape_column($matches[2]); - } - } - - // This matches any modifiers we support to SELECT. - if ( ! preg_match('/\b(?:rand|all|distinct(?:row)?|high_priority|sql_(?:small_result|b(?:ig_result|uffer_result)|no_cache|ca(?:che|lc_found_rows)))\s/i', $column)) - { - if (stripos($column, ' AS ') !== FALSE) - { - // Force 'AS' to uppercase - $column = str_ireplace(' AS ', ' AS ', $column); - - // Runs escape_column on both sides of an AS statement - $column = array_map(array($this, __FUNCTION__), explode(' AS ', $column)); - - // Re-create the AS statement - return implode(' AS ', $column); - } - - return preg_replace('/[^.*]+/', '`$0`', $column); - } - - $parts = explode(' ', $column); - $column = ''; - - for ($i = 0, $c = count($parts); $i < $c; $i++) - { - // The column is always last - if ($i == ($c - 1)) - { - $column .= preg_replace('/[^.*]+/', '`$0`', $parts[$i]); - } - else // otherwise, it's a modifier - { - $column .= $parts[$i].' '; - } - } - return $column; - } - - public function limit($limit, $offset = 0) - { - return 'LIMIT '.$offset.', '.$limit; - } - - public function compile_select($database) - { - $sql = ($database['distinct'] == TRUE) ? 'SELECT DISTINCT ' : 'SELECT '; - $sql .= (count($database['select']) > 0) ? implode(', ', $database['select']) : '*'; - - if (count($database['from']) > 0) - { - $sql .= "\nFROM "; - $sql .= implode(', ', $database['from']); - } - - if (count($database['join']) > 0) - { - foreach($database['join'] AS $join) - { - $sql .= "\n".$join['type'].'JOIN '.implode(', ', $join['tables']).' ON '.$join['conditions']; - } - } - - if (count($database['where']) > 0) - { - $sql .= "\nWHERE "; - } - - $sql .= implode("\n", $database['where']); - - if (count($database['groupby']) > 0) - { - $sql .= "\nGROUP BY "; - $sql .= implode(', ', $database['groupby']); - } - - if (count($database['having']) > 0) - { - $sql .= "\nHAVING "; - $sql .= implode("\n", $database['having']); - } - - if (count($database['orderby']) > 0) - { - $sql .= "\nORDER BY "; - $sql .= implode(', ', $database['orderby']); - } - - if (is_numeric($database['limit'])) - { - $sql .= "\n"; - $sql .= $this->limit($database['limit'], $database['offset']); - } - - return $sql; - } - - public function escape_str($str) - { - if ( ! $this->db_config['escape']) - return $str; - - if (function_exists('sqlite_escape_string')) - { - $res = sqlite_escape_string($str); - } - else - { - $res = str_replace("'", "''", $str); - } - return $res; - } - - public function list_tables() - { - $sql = "SELECT `name` FROM `sqlite_master` WHERE `type`='table' ORDER BY `name`;"; - try - { - $result = $this->query($sql)->result(FALSE, PDO::FETCH_ASSOC); - $tables = array(); - foreach ($result as $row) - { - $tables[] = current($row); - } - } - catch (PDOException $e) - { - throw new Kohana_Database_Exception('database.error', $e->getMessage()); - } - return $tables; - } - - public function show_error() - { - $err = $this->link->errorInfo(); - return isset($err[2]) ? $err[2] : 'Unknown error!'; - } - - public function list_fields($table, $query = FALSE) - { - static $tables; - if (is_object($query)) - { - if (empty($tables[$table])) - { - $tables[$table] = array(); - - foreach ($query->result() as $row) - { - $tables[$table][] = $row->name; - } - } - - return $tables[$table]; - } - else - { - $result = $this->link->query( 'PRAGMA table_info('.$this->escape_table($table).')' ); - - foreach ($result as $row) - { - $tables[$table][$row['name']] = $this->sql_type($row['type']); - } - - return $tables[$table]; - } - } - - public function field_data($table) - { - Kohana::log('error', 'This method is under developing'); - } - /** - * Version number query string - * - * @access public - * @return string - */ - function version() - { - return $this->link->getAttribute(constant("PDO::ATTR_SERVER_VERSION")); - } - -} // End Database_PdoSqlite_Driver Class - -/* - * PDO-sqlite Result - */ -class Pdosqlite_Result extends Database_Result { - - // Data fetching types - protected $fetch_type = PDO::FETCH_OBJ; - protected $return_type = PDO::FETCH_ASSOC; - - /** - * Sets up the result variables. - * - * @param resource query result - * @param resource database link - * @param boolean return objects or arrays - * @param string SQL query that was run - */ - public function __construct($result, $link, $object = TRUE, $sql) - { - if (is_object($result) OR $result = $link->prepare($sql)) - { - // run the query. Return true if success, false otherwise - if( ! $result->execute()) - { - // Throw Kohana Exception with error message. See PDOStatement errorInfo() method - $arr_infos = $result->errorInfo(); - throw new Kohana_Database_Exception('database.error', $arr_infos[2]); - } - - if (preg_match('/^SELECT|PRAGMA|EXPLAIN/i', $sql)) - { - $this->result = $result; - $this->current_row = 0; - - $this->total_rows = $this->sqlite_row_count(); - - $this->fetch_type = ($object === TRUE) ? PDO::FETCH_OBJ : PDO::FETCH_ASSOC; - } - elseif (preg_match('/^DELETE|INSERT|UPDATE/i', $sql)) - { - $this->insert_id = $link->lastInsertId(); - - $this->total_rows = $result->rowCount(); - } - } - else - { - // SQL error - throw new Kohana_Database_Exception('database.error', $link->errorInfo().' - '.$sql); - } - - // Set result type - $this->result($object); - - // Store the SQL - $this->sql = $sql; - } - - private function sqlite_row_count() - { - $count = 0; - while ($this->result->fetch()) - { - $count++; - } - - // The query must be re-fetched now. - $this->result->execute(); - - return $count; - } - - /* - * Destructor: __destruct - * Magic __destruct function, frees the result. - */ - public function __destruct() - { - if (is_object($this->result)) - { - $this->result->closeCursor(); - $this->result = NULL; - } - } - - public function result($object = TRUE, $type = PDO::FETCH_BOTH) - { - $this->fetch_type = (bool) $object ? PDO::FETCH_OBJ : PDO::FETCH_BOTH; - - if ($this->fetch_type == PDO::FETCH_OBJ) - { - $this->return_type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass'; - } - else - { - $this->return_type = $type; - } - - return $this; - } - - public function as_array($object = NULL, $type = PDO::FETCH_ASSOC) - { - return $this->result_array($object, $type); - } - - public function result_array($object = NULL, $type = PDO::FETCH_ASSOC) - { - $rows = array(); - - if (is_string($object)) - { - $fetch = $object; - } - elseif (is_bool($object)) - { - if ($object === TRUE) - { - $fetch = PDO::FETCH_OBJ; - - // NOTE - The class set by $type must be defined before fetching the result, - // autoloading is disabled to save a lot of stupid overhead. - $type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass'; - } - else - { - $fetch = PDO::FETCH_OBJ; - } - } - else - { - // Use the default config values - $fetch = $this->fetch_type; - - if ($fetch == PDO::FETCH_OBJ) - { - $type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass'; - } - } - try - { - while ($row = $this->result->fetch($fetch)) - { - $rows[] = $row; - } - } - catch(PDOException $e) - { - throw new Kohana_Database_Exception('database.error', $e->getMessage()); - return FALSE; - } - return $rows; - } - - public function list_fields() - { - $field_names = array(); - for ($i = 0, $max = $this->result->columnCount(); $i < $max; $i++) - { - $info = $this->result->getColumnMeta($i); - $field_names[] = $info['name']; - } - return $field_names; - } - - public function seek($offset) - { - // To request a scrollable cursor for your PDOStatement object, you must - // set the PDO::ATTR_CURSOR attribute to PDO::CURSOR_SCROLL when you - // prepare the statement. - Kohana::log('error', get_class($this).' does not support scrollable cursors, '.__FUNCTION__.' call ignored'); - - return FALSE; - } - - public function offsetGet($offset) - { - try - { - return $this->result->fetch($this->fetch_type, PDO::FETCH_ORI_ABS, $offset); - } - catch(PDOException $e) - { - throw new Kohana_Database_Exception('database.error', $e->getMessage()); - } - } - - public function rewind() - { - // Same problem that seek() has, see above. - return $this->seek(0); - } - -} // End PdoSqlite_Result Class \ No newline at end of file diff --git a/system/libraries/drivers/Database/Pgsql.php b/system/libraries/drivers/Database/Pgsql.php deleted file mode 100644 index c53c8439..00000000 --- a/system/libraries/drivers/Database/Pgsql.php +++ /dev/null @@ -1,538 +0,0 @@ -db_config = $config; - - Kohana::log('debug', 'PgSQL Database Driver Initialized'); - } - - public function connect() - { - // Check if link already exists - if (is_resource($this->link)) - return $this->link; - - // Import the connect variables - extract($this->db_config['connection']); - - // Persistent connections enabled? - $connect = ($this->db_config['persistent'] == TRUE) ? 'pg_pconnect' : 'pg_connect'; - - // Build the connection info - $port = isset($port) ? 'port=\''.$port.'\'' : ''; - $host = isset($host) ? 'host=\''.$host.'\' '.$port : ''; // if no host, connect with the socket - - $connection_string = $host.' dbname=\''.$database.'\' user=\''.$user.'\' password=\''.$pass.'\''; - // Make the connection and select the database - if ($this->link = $connect($connection_string)) - { - if ($charset = $this->db_config['character_set']) - { - echo $this->set_charset($charset); - } - - // Clear password after successful connect - $this->db_config['connection']['pass'] = NULL; - - return $this->link; - } - - return FALSE; - } - - public function query($sql) - { - // Only cache if it's turned on, and only cache if it's not a write statement - if ($this->db_config['cache'] AND ! preg_match('#\b(?:INSERT|UPDATE|SET)\b#i', $sql)) - { - $hash = $this->query_hash($sql); - - if ( ! isset($this->query_cache[$hash])) - { - // Set the cached object - $this->query_cache[$hash] = new Pgsql_Result(pg_query($this->link, $sql), $this->link, $this->db_config['object'], $sql); - } - else - { - // Rewind cached result - $this->query_cache[$hash]->rewind(); - } - - return $this->query_cache[$hash]; - } - - // Suppress warning triggered when a database error occurs (e.g., a constraint violation) - return new Pgsql_Result(@pg_query($this->link, $sql), $this->link, $this->db_config['object'], $sql); - } - - public function set_charset($charset) - { - $this->query('SET client_encoding TO '.pg_escape_string($this->link, $charset)); - } - - public function escape_table($table) - { - if (!$this->db_config['escape']) - return $table; - - return '"'.str_replace('.', '"."', $table).'"'; - } - - public function escape_column($column) - { - if (!$this->db_config['escape']) - return $column; - - if ($column == '*') - return $column; - - // This matches any functions we support to SELECT. - if ( preg_match('/(avg|count|sum|max|min)\(\s*(.*)\s*\)(\s*as\s*(.+)?)?/i', $column, $matches)) - { - if ( count($matches) == 3) - { - return $matches[1].'('.$this->escape_column($matches[2]).')'; - } - else if ( count($matches) == 5) - { - return $matches[1].'('.$this->escape_column($matches[2]).') AS '.$this->escape_column($matches[2]); - } - } - - // This matches any modifiers we support to SELECT. - if ( ! preg_match('/\b(?:all|distinct)\s/i', $column)) - { - if (stripos($column, ' AS ') !== FALSE) - { - // Force 'AS' to uppercase - $column = str_ireplace(' AS ', ' AS ', $column); - - // Runs escape_column on both sides of an AS statement - $column = array_map(array($this, __FUNCTION__), explode(' AS ', $column)); - - // Re-create the AS statement - return implode(' AS ', $column); - } - - return preg_replace('/[^.*]+/', '"$0"', $column); - } - - $parts = explode(' ', $column); - $column = ''; - - for ($i = 0, $c = count($parts); $i < $c; $i++) - { - // The column is always last - if ($i == ($c - 1)) - { - $column .= preg_replace('/[^.*]+/', '"$0"', $parts[$i]); - } - else // otherwise, it's a modifier - { - $column .= $parts[$i].' '; - } - } - return $column; - } - - public function regex($field, $match, $type, $num_regexs) - { - $prefix = ($num_regexs == 0) ? '' : $type; - - return $prefix.' '.$this->escape_column($field).' ~* \''.$this->escape_str($match).'\''; - } - - public function notregex($field, $match, $type, $num_regexs) - { - $prefix = $num_regexs == 0 ? '' : $type; - - return $prefix.' '.$this->escape_column($field).' !~* \''.$this->escape_str($match) . '\''; - } - - public function limit($limit, $offset = 0) - { - return 'LIMIT '.$limit.' OFFSET '.$offset; - } - - public function compile_select($database) - { - $sql = ($database['distinct'] == TRUE) ? 'SELECT DISTINCT ' : 'SELECT '; - $sql .= (count($database['select']) > 0) ? implode(', ', $database['select']) : '*'; - - if (count($database['from']) > 0) - { - $sql .= "\nFROM "; - $sql .= implode(', ', $database['from']); - } - - if (count($database['join']) > 0) - { - foreach($database['join'] AS $join) - { - $sql .= "\n".$join['type'].'JOIN '.implode(', ', $join['tables']).' ON '.$join['conditions']; - } - } - - if (count($database['where']) > 0) - { - $sql .= "\nWHERE "; - } - - $sql .= implode("\n", $database['where']); - - if (count($database['groupby']) > 0) - { - $sql .= "\nGROUP BY "; - $sql .= implode(', ', $database['groupby']); - } - - if (count($database['having']) > 0) - { - $sql .= "\nHAVING "; - $sql .= implode("\n", $database['having']); - } - - if (count($database['orderby']) > 0) - { - $sql .= "\nORDER BY "; - $sql .= implode(', ', $database['orderby']); - } - - if (is_numeric($database['limit'])) - { - $sql .= "\n"; - $sql .= $this->limit($database['limit'], $database['offset']); - } - - return $sql; - } - - public function escape_str($str) - { - if (!$this->db_config['escape']) - return $str; - - is_resource($this->link) or $this->connect(); - - return pg_escape_string($this->link, $str); - } - - public function list_tables() - { - $sql = 'SELECT table_schema || \'.\' || table_name FROM information_schema.tables WHERE table_schema NOT IN (\'pg_catalog\', \'information_schema\')'; - $result = $this->query($sql)->result(FALSE, PGSQL_ASSOC); - - $retval = array(); - foreach ($result as $row) - { - $retval[] = current($row); - } - - return $retval; - } - - public function show_error() - { - return pg_last_error($this->link); - } - - public function list_fields($table) - { - $result = NULL; - - foreach ($this->field_data($table) as $row) - { - // Make an associative array - $result[$row->column_name] = $this->sql_type($row->data_type); - - if (!strncmp($row->column_default, 'nextval(', 8)) - { - $result[$row->column_name]['sequenced'] = TRUE; - } - - if ($row->is_nullable === 'YES') - { - $result[$row->column_name]['null'] = TRUE; - } - } - - if (!isset($result)) - throw new Kohana_Database_Exception('database.table_not_found', $table); - - return $result; - } - - public function field_data($table) - { - // http://www.postgresql.org/docs/8.3/static/infoschema-columns.html - $result = $this->query(' - SELECT column_name, column_default, is_nullable, data_type, udt_name, - character_maximum_length, numeric_precision, numeric_precision_radix, numeric_scale - FROM information_schema.columns - WHERE table_name = \''. $this->escape_str($table) .'\' - ORDER BY ordinal_position - '); - - return $result->result_array(TRUE); - } - -} // End Database_Pgsql_Driver Class - -/** - * PostgreSQL Result - */ -class Pgsql_Result extends Database_Result { - - // Data fetching types - protected $fetch_type = 'pgsql_fetch_object'; - protected $return_type = PGSQL_ASSOC; - - /** - * Sets up the result variables. - * - * @param resource query result - * @param resource database link - * @param boolean return objects or arrays - * @param string SQL query that was run - */ - public function __construct($result, $link, $object = TRUE, $sql) - { - $this->link = $link; - $this->result = $result; - - // If the query is a resource, it was a SELECT, SHOW, DESCRIBE, EXPLAIN query - if (is_resource($result)) - { - // Its an DELETE, INSERT, REPLACE, or UPDATE query - if (preg_match('/^(?:delete|insert|replace|update)\b/iD', trim($sql), $matches)) - { - $this->insert_id = (strtolower($matches[0]) == 'insert') ? $this->insert_id() : FALSE; - $this->total_rows = pg_affected_rows($this->result); - } - else - { - $this->current_row = 0; - $this->total_rows = pg_num_rows($this->result); - $this->fetch_type = ($object === TRUE) ? 'pg_fetch_object' : 'pg_fetch_array'; - } - } - else - { - throw new Kohana_Database_Exception('database.error', pg_last_error().' - '.$sql); - } - - // Set result type - $this->result($object); - - // Store the SQL - $this->sql = $sql; - } - - /** - * Magic __destruct function, frees the result. - */ - public function __destruct() - { - if (is_resource($this->result)) - { - pg_free_result($this->result); - } - } - - public function result($object = TRUE, $type = PGSQL_ASSOC) - { - $this->fetch_type = ((bool) $object) ? 'pg_fetch_object' : 'pg_fetch_array'; - - // This check has to be outside the previous statement, because we do not - // know the state of fetch_type when $object = NULL - // NOTE - The class set by $type must be defined before fetching the result, - // autoloading is disabled to save a lot of stupid overhead. - if ($this->fetch_type == 'pg_fetch_object') - { - $this->return_type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass'; - } - else - { - $this->return_type = $type; - } - - return $this; - } - - public function as_array($object = NULL, $type = PGSQL_ASSOC) - { - return $this->result_array($object, $type); - } - - public function result_array($object = NULL, $type = PGSQL_ASSOC) - { - $rows = array(); - - if (is_string($object)) - { - $fetch = $object; - } - elseif (is_bool($object)) - { - if ($object === TRUE) - { - $fetch = 'pg_fetch_object'; - - // NOTE - The class set by $type must be defined before fetching the result, - // autoloading is disabled to save a lot of stupid overhead. - $type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass'; - } - else - { - $fetch = 'pg_fetch_array'; - } - } - else - { - // Use the default config values - $fetch = $this->fetch_type; - - if ($fetch == 'pg_fetch_object') - { - $type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass'; - } - } - - if ($this->total_rows) - { - pg_result_seek($this->result, 0); - - while ($row = $fetch($this->result, NULL, $type)) - { - $rows[] = $row; - } - } - - return $rows; - } - - public function insert_id() - { - if ($this->insert_id === NULL) - { - $query = 'SELECT LASTVAL() AS insert_id'; - - // Disable error reporting for this, just to silence errors on - // tables that have no serial column. - $ER = error_reporting(0); - - $result = pg_query($this->link, $query); - $insert_id = pg_fetch_array($result, NULL, PGSQL_ASSOC); - - $this->insert_id = $insert_id['insert_id']; - - // Reset error reporting - error_reporting($ER); - } - - return $this->insert_id; - } - - public function seek($offset) - { - if ($this->offsetExists($offset) AND pg_result_seek($this->result, $offset)) - { - // Set the current row to the offset - $this->current_row = $offset; - - return TRUE; - } - - return FALSE; - } - - public function list_fields() - { - $field_names = array(); - - $fields = pg_num_fields($this->result); - for ($i = 0; $i < $fields; ++$i) - { - $field_names[] = pg_field_name($this->result, $i); - } - - return $field_names; - } - - /** - * ArrayAccess: offsetGet - */ - public function offsetGet($offset) - { - if ( ! $this->seek($offset)) - return FALSE; - - // Return the row by calling the defined fetching callback - $fetch = $this->fetch_type; - return $fetch($this->result, NULL, $this->return_type); - } - -} // End Pgsql_Result Class - -/** - * PostgreSQL Prepared Statement (experimental) - */ -class Kohana_Pgsql_Statement { - - protected $link = NULL; - protected $stmt; - - public function __construct($sql, $link) - { - $this->link = $link; - - $this->stmt = $this->link->prepare($sql); - - return $this; - } - - public function __destruct() - { - $this->stmt->close(); - } - - // Sets the bind parameters - public function bind_params() - { - $argv = func_get_args(); - return $this; - } - - // sets the statement values to the bound parameters - public function set_vals() - { - return $this; - } - - // Runs the statement - public function execute() - { - return $this; - } -} diff --git a/system/libraries/drivers/Image.php b/system/libraries/drivers/Image.php index f89ba953..39936c30 100644 --- a/system/libraries/drivers/Image.php +++ b/system/libraries/drivers/Image.php @@ -2,12 +2,12 @@ /** * Image API driver. * - * $Id: Image.php 3769 2008-12-15 00:48:56Z zombor $ + * $Id: Image.php 4679 2009-11-10 01:45:52Z isaiah $ * * @package Image * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html + * @copyright (c) 2007-2009 Kohana Team + * @license http://kohanaphp.com/license */ abstract class Image_Driver { @@ -102,9 +102,11 @@ abstract class Image_Driver { * @param array actions to execute * @param string destination directory path * @param string destination filename + * @param boolean render the image + * @param string background color * @return boolean */ - abstract public function process($image, $actions, $dir, $file); + abstract public function process($image, $actions, $dir, $file, $render = FALSE, $background = NULL); /** * Flip an image. Valid directions are horizontal and vertical. diff --git a/system/libraries/drivers/Image/GD.php b/system/libraries/drivers/Image/GD.php index be2af4e2..6ffffe8a 100644 --- a/system/libraries/drivers/Image/GD.php +++ b/system/libraries/drivers/Image/GD.php @@ -2,12 +2,12 @@ /** * GD Image Driver. * - * $Id: GD.php 3769 2008-12-15 00:48:56Z zombor $ + * $Id: GD.php 4679 2009-11-10 01:45:52Z isaiah $ * * @package Image * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html + * @copyright (c) 2007-2009 Kohana Team + * @license http://kohanaphp.com/license */ class Image_GD_Driver extends Image_Driver { @@ -20,17 +20,17 @@ class Image_GD_Driver extends Image_Driver { { // Make sure that GD2 is available if ( ! function_exists('gd_info')) - throw new Kohana_Exception('image.gd.requires_v2'); + throw new Kohana_Exception('The Image library requires GD2. Please see http://php.net/gd_info for more information.'); // Get the GD information $info = gd_info(); // Make sure that the GD2 is installed if (strpos($info['GD Version'], '2.') === FALSE) - throw new Kohana_Exception('image.gd.requires_v2'); + throw new Kohana_Exception('The Image library requires GD2. Please see http://php.net/gd_info for more information.'); } - public function process($image, $actions, $dir, $file, $render = FALSE) + public function process($image, $actions, $dir, $file, $render = FALSE, $background = NULL) { // Set the "create" function switch ($image['type']) @@ -63,11 +63,11 @@ class Image_GD_Driver extends Image_Driver { // Make sure the image type is supported for import if (empty($create) OR ! function_exists($create)) - throw new Kohana_Exception('image.type_not_allowed', $image['file']); + throw new Kohana_Exception('The specified image, :type:, is not an allowed image type.', array(':type:' => $image['file'])); // Make sure the image type is supported for saving if (empty($save) OR ! function_exists($save)) - throw new Kohana_Exception('image.type_not_allowed', $dir.$file); + throw new Kohana_Exception('The specified image, :type:, is not an allowed image type.', array(':type:' => $dir.$file)); // Load the image $this->image = $image; @@ -211,11 +211,11 @@ class Image_GD_Driver extends Image_Driver { // Recalculate the percentage to a pixel size $properties['height'] = round($height * (substr($properties['height'], 0, -1) / 100)); } - + // Recalculate the width and height, if they are missing empty($properties['width']) and $properties['width'] = round($width * $properties['height'] / $height); empty($properties['height']) and $properties['height'] = round($height * $properties['width'] / $width); - + if ($properties['master'] === Image::AUTO) { // Change an automatic master dim to the correct type @@ -314,7 +314,7 @@ class Image_GD_Driver extends Image_Driver { { // Make sure that the sharpening function is available if ( ! function_exists('imageconvolution')) - throw new Kohana_Exception('image.unsupported_method', __FUNCTION__); + throw new Kohana_Exception('Your configured driver does not support the :method: image transformation.', array(':method:' => __FUNCTION__)); // Amount should be in the range of 18-10 $amount = round(abs(-18 + ($amount * 0.08)), 2); @@ -336,23 +336,52 @@ class Image_GD_Driver extends Image_Driver { switch($properties['mime']) { case "image/jpeg": - $overlay_img = imagecreatefromjpeg($properties['overlay_file']); + $overlay_img = imagecreatefromjpeg($properties['overlay_file']); break; case "image/gif": - $overlay_img = imagecreatefromgif($properties['overlay_file']); + $overlay_img = imagecreatefromgif($properties['overlay_file']); break; case "image/png": - $overlay_img = imagecreatefrompng($properties['overlay_file']); + $overlay_img = imagecreatefrompng($properties['overlay_file']); break; } - imagecopymerge($this->tmp_image, $overlay_img, $properties['x'], $properties['y'], 0, 0, imagesx($overlay_img), imagesy($overlay_img), $properties['transparency']); + $this->imagecopymerge_alpha($this->tmp_image, $overlay_img, $properties['x'], $properties['y'], 0, 0, imagesx($overlay_img), imagesy($overlay_img), $properties['transparency']); + imagedestroy($overlay_img); + return TRUE; } + /** + * A replacement for php's imagecopymerge() function that supports the alpha channel + * See php bug #23815: http://bugs.php.net/bug.php?id=23815 + * + * @param resource $dst_im Destination image link resource + * @param resource $src_im Source image link resource + * @param integer $dst_x x-coordinate of destination point + * @param integer $dst_y y-coordinate of destination point + * @param integer $src_x x-coordinate of source point + * @param integer $src_y y-coordinate of source point + * @param integer $src_w Source width + * @param integer $src_h Source height + * @param integer $pct Transparency percent (0 to 100) + */ + protected function imagecopymerge_alpha($dst_im, $src_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $pct) + { + // Create a new blank image the site of our source image + $cut = imagecreatetruecolor($src_w, $src_h); + + // Copy the blank image into the destination image where the source goes + imagecopy($cut, $dst_im, 0, 0, $dst_x, $dst_y, $src_w, $src_h); + + // Place the source image in the destination image + imagecopy($cut, $src_im, 0, 0, $src_x, $src_y, $src_w, $src_h); + imagecopymerge($dst_im, $cut, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $pct); + } + protected function properties() { return array(imagesx($this->tmp_image), imagesy($this->tmp_image)); @@ -368,6 +397,16 @@ class Image_GD_Driver extends Image_Driver { */ protected function imagecreatetransparent($width, $height) { + if ($width < 1) + { + $width = 1; + } + + if ($height < 1) + { + $height = 1; + } + if (self::$blank_png === NULL) { // Decode the blank PNG if it has not been done already diff --git a/system/libraries/drivers/Image/GraphicsMagick.php b/system/libraries/drivers/Image/GraphicsMagick.php index a8bc4d9b..89b40b41 100644 --- a/system/libraries/drivers/Image/GraphicsMagick.php +++ b/system/libraries/drivers/Image/GraphicsMagick.php @@ -4,8 +4,8 @@ * * @package Image * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html + * @copyright (c) 2007-2009 Kohana Team + * @license http://kohanaphp.com/license */ class Image_GraphicsMagick_Driver extends Image_Driver { @@ -31,7 +31,7 @@ class Image_GraphicsMagick_Driver extends Image_Driver { { // Attempt to locate GM by using "which" (only works for *nix!) if ( ! is_file($path = exec('which gm'))) - throw new Kohana_Exception('image.graphicsmagick.not_found'); + throw new Kohana_Exception('The GraphicsMagick directory specified does not contain a required program.'); $config['directory'] = dirname($path); } @@ -41,7 +41,7 @@ class Image_GraphicsMagick_Driver extends Image_Driver { // Check to make sure the provided path is correct if ( ! is_file(realpath($config['directory']).'/gm'.$this->ext)) - throw new Kohana_Exception('image.graphicsmagick.not_found', 'gm'.$this->ext); + throw new Kohana_Exception('The GraphicsMagick directory specified does not contain a required program, :gm:.', array(':gm:' => 'gm'.$this->ext)); // Set the installation directory @@ -52,8 +52,12 @@ class Image_GraphicsMagick_Driver extends Image_Driver { * Creates a temporary image and executes the given actions. By creating a * temporary copy of the image before manipulating it, this process is atomic. */ - public function process($image, $actions, $dir, $file, $render = FALSE) + public function process($image, $actions, $dir, $file, $render = FALSE, $background = NULL) { + // Need to implement $background support + if ($background !== NULL) + throw new Kohana_Exception('The GraphicsMagick driver does not support setting a background color'); + // We only need the filename $image = $image['file']; diff --git a/system/libraries/drivers/Image/ImageMagick.php b/system/libraries/drivers/Image/ImageMagick.php index 4b381fd6..31862f75 100644 --- a/system/libraries/drivers/Image/ImageMagick.php +++ b/system/libraries/drivers/Image/ImageMagick.php @@ -2,12 +2,12 @@ /** * ImageMagick Image Driver. * - * $Id: ImageMagick.php 3769 2008-12-15 00:48:56Z zombor $ + * $Id: ImageMagick.php 4679 2009-11-10 01:45:52Z isaiah $ * * @package Image * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html + * @copyright (c) 2007-2009 Kohana Team + * @license http://kohanaphp.com/license */ class Image_ImageMagick_Driver extends Image_Driver { @@ -33,7 +33,7 @@ class Image_ImageMagick_Driver extends Image_Driver { { // Attempt to locate IM by using "which" (only works for *nix!) if ( ! is_file($path = exec('which convert'))) - throw new Kohana_Exception('image.imagemagick.not_found'); + throw new Kohana_Exception('The ImageMagick directory specified does not contain a required program.'); $config['directory'] = dirname($path); } @@ -43,7 +43,7 @@ class Image_ImageMagick_Driver extends Image_Driver { // Check to make sure the provided path is correct if ( ! is_file(realpath($config['directory']).'/convert'.$this->ext)) - throw new Kohana_Exception('image.imagemagick.not_found', 'convert'.$this->ext); + throw new Kohana_Exception('The ImageMagick directory specified does not contain a required program, :im:', array(':im:' => 'convert'.$this->ext)); // Set the installation directory $this->dir = str_replace('\\', '/', realpath($config['directory'])).'/'; @@ -53,7 +53,7 @@ class Image_ImageMagick_Driver extends Image_Driver { * Creates a temporary image and executes the given actions. By creating a * temporary copy of the image before manipulating it, this process is atomic. */ - public function process($image, $actions, $dir, $file, $render = FALSE) + public function process($image, $actions, $dir, $file, $render = FALSE, $background = NULL) { // We only need the filename $image = $image['file']; @@ -70,23 +70,34 @@ class Image_ImageMagick_Driver extends Image_Driver { // Use 95 for the default quality empty($quality) and $quality = 95; + if (is_string($background)) + { + // Set the background color + $this->background = escapeshellarg($background); + } + else + { + // Use a transparent background + $this->background = 'transparent'; + } + // All calls to these will need to be escaped, so do it now $this->cmd_image = escapeshellarg($this->tmp_image); - $this->new_image = ($render)? $this->cmd_image : escapeshellarg($dir.$file); + $this->new_image = $render ? $this->cmd_image : escapeshellarg($dir.$file); if ($status = $this->execute($actions)) { // Use convert to change the image into its final version. This is // done to allow the file type to change correctly, and to handle // the quality conversion in the most effective way possible. - if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -quality '.$quality.'% '.$this->cmd_image.' '.$this->new_image)) + if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -background '.$this->background.' -flatten -quality '.$quality.'% '.$this->cmd_image.' '.$this->new_image)) { $this->errors[] = $error; } else { // Output the image directly to the browser - if ($render !== FALSE) + if ($render === TRUE) { $contents = file_get_contents($this->tmp_image); switch (substr($file, strrpos($file, '.') + 1)) @@ -122,7 +133,7 @@ class Image_ImageMagick_Driver extends Image_Driver { // Set the IM geometry based on the properties $geometry = escapeshellarg($prop['width'].'x'.$prop['height'].'+'.$prop['left'].'+'.$prop['top']); - if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -crop '.$geometry.' '.$this->cmd_image.' '.$this->cmd_image)) + if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -background '.$this->background.' -flatten -crop '.$geometry.' '.$this->cmd_image.' '.$this->cmd_image)) { $this->errors[] = $error; return FALSE; @@ -136,7 +147,7 @@ class Image_ImageMagick_Driver extends Image_Driver { // Convert the direction into a IM command $dir = ($dir === Image::HORIZONTAL) ? '-flop' : '-flip'; - if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' '.$dir.' '.$this->cmd_image.' '.$this->cmd_image)) + if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -background '.$this->background.' -flatten '.$dir.' '.$this->cmd_image.' '.$this->cmd_image)) { $this->errors[] = $error; return FALSE; @@ -164,7 +175,7 @@ class Image_ImageMagick_Driver extends Image_Driver { } // Use "convert" to change the width and height - if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -resize '.$dim.' '.$this->cmd_image.' '.$this->cmd_image)) + if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -background '.$this->background.' -flatten -resize '.$dim.' '.$this->cmd_image.' '.$this->cmd_image)) { $this->errors[] = $error; return FALSE; @@ -175,7 +186,7 @@ class Image_ImageMagick_Driver extends Image_Driver { public function rotate($amt) { - if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -rotate '.escapeshellarg($amt).' -background transparent '.$this->cmd_image.' '.$this->cmd_image)) + if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -background '.$this->background.' -flatten -rotate '.escapeshellarg($amt).' -background transparent '.$this->cmd_image.' '.$this->cmd_image)) { $this->errors[] = $error; return FALSE; @@ -195,7 +206,7 @@ class Image_ImageMagick_Driver extends Image_Driver { // Convert the amount to an IM command $sharpen = escapeshellarg($radius.'x'.$sigma.'+'.$amount.'+0'); - if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -unsharp '.$sharpen.' '.$this->cmd_image.' '.$this->cmd_image)) + if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -background '.$this->background.' -flatten -unsharp '.$sharpen.' '.$this->cmd_image.' '.$this->cmd_image)) { $this->errors[] = $error; return FALSE; diff --git a/system/libraries/drivers/Log.php b/system/libraries/drivers/Log.php new file mode 100644 index 00000000..cd6dba7d --- /dev/null +++ b/system/libraries/drivers/Log.php @@ -0,0 +1,22 @@ +config = $config; + } + + abstract public function save(array $messages); +} \ No newline at end of file diff --git a/system/libraries/drivers/Log/Database.php b/system/libraries/drivers/Log/Database.php new file mode 100644 index 00000000..19db9747 --- /dev/null +++ b/system/libraries/drivers/Log/Database.php @@ -0,0 +1,40 @@ +config['group']) + ->insert($this->config['table']) + ->columns(array('date', 'level', 'message')); + + $run_insert = FALSE; + + foreach ($messages AS $message) + { + if ($this->config['log_levels'][$message['type']] <= $this->config['log_threshold']) + { + // Add new message to database + $insert->values($message); + + // There is data to insert + $run_insert = TRUE; + } + } + + // Update the database + if ($run_insert) + { + $insert->execute(); + } + } +} \ No newline at end of file diff --git a/system/libraries/drivers/Log/File.php b/system/libraries/drivers/Log/File.php new file mode 100644 index 00000000..6ad565b4 --- /dev/null +++ b/system/libraries/drivers/Log/File.php @@ -0,0 +1,44 @@ +config['log_directory'].'/'.date('Y-m-d').'.log'.EXT; + + if ( ! is_file($filename)) + { + // Write the SYSPATH checking header + file_put_contents($filename, + ''.PHP_EOL.PHP_EOL); + + // Prevent external writes + chmod($filename, $this->config['posix_permissions']); + } + + foreach ($messages AS $message) + { + if ($this->config['log_levels'][$message['type']] <= $this->config['log_threshold']) + { + // Add a new message line + $messages_to_write[] = date($this->config['date_format'], $message['date']).' --- '.$message['type'].': '.$message['message']; + } + } + + if ( ! empty($messages_to_write)) + { + // Write messages to log file + file_put_contents($filename, implode(PHP_EOL, $messages_to_write).PHP_EOL, FILE_APPEND); + } + } +} \ No newline at end of file diff --git a/system/libraries/drivers/Log/Syslog.php b/system/libraries/drivers/Log/Syslog.php new file mode 100644 index 00000000..5da5d255 --- /dev/null +++ b/system/libraries/drivers/Log/Syslog.php @@ -0,0 +1,34 @@ + LOG_ERR, + 'alert' => LOG_WARNING, + 'info' => LOG_INFO, + 'debug' => LOG_DEBUG); + + public function save(array $messages) + { + // Open the connection to syslog + openlog($this->config['ident'], LOG_CONS, LOG_USER); + + do + { + // Load the next message + list ($date, $type, $text) = array_shift($messages); + + syslog($this->syslog_levels[$type], $text); + } + while ( ! empty($messages)); + + // Close connection to syslog + closelog(); + } +} \ No newline at end of file diff --git a/system/libraries/drivers/Session.php b/system/libraries/drivers/Session.php index fb58c8d3..759ccd84 100644 --- a/system/libraries/drivers/Session.php +++ b/system/libraries/drivers/Session.php @@ -2,12 +2,12 @@ /** * Session driver interface * - * $Id: Session.php 3769 2008-12-15 00:48:56Z zombor $ + * $Id: Session.php 4679 2009-11-10 01:45:52Z isaiah $ * * @package Core * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html + * @copyright (c) 2007-2009 Kohana Team + * @license http://kohanaphp.com/license */ interface Session_Driver { diff --git a/system/libraries/drivers/Session/Cache.php b/system/libraries/drivers/Session/Cache.php index 45e49495..c0d4d0a4 100644 --- a/system/libraries/drivers/Session/Cache.php +++ b/system/libraries/drivers/Session/Cache.php @@ -10,12 +10,12 @@ * Lifetime does not need to be set as it is * overridden by the session expiration setting. * - * $Id: Cache.php 4431 2009-07-01 03:41:41Z kiall $ + * $Id: Cache.php 4679 2009-11-10 01:45:52Z isaiah $ * * @package Core * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html + * @copyright (c) 2007-2009 Kohana Team + * @license http://kohanaphp.com/license */ class Session_Cache_Driver implements Session_Driver { @@ -30,7 +30,7 @@ class Session_Cache_Driver implements Session_Driver { $this->encrypt = new Encrypt; } - Kohana::log('debug', 'Session Cache Driver Initialized'); + Kohana_Log::add('debug', 'Session Cache Driver Initialized'); } public function open($path, $name) @@ -48,7 +48,7 @@ class Session_Cache_Driver implements Session_Driver { // Test the config group name if (($config = Kohana::config('cache.'.$config)) === NULL) - throw new Kohana_Exception('cache.undefined_group', $name); + throw new Kohana_Exception('The :group: group is not defined in your configuration.', array(':group:' => $name)); } $config['lifetime'] = (Kohana::config('session.expiration') == 0) ? 86400 : Kohana::config('session.expiration'); diff --git a/system/libraries/drivers/Session/Cookie.php b/system/libraries/drivers/Session/Cookie.php index 4cf18fc2..ef575cab 100644 --- a/system/libraries/drivers/Session/Cookie.php +++ b/system/libraries/drivers/Session/Cookie.php @@ -2,12 +2,12 @@ /** * Session cookie driver. * - * $Id: Cookie.php 4431 2009-07-01 03:41:41Z kiall $ + * $Id: Cookie.php 4679 2009-11-10 01:45:52Z isaiah $ * * @package Core * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html + * @copyright (c) 2007-2009 Kohana Team + * @license http://kohanaphp.com/license */ class Session_Cookie_Driver implements Session_Driver { @@ -23,7 +23,7 @@ class Session_Cookie_Driver implements Session_Driver { $this->encrypt = Encrypt::instance(); } - Kohana::log('debug', 'Session Cookie Driver Initialized'); + Kohana_Log::add('debug', 'Session Cookie Driver Initialized'); } public function open($path, $name) @@ -55,7 +55,7 @@ class Session_Cookie_Driver implements Session_Driver { if (strlen($data) > 4048) { - Kohana::log('error', 'Session ('.$id.') data exceeds the 4KB limit, ignoring write.'); + Kohana_Log::add('error', 'Session ('.$id.') data exceeds the 4KB limit, ignoring write.'); return FALSE; } diff --git a/system/libraries/drivers/Session/Database.php b/system/libraries/drivers/Session/Database.php index 490875a1..cbe76001 100644 --- a/system/libraries/drivers/Session/Database.php +++ b/system/libraries/drivers/Session/Database.php @@ -2,12 +2,12 @@ /** * Session database driver. * - * $Id: Database.php 4431 2009-07-01 03:41:41Z kiall $ + * $Id: Database.php 4679 2009-11-10 01:45:52Z isaiah $ * * @package Core * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html + * @copyright (c) 2007-2009 Kohana Team + * @license http://kohanaphp.com/license */ class Session_Database_Driver implements Session_Driver { @@ -58,10 +58,7 @@ class Session_Database_Driver implements Session_Driver { } } - // Load database - $this->db = Database::instance($this->db); - - Kohana::log('debug', 'Session Database Driver Initialized'); + Kohana_Log::add('debug', 'Session Database Driver Initialized'); } public function open($path, $name) @@ -77,7 +74,11 @@ class Session_Database_Driver implements Session_Driver { public function read($id) { // Load the session - $query = $this->db->from($this->table)->where('session_id', $id)->limit(1)->get()->result(TRUE); + $query = db::select('data') + ->from($this->table) + ->where('session_id', '=', $id) + ->limit(1) + ->execute($this->db); if ($query->count() === 0) { @@ -111,7 +112,8 @@ class Session_Database_Driver implements Session_Driver { if ($this->session_id === NULL) { // Insert a new session - $query = $this->db->insert($this->table, $data); + $query = db::insert($this->table, $data) + ->execute($this->db); } elseif ($id === $this->session_id) { @@ -119,12 +121,18 @@ class Session_Database_Driver implements Session_Driver { unset($data['session_id']); // Update the existing session - $query = $this->db->update($this->table, $data, array('session_id' => $id)); + $query = db::update($this->table) + ->set($data) + ->where('session_id', '=', $id) + ->execute($this->db); } else { // Update the session and id - $query = $this->db->update($this->table, $data, array('session_id' => $this->session_id)); + $query = db::update($this->table) + ->set($data) + ->where('session_id', '=', $this->session_id) + ->execute($this->db); // Set the new session id $this->session_id = $id; @@ -136,7 +144,9 @@ class Session_Database_Driver implements Session_Driver { public function destroy($id) { // Delete the requested session - $this->db->delete($this->table, array('session_id' => $id)); + db::delete($this->table) + ->where('session_id', '=', $id) + ->execute($this->db); // Session id is no longer valid $this->session_id = NULL; @@ -156,9 +166,11 @@ class Session_Database_Driver implements Session_Driver { public function gc($maxlifetime) { // Delete all expired sessions - $query = $this->db->delete($this->table, array('last_activity <' => time() - $maxlifetime)); + $query = db::delete($this->table) + ->where('last_activity', '<', time() - $maxlifetime) + ->execute($this->db); - Kohana::log('debug', 'Session garbage collected: '.$query->count().' row(s) deleted.'); + Kohana_Log::add('debug', 'Session garbage collected: '.$query->count().' row(s) deleted.'); return TRUE; } -- cgit v1.2.3 From e79e0d1980f186edb97f242b378db8e5a7579db3 Mon Sep 17 00:00:00 2001 From: Bharat Mediratta Date: Thu, 26 Nov 2009 20:47:30 -0800 Subject: Make ORM::__call() always return an ORM so that it's chainable. --- system/libraries/ORM.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'system/libraries') diff --git a/system/libraries/ORM.php b/system/libraries/ORM.php index 1d1f48f5..eff22fc0 100644 --- a/system/libraries/ORM.php +++ b/system/libraries/ORM.php @@ -242,7 +242,7 @@ class ORM_Core { else { // Support for things like reset_select, reset_write, list_tables - return $this->db_builder->$method(); + $this->db_builder->$method(); } break; case 1: -- cgit v1.2.3 From 9285c8c66c530196399eb05bb5561c3fa5538335 Mon Sep 17 00:00:00 2001 From: Bharat Mediratta Date: Mon, 21 Dec 2009 20:05:27 -0800 Subject: Updated Kohana to r4724 --- system/config/cache.php | 3 + system/config/database.php | 3 +- system/core/Kohana.php | 39 +- system/core/Kohana_Exception.php | 4 +- system/helpers/form.php | 28 +- system/helpers/inflector.php | 6 +- system/helpers/request.php | 4 +- system/helpers/security.php | 13 +- system/helpers/text.php | 46 +- system/helpers/url.php | 21 +- system/helpers/utf8.php | 2 +- system/libraries/Cache.php | 87 +- system/libraries/Controller.php | 8 +- system/libraries/Database.php | 23 +- system/libraries/Database_Builder.php | 103 +- system/libraries/Database_Mysql.php | 9 +- system/libraries/Database_Mysqli.php | 32 +- system/libraries/Input.php | 8 +- system/libraries/Kohana_PHP_Exception.php | 4 +- system/libraries/ORM.php | 56 +- system/libraries/Profiler.php | 4 +- system/libraries/Router.php | 10 +- system/libraries/Validation.php | 155 +- system/libraries/drivers/Cache/File.php | 4 +- system/libraries/drivers/Cache/Memcache.php | 7 +- system/libraries/drivers/Cache/Xcache.php | 2 +- system/messages/core.php | 9 +- system/messages/validation/default.php | 17 + system/vendor/Markdown.php | 2909 --------------------------- 29 files changed, 458 insertions(+), 3158 deletions(-) create mode 100644 system/messages/validation/default.php delete mode 100644 system/vendor/Markdown.php (limited to 'system/libraries') diff --git a/system/config/cache.php b/system/config/cache.php index 68682c0a..76af4f6a 100644 --- a/system/config/cache.php +++ b/system/config/cache.php @@ -19,10 +19,13 @@ * thirty minutes. Specific lifetime can also be set when creating a new cache. * Setting this to 0 will never automatically delete caches. * + * prefix - Adds a prefix to all keys and tags. This can have a severe performance impact. + * */ $config['default'] = array ( 'driver' => 'file', 'params' => array('directory' => APPPATH.'cache', 'gc_probability' => 1000), 'lifetime' => 1800, + 'prefix' => NULL ); diff --git a/system/config/database.php b/system/config/database.php index 2e53fa2b..36e4614c 100644 --- a/system/config/database.php +++ b/system/config/database.php @@ -35,7 +35,8 @@ $config['default'] = array 'host' => 'localhost', 'port' => FALSE, 'socket' => FALSE, - 'database' => 'kohana' + 'database' => 'kohana', + 'params' => NULL, ), 'character_set' => 'utf8', 'table_prefix' => '', diff --git a/system/core/Kohana.php b/system/core/Kohana.php index 5258d635..740adb80 100644 --- a/system/core/Kohana.php +++ b/system/core/Kohana.php @@ -2,7 +2,7 @@ /** * Provides Kohana-specific helper functions. This is where the magic happens! * - * $Id: Kohana.php 4679 2009-11-10 01:45:52Z isaiah $ + * $Id: Kohana.php 4724 2009-12-21 16:28:54Z isaiah $ * * @package Core * @author Kohana Team @@ -45,6 +45,9 @@ abstract class Kohana_Core { protected static $internal_cache_key; protected static $internal_cache_encrypt; + // Server API that PHP is using. Allows testing of different APIs. + public static $server_api = PHP_SAPI; + /** * Sets up the PHP environment. Adds error/exception handling, output * buffering, and adds an auto-loading method for loading classes. @@ -162,6 +165,35 @@ abstract class Kohana_Core { // Set and validate the timezone date_default_timezone_set(Kohana::config('locale.timezone')); + // register_globals is enabled + if (ini_get('register_globals')) + { + if (isset($_REQUEST['GLOBALS'])) + { + // Prevent GLOBALS override attacks + exit('Global variable overload attack.'); + } + + // Destroy the REQUEST global + $_REQUEST = array(); + + // These globals are standard and should not be removed + $preserve = array('GLOBALS', '_REQUEST', '_GET', '_POST', '_FILES', '_COOKIE', '_SERVER', '_ENV', '_SESSION'); + + // This loop has the same effect as disabling register_globals + foreach (array_diff(array_keys($GLOBALS), $preserve) as $key) + { + global $$key; + $$key = NULL; + + // Unset the global variable + unset($GLOBALS[$key], $$key); + } + + // Warn the developer about register globals + Kohana_Log::add('debug', 'Disable register_globals! It is evil and deprecated: http://php.net/register_globals'); + } + // Enable Kohana routing Event::add('system.routing', array('Router', 'find_uri')); Event::add('system.routing', array('Router', 'setup')); @@ -602,7 +634,7 @@ abstract class Kohana_Core { header('Content-Encoding: '.$compress); // Sending Content-Length in CGI can result in unexpected behavior - if (stripos(PHP_SAPI, 'cgi') === FALSE) + if (stripos(Kohana::$server_api, 'cgi') === FALSE) { header('Content-Length: '.strlen($output)); } @@ -876,9 +908,6 @@ abstract class Kohana_Core { $group = explode('.', $key, 2); $group = $group[0]; - // Get locale name - $locale = Kohana::config('locale.language.0'); - if ( ! isset(Kohana::$internal_cache['messages'][$group])) { // Messages for this group diff --git a/system/core/Kohana_Exception.php b/system/core/Kohana_Exception.php index cdb06a62..2eb28f75 100644 --- a/system/core/Kohana_Exception.php +++ b/system/core/Kohana_Exception.php @@ -2,7 +2,7 @@ /** * Kohana Exceptions * - * $Id: Kohana_Exception.php 4679 2009-11-10 01:45:52Z isaiah $ + * $Id: Kohana_Exception.php 4692 2009-12-04 15:59:44Z cbandy $ * * @package Core * @author Kohana Team @@ -197,7 +197,7 @@ class Kohana_Exception_Core extends Exception { echo Kohana_Exception::text($e), "\n"; } - if (PHP_SAPI === 'cli') + if (Kohana::$server_api === 'cli') { // Exit with an error status exit(1); diff --git a/system/helpers/form.php b/system/helpers/form.php index 901edc91..4225b1b6 100644 --- a/system/helpers/form.php +++ b/system/helpers/form.php @@ -2,7 +2,7 @@ /** * Form helper class. * - * $Id: form.php 4679 2009-11-10 01:45:52Z isaiah $ + * $Id: form.php 4699 2009-12-08 18:45:14Z isaiah $ * * @package Core * @author Kohana Team @@ -421,32 +421,6 @@ class form_Core { if (empty($attr)) return ''; - if (isset($attr['name']) AND empty($attr['id']) AND strpos($attr['name'], '[') === FALSE) - { - if ($type === NULL AND ! empty($attr['type'])) - { - // Set the type by the attributes - $type = $attr['type']; - } - - switch ($type) - { - case 'text': - case 'textarea': - case 'password': - case 'select': - case 'checkbox': - case 'file': - case 'image': - case 'button': - case 'submit': - case 'hidden': - // Only specific types of inputs use name to id matching - $attr['id'] = $attr['name']; - break; - } - } - $order = array ( 'action', diff --git a/system/helpers/inflector.php b/system/helpers/inflector.php index 9bd281db..5a2910c0 100644 --- a/system/helpers/inflector.php +++ b/system/helpers/inflector.php @@ -2,7 +2,7 @@ /** * Inflector helper class. * - * $Id: inflector.php 4679 2009-11-10 01:45:52Z isaiah $ + * $Id: inflector.php 4722 2009-12-19 17:47:34Z isaiah $ * * @package Core * @author Kohana Team @@ -241,9 +241,9 @@ class inflector_Core { } /** - * Makes an underscored or dashed phrase human-reable. + * Makes an underscored or dashed phrase human-readable. * - * @param string phrase to make human-reable + * @param string phrase to make human-readable * @return string */ public static function humanize($str) diff --git a/system/helpers/request.php b/system/helpers/request.php index 4770d64b..31afee4e 100644 --- a/system/helpers/request.php +++ b/system/helpers/request.php @@ -2,7 +2,7 @@ /** * Request helper class. * - * $Id: request.php 4679 2009-11-10 01:45:52Z isaiah $ + * $Id: request.php 4692 2009-12-04 15:59:44Z cbandy $ * * @package Core * @author Kohana Team @@ -61,7 +61,7 @@ class request_Core { */ public static function protocol() { - if (PHP_SAPI === 'cli') + if (Kohana::$server_api === 'cli') { return NULL; } diff --git a/system/helpers/security.php b/system/helpers/security.php index 9eb82a58..33e5118e 100644 --- a/system/helpers/security.php +++ b/system/helpers/security.php @@ -2,7 +2,7 @@ /** * Security helper class. * - * $Id: security.php 4679 2009-11-10 01:45:52Z isaiah $ + * $Id: security.php 4698 2009-12-08 18:39:33Z isaiah $ * * @package Core * @author Kohana Team @@ -34,15 +34,4 @@ class security_Core { return preg_replace('#\s]*)["\']?[^>]*)?>#is', '$1', $str); } - /** - * Remove PHP tags from a string. - * - * @param string string to sanitize - * @return string - */ - public static function encode_php_tags($str) - { - return str_replace(array(''), array('<?', '?>'), $str); - } - } // End security \ No newline at end of file diff --git a/system/helpers/text.php b/system/helpers/text.php index 66bcd243..ed7f9cbf 100644 --- a/system/helpers/text.php +++ b/system/helpers/text.php @@ -2,7 +2,7 @@ /** * Text helper class. * - * $Id: text.php 4679 2009-11-10 01:45:52Z isaiah $ + * $Id: text.php 4689 2009-12-02 01:39:24Z isaiah $ * * @package Core * @author Kohana Team @@ -298,27 +298,37 @@ class text_Core { */ public static function auto_link_urls($text) { - // Finds all http/https/ftp/ftps links that are not part of an existing html anchor - if (preg_match_all('~\b(?)(?:ht|f)tps?://\S+(?:/|\b)~i', $text, $matches)) - { - foreach ($matches[0] as $match) - { - // Replace each link with an anchor - $text = str_replace($match, html::anchor($match), $text); - } - } - // Find all naked www.links.com (without http://) - if (preg_match_all('~\b(? $name)); + throw new Cache_Exception('The :group: group is not defined in your configuration.', array(':group:' => $name)); } if (is_array($config)) @@ -74,7 +74,7 @@ class Cache_Core { // Load the driver if ( ! Kohana::auto_load($driver)) - throw new Kohana_Exception('The :driver: driver for the :class: library could not be found', + throw new Cache_Exception('The :driver: driver for the :class: library could not be found', array(':driver:' => $this->config['driver'], ':class:' => get_class($this))); // Initialize the driver @@ -82,7 +82,7 @@ class Cache_Core { // Validate the driver if ( ! ($this->driver instanceof Cache_Driver)) - throw new Kohana_Exception('The :driver: driver for the :library: library must implement the :interface: interface', + throw new Cache_Exception('The :driver: driver for the :library: library must implement the :interface: interface', array(':driver:' => $this->config['driver'], ':library:' => get_class($this), ':interface:' => 'Cache_Driver')); Kohana_Log::add('debug', 'Cache Library initialized'); @@ -103,6 +103,16 @@ class Cache_Core { $key = array($key => $value); } + if ($this->config['prefix'] !== NULL) + { + $key = $this->add_prefix($key); + + if ($tags !== NULL) + { + $tags = $this->add_prefix($tags, FALSE); + } + } + return $this->driver->set($key, $tags, $lifetime); } @@ -119,6 +129,17 @@ class Cache_Core { $single = TRUE; } + if ($this->config['prefix'] !== NULL) + { + $keys = $this->add_prefix($keys, FALSE); + + if ( ! $single) + { + return $this->strip_prefix($this->driver->get($keys, $single)); + } + + } + return $this->driver->get($keys, $single); } @@ -132,7 +153,15 @@ class Cache_Core { $tags = array($tags); } - return $this->driver->get_tag($tags); + if ($this->config['prefix'] !== NULL) + { + $tags = $this->add_prefix($tags, FALSE); + return $this->strip_prefix($this->driver->get_tag($tags)); + } + else + { + return $this->driver->get_tag($tags); + } } /** @@ -145,6 +174,11 @@ class Cache_Core { $keys = array($keys); } + if ($this->config['prefix'] !== NULL) + { + $keys = $this->add_prefix($keys, FALSE); + } + return $this->driver->delete($keys); } @@ -158,6 +192,11 @@ class Cache_Core { $tags = array($tags); } + if ($this->config['prefix'] !== NULL) + { + $tags = $this->add_prefix($tags, FALSE); + } + return $this->driver->delete_tag($tags); } @@ -168,4 +207,44 @@ class Cache_Core { { return $this->driver->delete_all(); } + + /** + * Add a prefix to keys or tags + */ + protected function add_prefix($array, $to_key = TRUE) + { + $out = array(); + + foreach($array as $key => $value) + { + if ($to_key) + { + $out[$this->config['prefix'].$key] = $value; + } + else + { + $out[$key] = $this->config['prefix'].$value; + } + } + + return $out; + } + + /** + * Strip a prefix to keys or tags + */ + protected function strip_prefix($array) + { + $out = array(); + + $start = strlen($this->config['prefix']); + + foreach($array as $key => $value) + { + $out[substr($key, $start)] = $value; + } + + return $out; + } + } // End Cache Library \ No newline at end of file diff --git a/system/libraries/Controller.php b/system/libraries/Controller.php index da84bfc1..830c06e5 100644 --- a/system/libraries/Controller.php +++ b/system/libraries/Controller.php @@ -3,7 +3,7 @@ * Kohana Controller class. The controller class must be extended to work * properly, so this class is defined as abstract. * - * $Id: Controller.php 4679 2009-11-10 01:45:52Z isaiah $ + * $Id: Controller.php 4721 2009-12-17 23:02:07Z isaiah $ * * @package Core * @author Kohana Team @@ -27,12 +27,6 @@ abstract class Controller_Core { // Set the instance to the first controller loaded Kohana::$instance = $this; } - - // URI should always be available - $this->uri = URI::instance(); - - // Input should always be available - $this->input = Input::instance(); } /** diff --git a/system/libraries/Database.php b/system/libraries/Database.php index d3716c59..38a38fbf 100644 --- a/system/libraries/Database.php +++ b/system/libraries/Database.php @@ -1,9 +1,9 @@ cache instanceof Cache) + if ($this->cache instanceof Cache AND ($type == NULL OR $type == Database::CROSS_REQUEST)) { // Using cross-request Cache library if ($sql === TRUE) @@ -370,7 +373,7 @@ abstract class Database_Core { $this->cache->delete_all(); } } - elseif (is_array($this->cache)) + elseif (is_array($this->cache) AND ($type == NULL OR $type == Database::PER_REQUEST)) { // Using per-request memory cache if ($sql === TRUE) diff --git a/system/libraries/Database_Builder.php b/system/libraries/Database_Builder.php index 5a5a058c..4e6951e7 100644 --- a/system/libraries/Database_Builder.php +++ b/system/libraries/Database_Builder.php @@ -29,6 +29,7 @@ class Database_Builder_Core { protected $columns = array(); protected $values = array(); protected $type; + protected $distinct = FALSE; // TTL for caching (using Cache library) protected $ttl = FALSE; @@ -77,7 +78,8 @@ class Database_Builder_Core { if ($this->type === Database::SELECT) { // SELECT columns FROM table - $sql = 'SELECT '.$this->compile_select(); + $sql = $this->distinct ? 'SELECT DISTINCT ' : 'SELECT '; + $sql .= $this->compile_select(); if ( ! empty($this->from)) { @@ -146,7 +148,13 @@ class Database_Builder_Core { foreach ($this->select as $alias => $name) { - if (is_string($alias)) + if ($name instanceof Database_Builder) + { + // Using a subquery + $name->db = $this->db; + $vals[] = '('.(string) $name.') AS '.$this->db->quote_column($alias); + } + elseif (is_string($alias)) { $vals[] = $this->db->quote_column($name, $alias); } @@ -374,28 +382,48 @@ class Database_Builder_Core { /** * Add conditions to the HAVING clause (AND) * - * @param mixed Column name or array of columns => vals + * @param mixed Column name or array of triplets * @param string Operation to perform * @param mixed Value * @return Database_Builder */ public function and_having($columns, $op = '=', $value = NULL) { - $this->having[] = array('AND' => array($columns, $op, $value)); + if (is_array($columns)) + { + foreach ($columns as $column) + { + $this->having[] = array('AND' => $column); + } + } + else + { + $this->having[] = array('AND' => array($columns, $op, $value)); + } return $this; } /** * Add conditions to the HAVING clause (OR) * - * @param mixed Column name or array of columns => vals + * @param mixed Column name or array of triplets * @param string Operation to perform * @param mixed Value * @return Database_Builder */ public function or_having($columns, $op = '=', $value = NULL) { - $this->having[] = array('OR' => array($columns, $op, $value)); + if (is_array($columns)) + { + foreach ($columns as $column) + { + $this->having[] = array('OR' => $column); + } + } + else + { + $this->having[] = array('OR' => array($columns, $op, $value)); + } return $this; } @@ -408,13 +436,25 @@ class Database_Builder_Core { */ public function order_by($columns, $direction = NULL) { - if (is_string($columns)) + if (is_array($columns)) { - $columns = array($columns => $direction); + foreach ($columns as $column => $direction) + { + if (is_string($column)) + { + $this->order_by[] = array($column => $direction); + } + else + { + // $direction is the column name when the array key is numeric + $this->order_by[] = array($direction => NULL); + } + } + } + else + { + $this->order_by[] = array($columns => $direction); } - - $this->order_by[] = $columns; - return $this; } @@ -542,28 +582,48 @@ class Database_Builder_Core { /** * Add conditions to the WHERE clause (AND) * - * @param mixed Column name or array of columns => vals + * @param mixed Column name or array of triplets * @param string Operation to perform * @param mixed Value * @return Database_Builder */ public function and_where($columns, $op = '=', $value = NULL) { - $this->where[] = array('AND' => array($columns, $op, $value)); + if (is_array($columns)) + { + foreach ($columns as $column) + { + $this->where[] = array('AND' => $column); + } + } + else + { + $this->where[] = array('AND' => array($columns, $op, $value)); + } return $this; } /** * Add conditions to the WHERE clause (OR) * - * @param mixed Column name or array of columns => vals + * @param mixed Column name or array of triplets * @param string Operation to perform * @param mixed Value * @return Database_Builder */ public function or_where($columns, $op = '=', $value = NULL) { - $this->where[] = array('OR' => array($columns, $op, $value)); + if (is_array($columns)) + { + foreach ($columns as $column) + { + $this->where[] = array('OR' => $column); + } + } + else + { + $this->where[] = array('OR' => array($columns, $op, $value)); + } return $this; } @@ -779,6 +839,19 @@ class Database_Builder_Core { return $this; } + /** + * Create a SELECT query and specify selected columns + * + * @param string|array column name or array(alias => column) + * @return Database_Builder + */ + public function select_distinct($columns = NULL) + { + $this->select($columns); + $this->distinct = TRUE; + return $this; + } + /** * Create an UPDATE query * diff --git a/system/libraries/Database_Mysql.php b/system/libraries/Database_Mysql.php index cb531194..a5775037 100644 --- a/system/libraries/Database_Mysql.php +++ b/system/libraries/Database_Mysql.php @@ -2,7 +2,7 @@ /** * MySQL database connection. * - * $Id: Database_Mysql.php 4684 2009-11-18 14:26:48Z isaiah $ + * $Id: Database_Mysql.php 4712 2009-12-10 21:47:09Z cbandy $ * * @package Kohana * @author Kohana Team @@ -31,16 +31,15 @@ class Database_Mysql_Core extends Database { extract($this->config['connection']); - // Set the connection type - $connect = ($this->config['persistent'] === TRUE) ? 'mysql_pconnect' : 'mysql_connect'; - $host = isset($host) ? $host : $socket; $port = isset($port) ? ':'.$port : ''; try { // Connect to the database - $this->connection = $connect($host.$port, $user, $pass, TRUE); + $this->connection = ($this->config['persistent'] === TRUE) + ? mysql_pconnect($host.$port, $user, $pass, $params) + : mysql_connect($host.$port, $user, $pass, TRUE, $params); } catch (Kohana_PHP_Exception $e) { diff --git a/system/libraries/Database_Mysqli.php b/system/libraries/Database_Mysqli.php index 523fcb19..08ed99df 100644 --- a/system/libraries/Database_Mysqli.php +++ b/system/libraries/Database_Mysqli.php @@ -2,7 +2,7 @@ /** * MySQL database connection. * - * $Id: Database_Mysqli.php 4679 2009-11-10 01:45:52Z isaiah $ + * $Id: Database_Mysqli.php 4712 2009-12-10 21:47:09Z cbandy $ * * @package Kohana * @author Kohana Team @@ -29,29 +29,29 @@ class Database_Mysqli_Core extends Database_Mysql { $host = isset($host) ? $host : $socket; - if($this->connection = new mysqli($host, $user, $pass, $database, $port)) { + $mysqli = mysqli_init(); - if (isset($this->config['character_set'])) - { - // Set the character set - $this->set_charset($this->config['character_set']); - } + if ( ! $mysqli->real_connect($host, $user, $pass, $database, $port, $socket, $params)) + throw new Database_Exception('#:errno: :error', + array(':error' => $mysqli->connect_error, ':errno' => $mysqli->connect_errno)); - // Clear password after successful connect - $this->db_config['connection']['pass'] = NULL; + $this->connection = $mysqli; - return $this->connection; + if (isset($this->config['character_set'])) + { + // Set the character set + $this->set_charset($this->config['character_set']); } - - // Unable to connect to the database - throw new Database_Exception('#:errno: :error', - array(':error' => $this->connection->connect_error, - ':errno' => $this->connection->connect_errno)); } public function disconnect() { - return is_object($this->connection) and $this->connection->close(); + if (is_object($this->connection)) + { + $this->connection->close(); + } + + $this->connection = NULL; } public function set_charset($charset) diff --git a/system/libraries/Input.php b/system/libraries/Input.php index 83f0ed17..04403854 100644 --- a/system/libraries/Input.php +++ b/system/libraries/Input.php @@ -2,7 +2,7 @@ /** * Input library. * - * $Id: Input.php 4680 2009-11-10 01:57:00Z isaiah $ + * $Id: Input.php 4720 2009-12-17 21:15:03Z isaiah $ * * @package Core * @author Kohana Team @@ -54,7 +54,7 @@ class Input_Core { $_COOKIE = Input::clean($_COOKIE); $_SERVER = Input::clean($_SERVER); - if (PHP_SAPI == 'cli') + if (Kohana::$server_api === 'cli') { // Convert command line arguments $_SERVER['argv'] = Input::clean($_SERVER['argv']); @@ -311,7 +311,7 @@ class Input_Core { if (trim($data) === '') return $data; - if ($tool === TRUE) + if (is_bool($tool)) { $tool = 'default'; } @@ -371,7 +371,7 @@ class Input_Core { $data = html_entity_decode($data, ENT_COMPAT, 'UTF-8'); // Remove any attribute starting with "on" or xmlns - $data = preg_replace('#(<[^>]+?[\x00-\x20"\'])(?:on|xmlns)[^>]*+>#iu', '$1>', $data); + $data = preg_replace('#(?:on[a-z]+|xmlns)\s*=\s*[\'"\x00-\x20]?[^\'>"]*[\'"\x00-\x20]?\s?#iu', '', $data); // Remove javascript: and vbscript: protocols $data = preg_replace('#([a-z]*)[\x00-\x20]*=[\x00-\x20]*([`\'"]*)[\x00-\x20]*j[\x00-\x20]*a[\x00-\x20]*v[\x00-\x20]*a[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2nojavascript...', $data); diff --git a/system/libraries/Kohana_PHP_Exception.php b/system/libraries/Kohana_PHP_Exception.php index fca5b30b..019c686b 100644 --- a/system/libraries/Kohana_PHP_Exception.php +++ b/system/libraries/Kohana_PHP_Exception.php @@ -2,7 +2,7 @@ /** * Kohana PHP Error Exceptions * - * $Id: Kohana_PHP_Exception.php 4679 2009-11-10 01:45:52Z isaiah $ + * $Id: Kohana_PHP_Exception.php 4687 2009-11-30 21:59:26Z isaiah $ * * @package Core * @author Kohana Team @@ -89,7 +89,7 @@ class Kohana_PHP_Exception_Core extends Kohana_Exception { */ public static function shutdown_handler() { - if (Kohana_PHP_Exception::$enabled AND $error = error_get_last()) + if (Kohana_PHP_Exception::$enabled AND $error = error_get_last() AND (error_reporting() & $error['type'])) { // Fake an exception for nice debugging Kohana_Exception::handle(new Kohana_PHP_Exception($error['type'], $error['message'], $error['file'], $error['line'])); diff --git a/system/libraries/ORM.php b/system/libraries/ORM.php index eff22fc0..2f2feed5 100644 --- a/system/libraries/ORM.php +++ b/system/libraries/ORM.php @@ -8,7 +8,7 @@ * [ref-orm]: http://wikipedia.org/wiki/Object-relational_mapping * [ref-act]: http://wikipedia.org/wiki/Active_record * - * $Id: ORM.php 4682 2009-11-11 20:53:23Z isaiah $ + * $Id: ORM.php 4711 2009-12-10 20:40:52Z isaiah $ * * @package ORM * @author Kohana Team @@ -102,7 +102,7 @@ class ORM_Core { $this->object_name = strtolower(substr(get_class($this), 0, -6)); $this->object_plural = inflector::plural($this->object_name); - if (!isset($this->sorting)) + if ( ! isset($this->sorting)) { // Default sorting $this->sorting = array($this->primary_key => 'asc'); @@ -119,7 +119,7 @@ class ORM_Core { // Load an object $this->_load_values((array) $id); } - elseif (!empty($id)) + elseif ( ! empty($id)) { // Set the object's primary key, but don't load it until needed $this->object[$this->primary_key] = $id; @@ -234,7 +234,7 @@ class ORM_Core { switch ($num_args) { case 0: - if (in_array($method, array('open', 'close', 'cache'))) + if (in_array($method, array('open', 'and_open', 'or_open', 'close', 'cache'))) { // Should return ORM, not Database $this->db_builder->$method(); @@ -320,12 +320,12 @@ class ORM_Core { } // Foreign key lies in this table (this model belongs_to target model) - $where = array($model->foreign_key(TRUE) => $this->object[$this->foreign_key($column)]); + $where = array($model->foreign_key(TRUE), '=', $this->object[$this->foreign_key($column)]); } else { // Foreign key lies in the target table (this model has_one target model) - $where = array($this->foreign_key($column, $model->table_name) => $this->primary_key_value); + $where = array($this->foreign_key($column, $model->table_name), '=', $this->primary_key_value); } // one<>alias:one relationship @@ -524,16 +524,6 @@ class ORM_Core { unset($this->object[$column], $this->changed[$column], $this->related[$column]); } - /** - * Displays the primary key of a model when it is converted to a string. - * - * @return string - */ - public function __toString() - { - return (string) $this->primary_key_value; - } - /** * Returns the values of this object as an array. * @@ -665,7 +655,7 @@ class ORM_Core { if (is_array($id)) { // Search for all clauses - $this->db_builder->where($id); + $this->db_builder->where(array($id)); } else { @@ -767,6 +757,9 @@ class ORM_Core { ORM_Validation_Exception::handle_validation($this->table_name, $array); } + // Fields may have been modified by filters + $this->object = array_merge($this->object, $array->getArrayCopy()); + // Return validation status return $this; } @@ -782,8 +775,10 @@ class ORM_Core { if ( ! empty($this->changed)) { // Require model validation before saving - if (!$this->_valid) + if ( ! $this->_valid) + { $this->validate(); + } $data = array(); foreach ($this->changed as $column) @@ -881,8 +876,8 @@ class ORM_Core { } // Foreign keys for the join table - $object_fk = $this->foreign_key(NULL); - $related_fk = $model->foreign_key(NULL); + $object_fk = $this->foreign_key($join_table); + $related_fk = $model->foreign_key($join_table); if ( ! empty($added)) { @@ -909,6 +904,12 @@ class ORM_Core { } } + if ($this->saved() === TRUE) + { + // Clear the per-request database cache + $this->db->clear_cache(NULL, Database::PER_REQUEST); + } + return $this; } @@ -933,6 +934,9 @@ class ORM_Core { ->where($this->primary_key, '=', $id) ->execute($this->db); + // Clear the per-request database cache + $this->db->clear_cache(NULL, Database::PER_REQUEST); + return $this->clear(); } @@ -965,6 +969,9 @@ class ORM_Core { return $this; } + // Clear the per-request database cache + $this->db->clear_cache(NULL, Database::PER_REQUEST); + return $this->clear(); } @@ -1154,12 +1161,13 @@ class ORM_Core { * * @chainable * @param string SQL query to clear + * @param integer Type of cache to clear, Database::CROSS_REQUEST or Database::PER_REQUEST * @return ORM */ - public function clear_cache($sql = NULL) + public function clear_cache($sql = NULL, $type = NULL) { // Proxy to database - $this->db->clear_cache($sql); + $this->db->clear_cache($sql, $type); ORM::$column_cache = array(); @@ -1550,9 +1558,9 @@ class ORM_Core { */ protected function load_relations($table, ORM $model) { - $result = db::select(array('id' => $model->foreign_key(NULL))) + $result = db::select(array('id' => $model->foreign_key($table))) ->from($table) - ->where($this->foreign_key(NULL, $table), '=', $this->primary_key_value) + ->where($this->foreign_key($table, $table), '=', $this->primary_key_value) ->execute($this->db) ->as_object(); diff --git a/system/libraries/Profiler.php b/system/libraries/Profiler.php index b7a5ecae..940e365d 100644 --- a/system/libraries/Profiler.php +++ b/system/libraries/Profiler.php @@ -8,7 +8,7 @@ * POST Data - The name and values of any POST data submitted to the current page. * Cookie Data - All cookies sent for the current request. * - * $Id: Profiler.php 4679 2009-11-10 01:45:52Z isaiah $ + * $Id: Profiler.php 4719 2009-12-17 04:31:48Z isaiah $ * * @package Profiler * @author Kohana Team @@ -101,7 +101,7 @@ class Profiler_Core { // Don't display if there's no profiles if (empty(Profiler::$profiles)) - return $output; + return Kohana::$output; $styles = ''; foreach (Profiler::$profiles as $profile) diff --git a/system/libraries/Router.php b/system/libraries/Router.php index 82dbb9b5..18e01af2 100644 --- a/system/libraries/Router.php +++ b/system/libraries/Router.php @@ -2,7 +2,7 @@ /** * Router * - * $Id: Router.php 4679 2009-11-10 01:45:52Z isaiah $ + * $Id: Router.php 4693 2009-12-04 17:11:16Z cbandy $ * * @package Core * @author Kohana Team @@ -38,7 +38,7 @@ class Router_Core { if ( ! empty($_SERVER['QUERY_STRING'])) { // Set the query string to the current query string - Router::$query_string = '?'.trim(urldecode($_SERVER['QUERY_STRING']), '&/'); + Router::$query_string = '?'.urldecode(trim($_SERVER['QUERY_STRING'], '&')); } if (Router::$routes === NULL) @@ -173,7 +173,7 @@ class Router_Core { */ public static function find_uri() { - if (PHP_SAPI === 'cli') + if (Kohana::$server_api === 'cli') { // Command line requires a bit of hacking if (isset($_SERVER['argv'][1])) @@ -181,9 +181,9 @@ class Router_Core { Router::$current_uri = $_SERVER['argv'][1]; // Remove GET string from segments - if (($query = strpos(Router::$current_uri, '?')) !== FALSE) + if (strpos(Router::$current_uri, '?') !== FALSE) { - list (Router::$current_uri, $query) = explode('?', Router::$current_uri, 2); + list(Router::$current_uri, $query) = explode('?', Router::$current_uri, 2); // Parse the query string into $_GET parse_str($query, $_GET); diff --git a/system/libraries/Validation.php b/system/libraries/Validation.php index f340e63c..47f34584 100644 --- a/system/libraries/Validation.php +++ b/system/libraries/Validation.php @@ -2,7 +2,7 @@ /** * Validation library. * - * $Id: Validation.php 4679 2009-11-10 01:45:52Z isaiah $ + * $Id: Validation.php 4713 2009-12-10 22:25:38Z isaiah $ * * @package Validation * @author Kohana Team @@ -26,12 +26,12 @@ class Validation_Core extends ArrayObject { protected $errors = array(); protected $messages = array(); + // Field labels + protected $labels = array(); + // Fields that are expected to be arrays protected $array_fields = array(); - // Checks if there is data to validate. - protected $submitted; - /** * Creates a new Validation instance. * @@ -52,9 +52,6 @@ class Validation_Core extends ArrayObject { */ public function __construct(array $array) { - // The array is submitted if the array is not empty - $this->submitted = ! empty($array); - parent::__construct($array, ArrayObject::ARRAY_AS_PROPS | ArrayObject::STD_PROP_LIST); } @@ -85,21 +82,6 @@ class Validation_Core extends ArrayObject { return $copy; } - /** - * Test if the data has been submitted. - * - * @return boolean - */ - public function submitted($value = NULL) - { - if (is_bool($value)) - { - $this->submitted = $value; - } - - return $this->submitted; - } - /** * Returns an array of all the field names that have filters, rules, or callbacks. * @@ -196,6 +178,42 @@ class Validation_Core extends ArrayObject { return $this; } + /** + * Sets or overwrites the label name for a field. + * + * @param string field name + * @param string label + * @return $this + */ + public function label($field, $label = NULL) + { + if ($label === NULL AND ($field !== TRUE OR $field !== '*') AND ! isset($this->labels[$field])) + { + // Set the field label to the field name + $this->labels[$field] = ucfirst(preg_replace('/[^\pL]+/u', ' ', $field)); + } + elseif ($label !== NULL) + { + // Set the label for this field + $this->labels[$field] = $label; + } + + return $this; + } + + /** + * Sets labels using an array. + * + * @param array list of field => label names + * @return $this + */ + public function labels(array $labels) + { + $this->labels = $labels + $this->labels; + + return $this; + } + /** * Converts a filter, rule, or callback into a fully-qualified callback array. * @@ -338,6 +356,9 @@ class Validation_Core extends ArrayObject { $rules = func_get_args(); $rules = array_slice($rules, 1); + // Set a default field label + $this->label($field); + if ($field === TRUE) { // Use wildcard @@ -412,6 +433,9 @@ class Validation_Core extends ArrayObject { $callbacks = func_get_args(); $callbacks = array_slice($callbacks, 1); + // Set a default field label + $this->label($field); + if ($field === TRUE) { // Use wildcard @@ -471,9 +495,6 @@ class Validation_Core extends ArrayObject { } } - if ($this->submitted === FALSE) - return FALSE; - foreach ($this->rules as $field => $callbacks) { foreach ($callbacks as $callback) @@ -534,6 +555,7 @@ class Validation_Core extends ArrayObject { if (($result == $is_false)) { + $rule = $is_false ? '!'.$rule : $rule; $this->add_error($field, $rule, $args); // Stop validating this field when an error is found @@ -618,46 +640,6 @@ class Validation_Core extends ArrayObject { return $this; } - /** - * Sets or returns the message for an input. - * - * @chainable - * @param string input key - * @param string message to set - * @return string|object - */ - public function message($input = NULL, $message = NULL) - { - if ($message === NULL) - { - if ($input === NULL) - { - $messages = array(); - $keys = array_keys($this->messages); - - foreach ($keys as $input) - { - $messages[] = $this->message($input); - } - - return implode("\n", $messages); - } - - // Return nothing if no message exists - if (empty($this->messages[$input])) - return ''; - - // Return the HTML message string - return $this->messages[$input]; - } - else - { - $this->messages[$input] = $message; - } - - return $this; - } - /** * Return the errors array. * @@ -677,18 +659,49 @@ class Validation_Core extends ArrayObject { } else { - $errors = array(); foreach ($this->errors as $input => $error) { - // Key for this input error - $key = "$file.$input.$error[0]"; + // Locations to check for error messages + $error_locations = array + ( + "validation/{$file}.{$input}.{$error[0]}", + "validation/{$file}.{$input}.default", + "validation/default.{$error[0]}" + ); + + if (($message = Kohana::message($error_locations[0])) !== $error_locations[0]) + { + // Found a message for this field and error + } + elseif (($message = Kohana::message($error_locations[1])) !== $error_locations[1]) + { + // Found a default message for this field + } + elseif (($message = Kohana::message($error_locations[2])) !== $error_locations[2]) + { + // Found a default message for this error + } + else + { + // No message exists, display the path expected + $message = "validation/{$file}.{$input}.{$error[0]}"; + } - if (($errors[$input] = Kohana::message('validation/'.$key, $error[1])) === $key) + // Start the translation values list + $values = array(':field' => __($this->labels[$input])); + + if ( ! empty($error[1])) { - // Get the default error message - $errors[$input] = Kohana::message("$file.$input.default"); + foreach ($error[1] as $key => $value) + { + // Add each parameter as a numbered value, starting from 1 + $values[':param'.($key + 1)] = __($value); + } } + + // Translate the message using the default language + $errors[$input] = __($message, $values); } return $errors; diff --git a/system/libraries/drivers/Cache/File.php b/system/libraries/drivers/Cache/File.php index fc20c22d..d6ec0378 100644 --- a/system/libraries/drivers/Cache/File.php +++ b/system/libraries/drivers/Cache/File.php @@ -183,7 +183,7 @@ class Cache_File_Driver extends Cache_Driver { // Get the id from the filename list($key, $junk) = explode('~', basename($path), 2); - if (($data = $this->get($key)) !== FALSE) + if (($data = $this->get($key, TRUE)) !== FALSE) { // Add the result to the array $result[$key] = $data; @@ -211,7 +211,7 @@ class Cache_File_Driver extends Cache_Driver { // Remove the cache file if ( ! unlink($path)) { - Kohana::log('error', 'Cache: Unable to delete cache file: '.$path); + Kohana_Log::add('error', 'Cache: Unable to delete cache file: '.$path); $success = FALSE; } } diff --git a/system/libraries/drivers/Cache/Memcache.php b/system/libraries/drivers/Cache/Memcache.php index 636191d4..13d61d82 100644 --- a/system/libraries/drivers/Cache/Memcache.php +++ b/system/libraries/drivers/Cache/Memcache.php @@ -17,7 +17,7 @@ class Cache_Memcache_Driver extends Cache_Driver { public function __construct($config) { if ( ! extension_loaded('memcache')) - throw new Kohana_Exception('The memcache PHP extension must be loaded to use this driver.'); + throw new Cache_Exception('The memcache PHP extension must be loaded to use this driver.'); ini_set('memcache.allow_failover', (isset($config['allow_failover']) AND $config['allow_failover']) ? TRUE : FALSE); @@ -79,7 +79,10 @@ class Cache_Memcache_Driver extends Cache_Driver { if ($single) { - return ($items === FALSE OR count($items) > 0) ? current($items) : NULL; + if ($items === FALSE) + return NULL; + + return (count($items) > 0) ? current($items) : NULL; } else { diff --git a/system/libraries/drivers/Cache/Xcache.php b/system/libraries/drivers/Cache/Xcache.php index ad11e6d7..4c08405e 100644 --- a/system/libraries/drivers/Cache/Xcache.php +++ b/system/libraries/drivers/Cache/Xcache.php @@ -16,7 +16,7 @@ class Cache_Xcache_Driver extends Cache_Driver { public function __construct($config) { if ( ! extension_loaded('xcache')) - throw new Kohana_Exception('The xcache PHP extension must be loaded to use this driver.'); + throw new Cache_Exception('The xcache PHP extension must be loaded to use this driver.'); $this->config = $config; } diff --git a/system/messages/core.php b/system/messages/core.php index 4bf6ee8c..64f897e8 100644 --- a/system/messages/core.php +++ b/system/messages/core.php @@ -8,7 +8,6 @@ $messages = array E_PAGE_NOT_FOUND => __('Page Not Found'), // __('The requested page was not found. It may have moved, been deleted, or archived.'), E_DATABASE_ERROR => __('Database Error'), // __('A database error occurred while performing the requested procedure. Please review the database error below for more information.'), E_RECOVERABLE_ERROR => __('Recoverable Error'), // __('An error was detected which prevented the loading of this page. If this problem persists, please contact the website administrator.'), - E_ERROR => __('Fatal Error'), E_COMPILE_ERROR => __('Fatal Error'), E_CORE_ERROR => __('Fatal Error'), @@ -29,4 +28,10 @@ $messages = array 'driver' => 'driver', 'model' => 'model', 'view' => 'view', -); \ No newline at end of file +); + +// E_DEPRECATED is only defined in PHP >= 5.3.0 +if (defined('E_DEPRECATED')) +{ + $messages['errors'][E_DEPRECATED] = __('Deprecated'); +} \ No newline at end of file diff --git a/system/messages/validation/default.php b/system/messages/validation/default.php new file mode 100644 index 00000000..2c59fa06 --- /dev/null +++ b/system/messages/validation/default.php @@ -0,0 +1,17 @@ + 'The :field field is required', + 'length' => 'The :field field must be between :param1 and :param2 characters long', + 'depends_on' => 'The :field field requires the :param1 field', + 'matches' => 'The :field field must be the same as :param1', + 'email' => 'The :field field must be a valid email address', + 'decimal' => 'The :field field must be a decimal with :param1 places', + 'digit' => 'The :field field must be a digit', + 'in_array' => 'The :field field must be one of the available options', + 'alpha_numeric' => 'The :field field must consist only of alphabetical or numeric characters', + 'alpha_dash ' => 'The :field field must consist only of alphabetical, numeric, underscore and dash characters', + 'numeric ' => 'The :field field must be a valid number', + 'url' => 'The :field field must be a valid url', + 'phone' => 'The :field field must be a valid phone number', +); diff --git a/system/vendor/Markdown.php b/system/vendor/Markdown.php deleted file mode 100644 index b649f6c1..00000000 --- a/system/vendor/Markdown.php +++ /dev/null @@ -1,2909 +0,0 @@ - -# -# Original Markdown -# Copyright (c) 2004-2006 John Gruber -# -# - - -define( 'MARKDOWN_VERSION', "1.0.1m" ); # Sat 21 Jun 2008 -define( 'MARKDOWNEXTRA_VERSION', "1.2.3" ); # Wed 31 Dec 2008 - - -# -# Global default settings: -# - -# Change to ">" for HTML output -@define( 'MARKDOWN_EMPTY_ELEMENT_SUFFIX', " />"); - -# Define the width of a tab for code blocks. -@define( 'MARKDOWN_TAB_WIDTH', 4 ); - -# Optional title attribute for footnote links and backlinks. -@define( 'MARKDOWN_FN_LINK_TITLE', "" ); -@define( 'MARKDOWN_FN_BACKLINK_TITLE', "" ); - -# Optional class attribute for footnote links and backlinks. -@define( 'MARKDOWN_FN_LINK_CLASS', "" ); -@define( 'MARKDOWN_FN_BACKLINK_CLASS', "" ); - - -# -# WordPress settings: -# - -# Change to false to remove Markdown from posts and/or comments. -@define( 'MARKDOWN_WP_POSTS', true ); -@define( 'MARKDOWN_WP_COMMENTS', true ); - - - -### Standard Function Interface ### - -@define( 'MARKDOWN_PARSER_CLASS', 'MarkdownExtra_Parser' ); - -function Markdown($text) { -# -# Initialize the parser and return the result of its transform method. -# - # Setup static parser variable. - static $parser; - if (!isset($parser)) { - $parser_class = MARKDOWN_PARSER_CLASS; - $parser = new $parser_class; - } - - # Transform text using parser. - return $parser->transform($text); -} - - -### WordPress Plugin Interface ### - -/* -Plugin Name: Markdown Extra -Plugin URI: http://www.michelf.com/projects/php-markdown/ -Description: Markdown syntax allows you to write using an easy-to-read, easy-to-write plain text format. Based on the original Perl version by John Gruber. More... -Version: 1.2.2 -Author: Michel Fortin -Author URI: http://www.michelf.com/ -*/ - -if (isset($wp_version)) { - # More details about how it works here: - # - - # Post content and excerpts - # - Remove WordPress paragraph generator. - # - Run Markdown on excerpt, then remove all tags. - # - Add paragraph tag around the excerpt, but remove it for the excerpt rss. - if (MARKDOWN_WP_POSTS) { - remove_filter('the_content', 'wpautop'); - remove_filter('the_content_rss', 'wpautop'); - remove_filter('the_excerpt', 'wpautop'); - add_filter('the_content', 'mdwp_MarkdownPost', 6); - add_filter('the_content_rss', 'mdwp_MarkdownPost', 6); - add_filter('get_the_excerpt', 'mdwp_MarkdownPost', 6); - add_filter('get_the_excerpt', 'trim', 7); - add_filter('the_excerpt', 'mdwp_add_p'); - add_filter('the_excerpt_rss', 'mdwp_strip_p'); - - remove_filter('content_save_pre', 'balanceTags', 50); - remove_filter('excerpt_save_pre', 'balanceTags', 50); - add_filter('the_content', 'balanceTags', 50); - add_filter('get_the_excerpt', 'balanceTags', 9); - } - - # Add a footnote id prefix to posts when inside a loop. - function mdwp_MarkdownPost($text) { - static $parser; - if (!$parser) { - $parser_class = MARKDOWN_PARSER_CLASS; - $parser = new $parser_class; - } - if (is_single() || is_page() || is_feed()) { - $parser->fn_id_prefix = ""; - } else { - $parser->fn_id_prefix = get_the_ID() . "."; - } - return $parser->transform($text); - } - - # Comments - # - Remove WordPress paragraph generator. - # - Remove WordPress auto-link generator. - # - Scramble important tags before passing them to the kses filter. - # - Run Markdown on excerpt then remove paragraph tags. - if (MARKDOWN_WP_COMMENTS) { - remove_filter('comment_text', 'wpautop', 30); - remove_filter('comment_text', 'make_clickable'); - add_filter('pre_comment_content', 'Markdown', 6); - add_filter('pre_comment_content', 'mdwp_hide_tags', 8); - add_filter('pre_comment_content', 'mdwp_show_tags', 12); - add_filter('get_comment_text', 'Markdown', 6); - add_filter('get_comment_excerpt', 'Markdown', 6); - add_filter('get_comment_excerpt', 'mdwp_strip_p', 7); - - global $mdwp_hidden_tags, $mdwp_placeholders; - $mdwp_hidden_tags = explode(' ', - '

 
  • '); - $mdwp_placeholders = explode(' ', str_rot13( - 'pEj07ZbbBZ U1kqgh4w4p pre2zmeN6K QTi31t9pre ol0MP1jzJR '. - 'ML5IjmbRol ulANi1NsGY J7zRLJqPul liA8ctl16T K9nhooUHli')); - } - - function mdwp_add_p($text) { - if (!preg_match('{^$|^<(p|ul|ol|dl|pre|blockquote)>}i', $text)) { - $text = '

    '.$text.'

    '; - $text = preg_replace('{\n{2,}}', "

    \n\n

    ", $text); - } - return $text; - } - - function mdwp_strip_p($t) { return preg_replace('{}i', '', $t); } - - function mdwp_hide_tags($text) { - global $mdwp_hidden_tags, $mdwp_placeholders; - return str_replace($mdwp_hidden_tags, $mdwp_placeholders, $text); - } - function mdwp_show_tags($text) { - global $mdwp_hidden_tags, $mdwp_placeholders; - return str_replace($mdwp_placeholders, $mdwp_hidden_tags, $text); - } -} - - -### bBlog Plugin Info ### - -function identify_modifier_markdown() { - return array( - 'name' => 'markdown', - 'type' => 'modifier', - 'nicename' => 'PHP Markdown Extra', - 'description' => 'A text-to-HTML conversion tool for web writers', - 'authors' => 'Michel Fortin and John Gruber', - 'licence' => 'GPL', - 'version' => MARKDOWNEXTRA_VERSION, - 'help' => 'Markdown syntax allows you to write using an easy-to-read, easy-to-write plain text format. Based on the original Perl version by John Gruber. More...', - ); -} - - -### Smarty Modifier Interface ### - -function smarty_modifier_markdown($text) { - return Markdown($text); -} - - -### Textile Compatibility Mode ### - -# Rename this file to "classTextile.php" and it can replace Textile everywhere. - -if (strcasecmp(substr(__FILE__, -16), "classTextile.php") == 0) { - # Try to include PHP SmartyPants. Should be in the same directory. - @include_once 'smartypants.php'; - # Fake Textile class. It calls Markdown instead. - class Textile { - function TextileThis($text, $lite='', $encode='') { - if ($lite == '' && $encode == '') $text = Markdown($text); - if (function_exists('SmartyPants')) $text = SmartyPants($text); - return $text; - } - # Fake restricted version: restrictions are not supported for now. - function TextileRestricted($text, $lite='', $noimage='') { - return $this->TextileThis($text, $lite); - } - # Workaround to ensure compatibility with TextPattern 4.0.3. - function blockLite($text) { return $text; } - } -} - - - -# -# Markdown Parser Class -# - -class Markdown_Parser { - - # Regex to match balanced [brackets]. - # Needed to insert a maximum bracked depth while converting to PHP. - var $nested_brackets_depth = 6; - var $nested_brackets_re; - - var $nested_url_parenthesis_depth = 4; - var $nested_url_parenthesis_re; - - # Table of hash values for escaped characters: - var $escape_chars = '\`*_{}[]()>#+-.!'; - var $escape_chars_re; - - # Change to ">" for HTML output. - var $empty_element_suffix = MARKDOWN_EMPTY_ELEMENT_SUFFIX; - var $tab_width = MARKDOWN_TAB_WIDTH; - - # Change to `true` to disallow markup or entities. - var $no_markup = false; - var $no_entities = false; - - # Predefined urls and titles for reference links and images. - var $predef_urls = array(); - var $predef_titles = array(); - - - function Markdown_Parser() { - # - # Constructor function. Initialize appropriate member variables. - # - $this->_initDetab(); - $this->prepareItalicsAndBold(); - - $this->nested_brackets_re = - str_repeat('(?>[^\[\]]+|\[', $this->nested_brackets_depth). - str_repeat('\])*', $this->nested_brackets_depth); - - $this->nested_url_parenthesis_re = - str_repeat('(?>[^()\s]+|\(', $this->nested_url_parenthesis_depth). - str_repeat('(?>\)))*', $this->nested_url_parenthesis_depth); - - $this->escape_chars_re = '['.preg_quote($this->escape_chars).']'; - - # Sort document, block, and span gamut in ascendent priority order. - asort($this->document_gamut); - asort($this->block_gamut); - asort($this->span_gamut); - } - - - # Internal hashes used during transformation. - var $urls = array(); - var $titles = array(); - var $html_hashes = array(); - - # Status flag to avoid invalid nesting. - var $in_anchor = false; - - - function setup() { - # - # Called before the transformation process starts to setup parser - # states. - # - # Clear global hashes. - $this->urls = $this->predef_urls; - $this->titles = $this->predef_titles; - $this->html_hashes = array(); - - $in_anchor = false; - } - - function teardown() { - # - # Called after the transformation process to clear any variable - # which may be taking up memory unnecessarly. - # - $this->urls = array(); - $this->titles = array(); - $this->html_hashes = array(); - } - - - function transform($text) { - # - # Main function. Performs some preprocessing on the input text - # and pass it through the document gamut. - # - $this->setup(); - - # Remove UTF-8 BOM and marker character in input, if present. - $text = preg_replace('{^\xEF\xBB\xBF|\x1A}', '', $text); - - # Standardize line endings: - # DOS to Unix and Mac to Unix - $text = preg_replace('{\r\n?}', "\n", $text); - - # Make sure $text ends with a couple of newlines: - $text .= "\n\n"; - - # Convert all tabs to spaces. - $text = $this->detab($text); - - # Turn block-level HTML blocks into hash entries - $text = $this->hashHTMLBlocks($text); - - # Strip any lines consisting only of spaces and tabs. - # This makes subsequent regexen easier to write, because we can - # match consecutive blank lines with /\n+/ instead of something - # contorted like /[ ]*\n+/ . - $text = preg_replace('/^[ ]+$/m', '', $text); - - # Run document gamut methods. - foreach ($this->document_gamut as $method => $priority) { - $text = $this->$method($text); - } - - $this->teardown(); - - return $text . "\n"; - } - - var $document_gamut = array( - # Strip link definitions, store in hashes. - "stripLinkDefinitions" => 20, - - "runBasicBlockGamut" => 30, - ); - - - function stripLinkDefinitions($text) { - # - # Strips link definitions from text, stores the URLs and titles in - # hash references. - # - $less_than_tab = $this->tab_width - 1; - - # Link defs are in the form: ^[id]: url "optional title" - $text = preg_replace_callback('{ - ^[ ]{0,'.$less_than_tab.'}\[(.+)\][ ]?: # id = $1 - [ ]* - \n? # maybe *one* newline - [ ]* - ? # url = $2 - [ ]* - \n? # maybe one newline - [ ]* - (?: - (?<=\s) # lookbehind for whitespace - ["(] - (.*?) # title = $3 - [")] - [ ]* - )? # title is optional - (?:\n+|\Z) - }xm', - array(&$this, '_stripLinkDefinitions_callback'), - $text); - return $text; - } - function _stripLinkDefinitions_callback($matches) { - $link_id = strtolower($matches[1]); - $this->urls[$link_id] = $matches[2]; - $this->titles[$link_id] =& $matches[3]; - return ''; # String that will replace the block - } - - - function hashHTMLBlocks($text) { - if ($this->no_markup) return $text; - - $less_than_tab = $this->tab_width - 1; - - # Hashify HTML blocks: - # We only want to do this for block-level HTML tags, such as headers, - # lists, and tables. That's because we still want to wrap

    s around - # "paragraphs" that are wrapped in non-block-level tags, such as anchors, - # phrase emphasis, and spans. The list of tags we're looking for is - # hard-coded: - # - # * List "a" is made of tags which can be both inline or block-level. - # These will be treated block-level when the start tag is alone on - # its line, otherwise they're not matched here and will be taken as - # inline later. - # * List "b" is made of tags which are always block-level; - # - $block_tags_a_re = 'ins|del'; - $block_tags_b_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|'. - 'script|noscript|form|fieldset|iframe|math'; - - # Regular expression for the content of a block tag. - $nested_tags_level = 4; - $attr = ' - (?> # optional tag attributes - \s # starts with whitespace - (?> - [^>"/]+ # text outside quotes - | - /+(?!>) # slash not followed by ">" - | - "[^"]*" # text inside double quotes (tolerate ">") - | - \'[^\']*\' # text inside single quotes (tolerate ">") - )* - )? - '; - $content = - str_repeat(' - (?> - [^<]+ # content without tag - | - <\2 # nested opening tag - '.$attr.' # attributes - (?> - /> - | - >', $nested_tags_level). # end of opening tag - '.*?'. # last level nested tag content - str_repeat(' - # closing nested tag - ) - | - <(?!/\2\s*> # other tags with a different name - ) - )*', - $nested_tags_level); - $content2 = str_replace('\2', '\3', $content); - - # First, look for nested blocks, e.g.: - #

    - #
    - # tags for inner block must be indented. - #
    - #
    - # - # The outermost tags must start at the left margin for this to match, and - # the inner nested divs must be indented. - # We need to do this before the next, more liberal match, because the next - # match will start at the first `
    ` and stop at the first `
    `. - $text = preg_replace_callback('{(?> - (?> - (?<=\n\n) # Starting after a blank line - | # or - \A\n? # the beginning of the doc - ) - ( # save in $1 - - # Match from `\n` to `\n`, handling nested tags - # in between. - - [ ]{0,'.$less_than_tab.'} - <('.$block_tags_b_re.')# start tag = $2 - '.$attr.'> # attributes followed by > and \n - '.$content.' # content, support nesting - # the matching end tag - [ ]* # trailing spaces/tabs - (?=\n+|\Z) # followed by a newline or end of document - - | # Special version for tags of group a. - - [ ]{0,'.$less_than_tab.'} - <('.$block_tags_a_re.')# start tag = $3 - '.$attr.'>[ ]*\n # attributes followed by > - '.$content2.' # content, support nesting - # the matching end tag - [ ]* # trailing spaces/tabs - (?=\n+|\Z) # followed by a newline or end of document - - | # Special case just for
    . It was easier to make a special - # case than to make the other regex more complicated. - - [ ]{0,'.$less_than_tab.'} - <(hr) # start tag = $2 - '.$attr.' # attributes - /?> # the matching end tag - [ ]* - (?=\n{2,}|\Z) # followed by a blank line or end of document - - | # Special case for standalone HTML comments: - - [ ]{0,'.$less_than_tab.'} - (?s: - - ) - [ ]* - (?=\n{2,}|\Z) # followed by a blank line or end of document - - | # PHP and ASP-style processor instructions ( - ) - [ ]* - (?=\n{2,}|\Z) # followed by a blank line or end of document - - ) - )}Sxmi', - array(&$this, '_hashHTMLBlocks_callback'), - $text); - - return $text; - } - function _hashHTMLBlocks_callback($matches) { - $text = $matches[1]; - $key = $this->hashBlock($text); - return "\n\n$key\n\n"; - } - - - function hashPart($text, $boundary = 'X') { - # - # Called whenever a tag must be hashed when a function insert an atomic - # element in the text stream. Passing $text to through this function gives - # a unique text-token which will be reverted back when calling unhash. - # - # The $boundary argument specify what character should be used to surround - # the token. By convension, "B" is used for block elements that needs not - # to be wrapped into paragraph tags at the end, ":" is used for elements - # that are word separators and "X" is used in the general case. - # - # Swap back any tag hash found in $text so we do not have to `unhash` - # multiple times at the end. - $text = $this->unhash($text); - - # Then hash the block. - static $i = 0; - $key = "$boundary\x1A" . ++$i . $boundary; - $this->html_hashes[$key] = $text; - return $key; # String that will replace the tag. - } - - - function hashBlock($text) { - # - # Shortcut function for hashPart with block-level boundaries. - # - return $this->hashPart($text, 'B'); - } - - - var $block_gamut = array( - # - # These are all the transformations that form block-level - # tags like paragraphs, headers, and list items. - # - "doHeaders" => 10, - "doHorizontalRules" => 20, - - "doLists" => 40, - "doCodeBlocks" => 50, - "doBlockQuotes" => 60, - ); - - function runBlockGamut($text) { - # - # Run block gamut tranformations. - # - # We need to escape raw HTML in Markdown source before doing anything - # else. This need to be done for each block, and not only at the - # begining in the Markdown function since hashed blocks can be part of - # list items and could have been indented. Indented blocks would have - # been seen as a code block in a previous pass of hashHTMLBlocks. - $text = $this->hashHTMLBlocks($text); - - return $this->runBasicBlockGamut($text); - } - - function runBasicBlockGamut($text) { - # - # Run block gamut tranformations, without hashing HTML blocks. This is - # useful when HTML blocks are known to be already hashed, like in the first - # whole-document pass. - # - foreach ($this->block_gamut as $method => $priority) { - $text = $this->$method($text); - } - - # Finally form paragraph and restore hashed blocks. - $text = $this->formParagraphs($text); - - return $text; - } - - - function doHorizontalRules($text) { - # Do Horizontal Rules: - return preg_replace( - '{ - ^[ ]{0,3} # Leading space - ([-*_]) # $1: First marker - (?> # Repeated marker group - [ ]{0,2} # Zero, one, or two spaces. - \1 # Marker character - ){2,} # Group repeated at least twice - [ ]* # Tailing spaces - $ # End of line. - }mx', - "\n".$this->hashBlock("empty_element_suffix")."\n", - $text); - } - - - var $span_gamut = array( - # - # These are all the transformations that occur *within* block-level - # tags like paragraphs, headers, and list items. - # - # Process character escapes, code spans, and inline HTML - # in one shot. - "parseSpan" => -30, - - # Process anchor and image tags. Images must come first, - # because ![foo][f] looks like an anchor. - "doImages" => 10, - "doAnchors" => 20, - - # Make links out of things like `` - # Must come after doAnchors, because you can use < and > - # delimiters in inline links like [this](). - "doAutoLinks" => 30, - "encodeAmpsAndAngles" => 40, - - "doItalicsAndBold" => 50, - "doHardBreaks" => 60, - ); - - function runSpanGamut($text) { - # - # Run span gamut tranformations. - # - foreach ($this->span_gamut as $method => $priority) { - $text = $this->$method($text); - } - - return $text; - } - - - function doHardBreaks($text) { - # Do hard breaks: - return preg_replace_callback('/ {2,}\n/', - array(&$this, '_doHardBreaks_callback'), $text); - } - function _doHardBreaks_callback($matches) { - return $this->hashPart("empty_element_suffix\n"); - } - - - function doAnchors($text) { - # - # Turn Markdown link shortcuts into XHTML tags. - # - if ($this->in_anchor) return $text; - $this->in_anchor = true; - - # - # First, handle reference-style links: [link text] [id] - # - $text = preg_replace_callback('{ - ( # wrap whole match in $1 - \[ - ('.$this->nested_brackets_re.') # link text = $2 - \] - - [ ]? # one optional space - (?:\n[ ]*)? # one optional newline followed by spaces - - \[ - (.*?) # id = $3 - \] - ) - }xs', - array(&$this, '_doAnchors_reference_callback'), $text); - - # - # Next, inline-style links: [link text](url "optional title") - # - $text = preg_replace_callback('{ - ( # wrap whole match in $1 - \[ - ('.$this->nested_brackets_re.') # link text = $2 - \] - \( # literal paren - [ ]* - (?: - <(\S*)> # href = $3 - | - ('.$this->nested_url_parenthesis_re.') # href = $4 - ) - [ ]* - ( # $5 - ([\'"]) # quote char = $6 - (.*?) # Title = $7 - \6 # matching quote - [ ]* # ignore any spaces/tabs between closing quote and ) - )? # title is optional - \) - ) - }xs', - array(&$this, '_DoAnchors_inline_callback'), $text); - - # - # Last, handle reference-style shortcuts: [link text] - # These must come last in case you've also got [link test][1] - # or [link test](/foo) - # -// $text = preg_replace_callback('{ -// ( # wrap whole match in $1 -// \[ -// ([^\[\]]+) # link text = $2; can\'t contain [ or ] -// \] -// ) -// }xs', -// array(&$this, '_doAnchors_reference_callback'), $text); - - $this->in_anchor = false; - return $text; - } - function _doAnchors_reference_callback($matches) { - $whole_match = $matches[1]; - $link_text = $matches[2]; - $link_id =& $matches[3]; - - if ($link_id == "") { - # for shortcut links like [this][] or [this]. - $link_id = $link_text; - } - - # lower-case and turn embedded newlines into spaces - $link_id = strtolower($link_id); - $link_id = preg_replace('{[ ]?\n}', ' ', $link_id); - - if (isset($this->urls[$link_id])) { - $url = $this->urls[$link_id]; - $url = $this->encodeAttribute($url); - - $result = "titles[$link_id] ) ) { - $title = $this->titles[$link_id]; - $title = $this->encodeAttribute($title); - $result .= " title=\"$title\""; - } - - $link_text = $this->runSpanGamut($link_text); - $result .= ">$link_text"; - $result = $this->hashPart($result); - } - else { - $result = $whole_match; - } - return $result; - } - function _doAnchors_inline_callback($matches) { - $whole_match = $matches[1]; - $link_text = $this->runSpanGamut($matches[2]); - $url = $matches[3] == '' ? $matches[4] : $matches[3]; - $title =& $matches[7]; - - $url = $this->encodeAttribute($url); - - $result = "encodeAttribute($title); - $result .= " title=\"$title\""; - } - - $link_text = $this->runSpanGamut($link_text); - $result .= ">$link_text"; - - return $this->hashPart($result); - } - - - function doImages($text) { - # - # Turn Markdown image shortcuts into tags. - # - # - # First, handle reference-style labeled images: ![alt text][id] - # - $text = preg_replace_callback('{ - ( # wrap whole match in $1 - !\[ - ('.$this->nested_brackets_re.') # alt text = $2 - \] - - [ ]? # one optional space - (?:\n[ ]*)? # one optional newline followed by spaces - - \[ - (.*?) # id = $3 - \] - - ) - }xs', - array(&$this, '_doImages_reference_callback'), $text); - - # - # Next, handle inline images: ![alt text](url "optional title") - # Don't forget: encode * and _ - # - $text = preg_replace_callback('{ - ( # wrap whole match in $1 - !\[ - ('.$this->nested_brackets_re.') # alt text = $2 - \] - \s? # One optional whitespace character - \( # literal paren - [ ]* - (?: - <(\S*)> # src url = $3 - | - ('.$this->nested_url_parenthesis_re.') # src url = $4 - ) - [ ]* - ( # $5 - ([\'"]) # quote char = $6 - (.*?) # title = $7 - \6 # matching quote - [ ]* - )? # title is optional - \) - ) - }xs', - array(&$this, '_doImages_inline_callback'), $text); - - return $text; - } - function _doImages_reference_callback($matches) { - $whole_match = $matches[1]; - $alt_text = $matches[2]; - $link_id = strtolower($matches[3]); - - if ($link_id == "") { - $link_id = strtolower($alt_text); # for shortcut links like ![this][]. - } - - $alt_text = $this->encodeAttribute($alt_text); - if (isset($this->urls[$link_id])) { - $url = $this->encodeAttribute($this->urls[$link_id]); - $result = "\"$alt_text\"";titles[$link_id])) { - $title = $this->titles[$link_id]; - $title = $this->encodeAttribute($title); - $result .= " title=\"$title\""; - } - $result .= $this->empty_element_suffix; - $result = $this->hashPart($result); - } - else { - # If there's no such link ID, leave intact: - $result = $whole_match; - } - - return $result; - } - function _doImages_inline_callback($matches) { - $whole_match = $matches[1]; - $alt_text = $matches[2]; - $url = $matches[3] == '' ? $matches[4] : $matches[3]; - $title =& $matches[7]; - - $alt_text = $this->encodeAttribute($alt_text); - $url = $this->encodeAttribute($url); - $result = "\"$alt_text\"";encodeAttribute($title); - $result .= " title=\"$title\""; # $title already quoted - } - $result .= $this->empty_element_suffix; - - return $this->hashPart($result); - } - - - function doHeaders($text) { - # Setext-style headers: - # Header 1 - # ======== - # - # Header 2 - # -------- - # - $text = preg_replace_callback('{ ^(.+?)[ ]*\n(=+|-+)[ ]*\n+ }mx', - array(&$this, '_doHeaders_callback_setext'), $text); - - # atx-style headers: - # # Header 1 - # ## Header 2 - # ## Header 2 with closing hashes ## - # ... - # ###### Header 6 - # - $text = preg_replace_callback('{ - ^(\#{1,6}) # $1 = string of #\'s - [ ]* - (.+?) # $2 = Header text - [ ]* - \#* # optional closing #\'s (not counted) - \n+ - }xm', - array(&$this, '_doHeaders_callback_atx'), $text); - - return $text; - } - function _doHeaders_callback_setext($matches) { - # Terrible hack to check we haven't found an empty list item. - if ($matches[2] == '-' && preg_match('{^-(?: |$)}', $matches[1])) - return $matches[0]; - - $level = $matches[2]{0} == '=' ? 1 : 2; - $block = "".$this->runSpanGamut($matches[1]).""; - return "\n" . $this->hashBlock($block) . "\n\n"; - } - function _doHeaders_callback_atx($matches) { - $level = strlen($matches[1]); - $block = "".$this->runSpanGamut($matches[2]).""; - return "\n" . $this->hashBlock($block) . "\n\n"; - } - - - function doLists($text) { - # - # Form HTML ordered (numbered) and unordered (bulleted) lists. - # - $less_than_tab = $this->tab_width - 1; - - # Re-usable patterns to match list item bullets and number markers: - $marker_ul_re = '[*+-]'; - $marker_ol_re = '\d+[.]'; - $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)"; - - $markers_relist = array($marker_ul_re, $marker_ol_re); - - foreach ($markers_relist as $marker_re) { - # Re-usable pattern to match any entirel ul or ol list: - $whole_list_re = ' - ( # $1 = whole list - ( # $2 - [ ]{0,'.$less_than_tab.'} - ('.$marker_re.') # $3 = first list item marker - [ ]+ - ) - (?s:.+?) - ( # $4 - \z - | - \n{2,} - (?=\S) - (?! # Negative lookahead for another list item marker - [ ]* - '.$marker_re.'[ ]+ - ) - ) - ) - '; // mx - - # We use a different prefix before nested lists than top-level lists. - # See extended comment in _ProcessListItems(). - - if ($this->list_level) { - $text = preg_replace_callback('{ - ^ - '.$whole_list_re.' - }mx', - array(&$this, '_doLists_callback'), $text); - } - else { - $text = preg_replace_callback('{ - (?:(?<=\n)\n|\A\n?) # Must eat the newline - '.$whole_list_re.' - }mx', - array(&$this, '_doLists_callback'), $text); - } - } - - return $text; - } - function _doLists_callback($matches) { - # Re-usable patterns to match list item bullets and number markers: - $marker_ul_re = '[*+-]'; - $marker_ol_re = '\d+[.]'; - $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)"; - - $list = $matches[1]; - $list_type = preg_match("/$marker_ul_re/", $matches[3]) ? "ul" : "ol"; - - $marker_any_re = ( $list_type == "ul" ? $marker_ul_re : $marker_ol_re ); - - $list .= "\n"; - $result = $this->processListItems($list, $marker_any_re); - - $result = $this->hashBlock("<$list_type>\n" . $result . ""); - return "\n". $result ."\n\n"; - } - - var $list_level = 0; - - function processListItems($list_str, $marker_any_re) { - # - # Process the contents of a single ordered or unordered list, splitting it - # into individual list items. - # - # The $this->list_level global keeps track of when we're inside a list. - # Each time we enter a list, we increment it; when we leave a list, - # we decrement. If it's zero, we're not in a list anymore. - # - # We do this because when we're not inside a list, we want to treat - # something like this: - # - # I recommend upgrading to version - # 8. Oops, now this line is treated - # as a sub-list. - # - # As a single paragraph, despite the fact that the second line starts - # with a digit-period-space sequence. - # - # Whereas when we're inside a list (or sub-list), that line will be - # treated as the start of a sub-list. What a kludge, huh? This is - # an aspect of Markdown's syntax that's hard to parse perfectly - # without resorting to mind-reading. Perhaps the solution is to - # change the syntax rules such that sub-lists must start with a - # starting cardinal number; e.g. "1." or "a.". - - $this->list_level++; - - # trim trailing blank lines: - $list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str); - - $list_str = preg_replace_callback('{ - (\n)? # leading line = $1 - (^[ ]*) # leading whitespace = $2 - ('.$marker_any_re.' # list marker and space = $3 - (?:[ ]+|(?=\n)) # space only required if item is not empty - ) - ((?s:.*?)) # list item text = $4 - (?:(\n+(?=\n))|\n) # tailing blank line = $5 - (?= \n* (\z | \2 ('.$marker_any_re.') (?:[ ]+|(?=\n)))) - }xm', - array(&$this, '_processListItems_callback'), $list_str); - - $this->list_level--; - return $list_str; - } - function _processListItems_callback($matches) { - $item = $matches[4]; - $leading_line =& $matches[1]; - $leading_space =& $matches[2]; - $marker_space = $matches[3]; - $tailing_blank_line =& $matches[5]; - - if ($leading_line || $tailing_blank_line || - preg_match('/\n{2,}/', $item)) - { - # Replace marker with the appropriate whitespace indentation - $item = $leading_space . str_repeat(' ', strlen($marker_space)) . $item; - $item = $this->runBlockGamut($this->outdent($item)."\n"); - } - else { - # Recursion for sub-lists: - $item = $this->doLists($this->outdent($item)); - $item = preg_replace('/\n+$/', '', $item); - $item = $this->runSpanGamut($item); - } - - return "
  • " . $item . "
  • \n"; - } - - - function doCodeBlocks($text) { - # - # Process Markdown `
    ` blocks.
    -	#
    -		$text = preg_replace_callback('{
    -				(?:\n\n|\A\n?)
    -				(	            # $1 = the code block -- one or more lines, starting with a space/tab
    -				  (?>
    -					[ ]{'.$this->tab_width.'}  # Lines must start with a tab or a tab-width of spaces
    -					.*\n+
    -				  )+
    -				)
    -				((?=^[ ]{0,'.$this->tab_width.'}\S)|\Z)	# Lookahead for non-space at line-start, or end of doc
    -			}xm',
    -			array(&$this, '_doCodeBlocks_callback'), $text);
    -
    -		return $text;
    -	}
    -	function _doCodeBlocks_callback($matches) {
    -		$codeblock = $matches[1];
    -
    -		$codeblock = $this->outdent($codeblock);
    -		$codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
    -
    -		# trim leading newlines and trailing newlines
    -		$codeblock = preg_replace('/\A\n+|\n+\z/', '', $codeblock);
    -
    -		$codeblock = "
    $codeblock\n
    "; - return "\n\n".$this->hashBlock($codeblock)."\n\n"; - } - - - function makeCodeSpan($code) { - # - # Create a code span markup for $code. Called from handleSpanToken. - # - $code = htmlspecialchars(trim($code), ENT_NOQUOTES); - return $this->hashPart("$code"); - } - - - var $em_relist = array( - '' => '(?:(? '(?<=\S)(? '(?<=\S)(? '(?:(? '(?<=\S)(? '(?<=\S)(? '(?:(? '(?<=\S)(? '(?<=\S)(?em_relist as $em => $em_re) { - foreach ($this->strong_relist as $strong => $strong_re) { - # Construct list of allowed token expressions. - $token_relist = array(); - if (isset($this->em_strong_relist["$em$strong"])) { - $token_relist[] = $this->em_strong_relist["$em$strong"]; - } - $token_relist[] = $em_re; - $token_relist[] = $strong_re; - - # Construct master expression from list. - $token_re = '{('. implode('|', $token_relist) .')}'; - $this->em_strong_prepared_relist["$em$strong"] = $token_re; - } - } - } - - function doItalicsAndBold($text) { - $token_stack = array(''); - $text_stack = array(''); - $em = ''; - $strong = ''; - $tree_char_em = false; - - while (1) { - # - # Get prepared regular expression for seraching emphasis tokens - # in current context. - # - $token_re = $this->em_strong_prepared_relist["$em$strong"]; - - # - # Each loop iteration seach for the next emphasis token. - # Each token is then passed to handleSpanToken. - # - $parts = preg_split($token_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE); - $text_stack[0] .= $parts[0]; - $token =& $parts[1]; - $text =& $parts[2]; - - if (empty($token)) { - # Reached end of text span: empty stack without emitting. - # any more emphasis. - while ($token_stack[0]) { - $text_stack[1] .= array_shift($token_stack); - $text_stack[0] .= array_shift($text_stack); - } - break; - } - - $token_len = strlen($token); - if ($tree_char_em) { - # Reached closing marker while inside a three-char emphasis. - if ($token_len == 3) { - # Three-char closing marker, close em and strong. - array_shift($token_stack); - $span = array_shift($text_stack); - $span = $this->runSpanGamut($span); - $span = "$span"; - $text_stack[0] .= $this->hashPart($span); - $em = ''; - $strong = ''; - } else { - # Other closing marker: close one em or strong and - # change current token state to match the other - $token_stack[0] = str_repeat($token{0}, 3-$token_len); - $tag = $token_len == 2 ? "strong" : "em"; - $span = $text_stack[0]; - $span = $this->runSpanGamut($span); - $span = "<$tag>$span"; - $text_stack[0] = $this->hashPart($span); - $$tag = ''; # $$tag stands for $em or $strong - } - $tree_char_em = false; - } else if ($token_len == 3) { - if ($em) { - # Reached closing marker for both em and strong. - # Closing strong marker: - for ($i = 0; $i < 2; ++$i) { - $shifted_token = array_shift($token_stack); - $tag = strlen($shifted_token) == 2 ? "strong" : "em"; - $span = array_shift($text_stack); - $span = $this->runSpanGamut($span); - $span = "<$tag>$span"; - $text_stack[0] .= $this->hashPart($span); - $$tag = ''; # $$tag stands for $em or $strong - } - } else { - # Reached opening three-char emphasis marker. Push on token - # stack; will be handled by the special condition above. - $em = $token{0}; - $strong = "$em$em"; - array_unshift($token_stack, $token); - array_unshift($text_stack, ''); - $tree_char_em = true; - } - } else if ($token_len == 2) { - if ($strong) { - # Unwind any dangling emphasis marker: - if (strlen($token_stack[0]) == 1) { - $text_stack[1] .= array_shift($token_stack); - $text_stack[0] .= array_shift($text_stack); - } - # Closing strong marker: - array_shift($token_stack); - $span = array_shift($text_stack); - $span = $this->runSpanGamut($span); - $span = "$span"; - $text_stack[0] .= $this->hashPart($span); - $strong = ''; - } else { - array_unshift($token_stack, $token); - array_unshift($text_stack, ''); - $strong = $token; - } - } else { - # Here $token_len == 1 - if ($em) { - if (strlen($token_stack[0]) == 1) { - # Closing emphasis marker: - array_shift($token_stack); - $span = array_shift($text_stack); - $span = $this->runSpanGamut($span); - $span = "$span"; - $text_stack[0] .= $this->hashPart($span); - $em = ''; - } else { - $text_stack[0] .= $token; - } - } else { - array_unshift($token_stack, $token); - array_unshift($text_stack, ''); - $em = $token; - } - } - } - return $text_stack[0]; - } - - - function doBlockQuotes($text) { - $text = preg_replace_callback('/ - ( # Wrap whole match in $1 - (?> - ^[ ]*>[ ]? # ">" at the start of a line - .+\n # rest of the first line - (.+\n)* # subsequent consecutive lines - \n* # blanks - )+ - ) - /xm', - array(&$this, '_doBlockQuotes_callback'), $text); - - return $text; - } - function _doBlockQuotes_callback($matches) { - $bq = $matches[1]; - # trim one level of quoting - trim whitespace-only lines - $bq = preg_replace('/^[ ]*>[ ]?|^[ ]+$/m', '', $bq); - $bq = $this->runBlockGamut($bq); # recurse - - $bq = preg_replace('/^/m', " ", $bq); - # These leading spaces cause problem with
     content, 
    -		# so we need to fix that:
    -		$bq = preg_replace_callback('{(\s*
    .+?
    )}sx', - array(&$this, '_DoBlockQuotes_callback2'), $bq); - - return "\n". $this->hashBlock("
    \n$bq\n
    ")."\n\n"; - } - function _doBlockQuotes_callback2($matches) { - $pre = $matches[1]; - $pre = preg_replace('/^ /m', '', $pre); - return $pre; - } - - - function formParagraphs($text) { - # - # Params: - # $text - string to process with html

    tags - # - # Strip leading and trailing lines: - $text = preg_replace('/\A\n+|\n+\z/', '', $text); - - $grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY); - - # - # Wrap

    tags and unhashify HTML blocks - # - foreach ($grafs as $key => $value) { - if (!preg_match('/^B\x1A[0-9]+B$/', $value)) { - # Is a paragraph. - $value = $this->runSpanGamut($value); - $value = preg_replace('/^([ ]*)/', "

    ", $value); - $value .= "

    "; - $grafs[$key] = $this->unhash($value); - } - else { - # Is a block. - # Modify elements of @grafs in-place... - $graf = $value; - $block = $this->html_hashes[$graf]; - $graf = $block; -// if (preg_match('{ -// \A -// ( # $1 =
    tag -//
    ]* -// \b -// markdown\s*=\s* ([\'"]) # $2 = attr quote char -// 1 -// \2 -// [^>]* -// > -// ) -// ( # $3 = contents -// .* -// ) -// (
    ) # $4 = closing tag -// \z -// }xs', $block, $matches)) -// { -// list(, $div_open, , $div_content, $div_close) = $matches; -// -// # We can't call Markdown(), because that resets the hash; -// # that initialization code should be pulled into its own sub, though. -// $div_content = $this->hashHTMLBlocks($div_content); -// -// # Run document gamut methods on the content. -// foreach ($this->document_gamut as $method => $priority) { -// $div_content = $this->$method($div_content); -// } -// -// $div_open = preg_replace( -// '{\smarkdown\s*=\s*([\'"]).+?\1}', '', $div_open); -// -// $graf = $div_open . "\n" . $div_content . "\n" . $div_close; -// } - $grafs[$key] = $graf; - } - } - - return implode("\n\n", $grafs); - } - - - function encodeAttribute($text) { - # - # Encode text for a double-quoted HTML attribute. This function - # is *not* suitable for attributes enclosed in single quotes. - # - $text = $this->encodeAmpsAndAngles($text); - $text = str_replace('"', '"', $text); - return $text; - } - - - function encodeAmpsAndAngles($text) { - # - # Smart processing for ampersands and angle brackets that need to - # be encoded. Valid character entities are left alone unless the - # no-entities mode is set. - # - if ($this->no_entities) { - $text = str_replace('&', '&', $text); - } else { - # Ampersand-encoding based entirely on Nat Irons's Amputator - # MT plugin: - $text = preg_replace('/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/', - '&', $text);; - } - # Encode remaining <'s - $text = str_replace('<', '<', $text); - - return $text; - } - - - function doAutoLinks($text) { - $text = preg_replace_callback('{<((https?|ftp|dict):[^\'">\s]+)>}i', - array(&$this, '_doAutoLinks_url_callback'), $text); - - # Email addresses: - $text = preg_replace_callback('{ - < - (?:mailto:)? - ( - [-.\w\x80-\xFF]+ - \@ - [-a-z0-9\x80-\xFF]+(\.[-a-z0-9\x80-\xFF]+)*\.[a-z]+ - ) - > - }xi', - array(&$this, '_doAutoLinks_email_callback'), $text); - - return $text; - } - function _doAutoLinks_url_callback($matches) { - $url = $this->encodeAttribute($matches[1]); - $link = "$url"; - return $this->hashPart($link); - } - function _doAutoLinks_email_callback($matches) { - $address = $matches[1]; - $link = $this->encodeEmailAddress($address); - return $this->hashPart($link); - } - - - function encodeEmailAddress($addr) { - # - # Input: an email address, e.g. "foo@example.com" - # - # Output: the email address as a mailto link, with each character - # of the address encoded as either a decimal or hex entity, in - # the hopes of foiling most address harvesting spam bots. E.g.: - # - #

    foo@exampl - # e.com

    - # - # Based by a filter by Matthew Wickline, posted to BBEdit-Talk. - # With some optimizations by Milian Wolff. - # - $addr = "mailto:" . $addr; - $chars = preg_split('/(? $char) { - $ord = ord($char); - # Ignore non-ascii chars. - if ($ord < 128) { - $r = ($seed * (1 + $key)) % 100; # Pseudo-random function. - # roughly 10% raw, 45% hex, 45% dec - # '@' *must* be encoded. I insist. - if ($r > 90 && $char != '@') /* do nothing */; - else if ($r < 45) $chars[$key] = '&#x'.dechex($ord).';'; - else $chars[$key] = '&#'.$ord.';'; - } - } - - $addr = implode('', $chars); - $text = implode('', array_slice($chars, 7)); # text without `mailto:` - $addr = "$text"; - - return $addr; - } - - - function parseSpan($str) { - # - # Take the string $str and parse it into tokens, hashing embeded HTML, - # escaped characters and handling code spans. - # - $output = ''; - - $span_re = '{ - ( - \\\\'.$this->escape_chars_re.' - | - (?no_markup ? '' : ' - | - # comment - | - <\?.*?\?> | <%.*?%> # processing instruction - | - <[/!$]?[-a-zA-Z0-9:]+ # regular tags - (?> - \s - (?>[^"\'>]+|"[^"]*"|\'[^\']*\')* - )? - > - ').' - ) - }xs'; - - while (1) { - # - # Each loop iteration seach for either the next tag, the next - # openning code span marker, or the next escaped character. - # Each token is then passed to handleSpanToken. - # - $parts = preg_split($span_re, $str, 2, PREG_SPLIT_DELIM_CAPTURE); - - # Create token from text preceding tag. - if ($parts[0] != "") { - $output .= $parts[0]; - } - - # Check if we reach the end. - if (isset($parts[1])) { - $output .= $this->handleSpanToken($parts[1], $parts[2]); - $str = $parts[2]; - } - else { - break; - } - } - - return $output; - } - - - function handleSpanToken($token, &$str) { - # - # Handle $token provided by parseSpan by determining its nature and - # returning the corresponding value that should replace it. - # - switch ($token{0}) { - case "\\": - return $this->hashPart("&#". ord($token{1}). ";"); - case "`": - # Search for end marker in remaining text. - if (preg_match('/^(.*?[^`])'.preg_quote($token).'(?!`)(.*)$/sm', - $str, $matches)) - { - $str = $matches[2]; - $codespan = $this->makeCodeSpan($matches[1]); - return $this->hashPart($codespan); - } - return $token; // return as text since no ending marker found. - default: - return $this->hashPart($token); - } - } - - - function outdent($text) { - # - # Remove one level of line-leading tabs or spaces - # - return preg_replace('/^(\t|[ ]{1,'.$this->tab_width.'})/m', '', $text); - } - - - # String length function for detab. `_initDetab` will create a function to - # hanlde UTF-8 if the default function does not exist. - var $utf8_strlen = 'mb_strlen'; - - function detab($text) { - # - # Replace tabs with the appropriate amount of space. - # - # For each line we separate the line in blocks delemited by - # tab characters. Then we reconstruct every line by adding the - # appropriate number of space between each blocks. - - $text = preg_replace_callback('/^.*\t.*$/m', - array(&$this, '_detab_callback'), $text); - - return $text; - } - function _detab_callback($matches) { - $line = $matches[0]; - $strlen = $this->utf8_strlen; # strlen function for UTF-8. - - # Split in blocks. - $blocks = explode("\t", $line); - # Add each blocks to the line. - $line = $blocks[0]; - unset($blocks[0]); # Do not add first block twice. - foreach ($blocks as $block) { - # Calculate amount of space, insert spaces, insert block. - $amount = $this->tab_width - - $strlen($line, 'UTF-8') % $this->tab_width; - $line .= str_repeat(" ", $amount) . $block; - } - return $line; - } - function _initDetab() { - # - # Check for the availability of the function in the `utf8_strlen` property - # (initially `mb_strlen`). If the function is not available, create a - # function that will loosely count the number of UTF-8 characters with a - # regular expression. - # - if (function_exists($this->utf8_strlen)) return; - $this->utf8_strlen = create_function('$text', 'return preg_match_all( - "/[\\\\x00-\\\\xBF]|[\\\\xC0-\\\\xFF][\\\\x80-\\\\xBF]*/", - $text, $m);'); - } - - - function unhash($text) { - # - # Swap back in all the tags hashed by _HashHTMLBlocks. - # - return preg_replace_callback('/(.)\x1A[0-9]+\1/', - array(&$this, '_unhash_callback'), $text); - } - function _unhash_callback($matches) { - return $this->html_hashes[$matches[0]]; - } - -} - - -# -# Markdown Extra Parser Class -# - -class MarkdownExtra_Parser extends Markdown_Parser { - - # Prefix for footnote ids. - var $fn_id_prefix = ""; - - # Optional title attribute for footnote links and backlinks. - var $fn_link_title = MARKDOWN_FN_LINK_TITLE; - var $fn_backlink_title = MARKDOWN_FN_BACKLINK_TITLE; - - # Optional class attribute for footnote links and backlinks. - var $fn_link_class = MARKDOWN_FN_LINK_CLASS; - var $fn_backlink_class = MARKDOWN_FN_BACKLINK_CLASS; - - # Predefined abbreviations. - var $predef_abbr = array(); - - - function MarkdownExtra_Parser() { - # - # Constructor function. Initialize the parser object. - # - # Add extra escapable characters before parent constructor - # initialize the table. - $this->escape_chars .= ':|'; - - # Insert extra document, block, and span transformations. - # Parent constructor will do the sorting. - $this->document_gamut += array( - "doFencedCodeBlocks" => 5, - "stripFootnotes" => 15, - "stripAbbreviations" => 25, - "appendFootnotes" => 50, - ); - $this->block_gamut += array( - "doFencedCodeBlocks" => 5, - "doTables" => 15, - "doDefLists" => 45, - ); - $this->span_gamut += array( - "doFootnotes" => 5, - "doAbbreviations" => 70, - ); - - parent::Markdown_Parser(); - } - - - # Extra variables used during extra transformations. - var $footnotes = array(); - var $footnotes_ordered = array(); - var $abbr_desciptions = array(); - var $abbr_word_re = ''; - - # Give the current footnote number. - var $footnote_counter = 1; - - - function setup() { - # - # Setting up Extra-specific variables. - # - parent::setup(); - - $this->footnotes = array(); - $this->footnotes_ordered = array(); - $this->abbr_desciptions = array(); - $this->abbr_word_re = ''; - $this->footnote_counter = 1; - - foreach ($this->predef_abbr as $abbr_word => $abbr_desc) { - if ($this->abbr_word_re) - $this->abbr_word_re .= '|'; - $this->abbr_word_re .= preg_quote($abbr_word); - $this->abbr_desciptions[$abbr_word] = trim($abbr_desc); - } - } - - function teardown() { - # - # Clearing Extra-specific variables. - # - $this->footnotes = array(); - $this->footnotes_ordered = array(); - $this->abbr_desciptions = array(); - $this->abbr_word_re = ''; - - parent::teardown(); - } - - - ### HTML Block Parser ### - - # Tags that are always treated as block tags: - var $block_tags_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|form|fieldset|iframe|hr|legend'; - - # Tags treated as block tags only if the opening tag is alone on it's line: - var $context_block_tags_re = 'script|noscript|math|ins|del'; - - # Tags where markdown="1" default to span mode: - var $contain_span_tags_re = 'p|h[1-6]|li|dd|dt|td|th|legend|address'; - - # Tags which must not have their contents modified, no matter where - # they appear: - var $clean_tags_re = 'script|math'; - - # Tags that do not need to be closed. - var $auto_close_tags_re = 'hr|img'; - - - function hashHTMLBlocks($text) { - # - # Hashify HTML Blocks and "clean tags". - # - # We only want to do this for block-level HTML tags, such as headers, - # lists, and tables. That's because we still want to wrap

    s around - # "paragraphs" that are wrapped in non-block-level tags, such as anchors, - # phrase emphasis, and spans. The list of tags we're looking for is - # hard-coded. - # - # This works by calling _HashHTMLBlocks_InMarkdown, which then calls - # _HashHTMLBlocks_InHTML when it encounter block tags. When the markdown="1" - # attribute is found whitin a tag, _HashHTMLBlocks_InHTML calls back - # _HashHTMLBlocks_InMarkdown to handle the Markdown syntax within the tag. - # These two functions are calling each other. It's recursive! - # - # - # Call the HTML-in-Markdown hasher. - # - list($text, ) = $this->_hashHTMLBlocks_inMarkdown($text); - - return $text; - } - function _hashHTMLBlocks_inMarkdown($text, $indent = 0, - $enclosing_tag_re = '', $span = false) - { - # - # Parse markdown text, calling _HashHTMLBlocks_InHTML for block tags. - # - # * $indent is the number of space to be ignored when checking for code - # blocks. This is important because if we don't take the indent into - # account, something like this (which looks right) won't work as expected: - # - #

    - #
    - # Hello World. <-- Is this a Markdown code block or text? - #
    <-- Is this a Markdown code block or a real tag? - #
    - # - # If you don't like this, just don't indent the tag on which - # you apply the markdown="1" attribute. - # - # * If $enclosing_tag_re is not empty, stops at the first unmatched closing - # tag with that name. Nested tags supported. - # - # * If $span is true, text inside must treated as span. So any double - # newline will be replaced by a single newline so that it does not create - # paragraphs. - # - # Returns an array of that form: ( processed text , remaining text ) - # - if ($text === '') return array('', ''); - - # Regex to check for the presense of newlines around a block tag. - $newline_before_re = '/(?:^\n?|\n\n)*$/'; - $newline_after_re = - '{ - ^ # Start of text following the tag. - (?>[ ]*)? # Optional comment. - [ ]*\n # Must be followed by newline. - }xs'; - - # Regex to match any tag. - $block_tag_re = - '{ - ( # $2: Capture hole tag. - # Tag name. - '.$this->block_tags_re.' | - '.$this->context_block_tags_re.' | - '.$this->clean_tags_re.' | - (?!\s)'.$enclosing_tag_re.' - ) - (?: - (?=[\s"\'/a-zA-Z0-9]) # Allowed characters after tag name. - (?> - ".*?" | # Double quotes (can contain `>`) - \'.*?\' | # Single quotes (can contain `>`) - .+? # Anything but quotes and `>`. - )*? - )? - > # End of tag. - | - # HTML Comment - | - <\?.*?\?> | <%.*?%> # Processing instruction - | - # CData Block - | - # Code span marker - `+ - '. ( !$span ? ' # If not in span. - | - # Indented code block - (?> ^[ ]*\n? | \n[ ]*\n ) - [ ]{'.($indent+4).'}[^\n]* \n - (?> - (?: [ ]{'.($indent+4).'}[^\n]* | [ ]* ) \n - )* - | - # Fenced code block marker - (?> ^ | \n ) - [ ]{'.($indent).'}~~~+[ ]*\n - ' : '' ). ' # End (if not is span). - ) - }xs'; - - - $depth = 0; # Current depth inside the tag tree. - $parsed = ""; # Parsed text that will be returned. - - # - # Loop through every tag until we find the closing tag of the parent - # or loop until reaching the end of text if no parent tag specified. - # - do { - # - # Split the text using the first $tag_match pattern found. - # Text before pattern will be first in the array, text after - # pattern will be at the end, and between will be any catches made - # by the pattern. - # - $parts = preg_split($block_tag_re, $text, 2, - PREG_SPLIT_DELIM_CAPTURE); - - # If in Markdown span mode, add a empty-string span-level hash - # after each newline to prevent triggering any block element. - if ($span) { - $void = $this->hashPart("", ':'); - $newline = "$void\n"; - $parts[0] = $void . str_replace("\n", $newline, $parts[0]) . $void; - } - - $parsed .= $parts[0]; # Text before current tag. - - # If end of $text has been reached. Stop loop. - if (count($parts) < 3) { - $text = ""; - break; - } - - $tag = $parts[1]; # Tag to handle. - $text = $parts[2]; # Remaining text after current tag. - $tag_re = preg_quote($tag); # For use in a regular expression. - - # - # Check for: Code span marker - # - if ($tag{0} == "`") { - # Find corresponding end marker. - $tag_re = preg_quote($tag); - if (preg_match('{^(?>.+?|\n(?!\n))*?(?.*\n)+?'.$tag_re.' *\n}', $text, - $matches)) - { - # End marker found: pass text unchanged until marker. - $parsed .= $tag . $matches[0]; - $text = substr($text, strlen($matches[0])); - } - else { - # No end marker: just skip it. - $parsed .= $tag; - } - } - } - # - # Check for: Opening Block level tag or - # Opening Context Block tag (like ins and del) - # used as a block tag (tag is alone on it's line). - # - else if (preg_match('{^<(?:'.$this->block_tags_re.')\b}', $tag) || - ( preg_match('{^<(?:'.$this->context_block_tags_re.')\b}', $tag) && - preg_match($newline_before_re, $parsed) && - preg_match($newline_after_re, $text) ) - ) - { - # Need to parse tag and following text using the HTML parser. - list($block_text, $text) = - $this->_hashHTMLBlocks_inHTML($tag . $text, "hashBlock", true); - - # Make sure it stays outside of any paragraph by adding newlines. - $parsed .= "\n\n$block_text\n\n"; - } - # - # Check for: Clean tag (like script, math) - # HTML Comments, processing instructions. - # - else if (preg_match('{^<(?:'.$this->clean_tags_re.')\b}', $tag) || - $tag{1} == '!' || $tag{1} == '?') - { - # Need to parse tag and following text using the HTML parser. - # (don't check for markdown attribute) - list($block_text, $text) = - $this->_hashHTMLBlocks_inHTML($tag . $text, "hashClean", false); - - $parsed .= $block_text; - } - # - # Check for: Tag with same name as enclosing tag. - # - else if ($enclosing_tag_re !== '' && - # Same name as enclosing tag. - preg_match('{^= 0); - - return array($parsed, $text); - } - function _hashHTMLBlocks_inHTML($text, $hash_method, $md_attr) { - # - # Parse HTML, calling _HashHTMLBlocks_InMarkdown for block tags. - # - # * Calls $hash_method to convert any blocks. - # * Stops when the first opening tag closes. - # * $md_attr indicate if the use of the `markdown="1"` attribute is allowed. - # (it is not inside clean tags) - # - # Returns an array of that form: ( processed text , remaining text ) - # - if ($text === '') return array('', ''); - - # Regex to match `markdown` attribute inside of a tag. - $markdown_attr_re = ' - { - \s* # Eat whitespace before the `markdown` attribute - markdown - \s*=\s* - (?> - (["\']) # $1: quote delimiter - (.*?) # $2: attribute value - \1 # matching delimiter - | - ([^\s>]*) # $3: unquoted attribute value - ) - () # $4: make $3 always defined (avoid warnings) - }xs'; - - # Regex to match any tag. - $tag_re = '{ - ( # $2: Capture hole tag. - - ".*?" | # Double quotes (can contain `>`) - \'.*?\' | # Single quotes (can contain `>`) - .+? # Anything but quotes and `>`. - )*? - )? - > # End of tag. - | - # HTML Comment - | - <\?.*?\?> | <%.*?%> # Processing instruction - | - # CData Block - ) - }xs'; - - $original_text = $text; # Save original text in case of faliure. - - $depth = 0; # Current depth inside the tag tree. - $block_text = ""; # Temporary text holder for current text. - $parsed = ""; # Parsed text that will be returned. - - # - # Get the name of the starting tag. - # (This pattern makes $base_tag_name_re safe without quoting.) - # - if (preg_match('/^<([\w:$]*)\b/', $text, $matches)) - $base_tag_name_re = $matches[1]; - - # - # Loop through every tag until we find the corresponding closing tag. - # - do { - # - # Split the text using the first $tag_match pattern found. - # Text before pattern will be first in the array, text after - # pattern will be at the end, and between will be any catches made - # by the pattern. - # - $parts = preg_split($tag_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE); - - if (count($parts) < 3) { - # - # End of $text reached with unbalenced tag(s). - # In that case, we return original text unchanged and pass the - # first character as filtered to prevent an infinite loop in the - # parent function. - # - return array($original_text{0}, substr($original_text, 1)); - } - - $block_text .= $parts[0]; # Text before current tag. - $tag = $parts[1]; # Tag to handle. - $text = $parts[2]; # Remaining text after current tag. - - # - # Check for: Auto-close tag (like
    ) - # Comments and Processing Instructions. - # - if (preg_match('{^auto_close_tags_re.')\b}', $tag) || - $tag{1} == '!' || $tag{1} == '?') - { - # Just add the tag to the block as if it was text. - $block_text .= $tag; - } - else { - # - # Increase/decrease nested tag count. Only do so if - # the tag's name match base tag's. - # - if (preg_match('{^mode = $attr_m[2] . $attr_m[3]; - $span_mode = $this->mode == 'span' || $this->mode != 'block' && - preg_match('{^<(?:'.$this->contain_span_tags_re.')\b}', $tag); - - # Calculate indent before tag. - if (preg_match('/(?:^|\n)( *?)(?! ).*?$/', $block_text, $matches)) { - $strlen = $this->utf8_strlen; - $indent = $strlen($matches[1], 'UTF-8'); - } else { - $indent = 0; - } - - # End preceding block with this tag. - $block_text .= $tag; - $parsed .= $this->$hash_method($block_text); - - # Get enclosing tag name for the ParseMarkdown function. - # (This pattern makes $tag_name_re safe without quoting.) - preg_match('/^<([\w:$]*)\b/', $tag, $matches); - $tag_name_re = $matches[1]; - - # Parse the content using the HTML-in-Markdown parser. - list ($block_text, $text) - = $this->_hashHTMLBlocks_inMarkdown($text, $indent, - $tag_name_re, $span_mode); - - # Outdent markdown text. - if ($indent > 0) { - $block_text = preg_replace("/^[ ]{1,$indent}/m", "", - $block_text); - } - - # Append tag content to parsed text. - if (!$span_mode) $parsed .= "\n\n$block_text\n\n"; - else $parsed .= "$block_text"; - - # Start over a new block. - $block_text = ""; - } - else $block_text .= $tag; - } - - } while ($depth > 0); - - # - # Hash last block text that wasn't processed inside the loop. - # - $parsed .= $this->$hash_method($block_text); - - return array($parsed, $text); - } - - - function hashClean($text) { - # - # Called whenever a tag must be hashed when a function insert a "clean" tag - # in $text, it pass through this function and is automaticaly escaped, - # blocking invalid nested overlap. - # - return $this->hashPart($text, 'C'); - } - - - function doHeaders($text) { - # - # Redefined to add id attribute support. - # - # Setext-style headers: - # Header 1 {#header1} - # ======== - # - # Header 2 {#header2} - # -------- - # - $text = preg_replace_callback( - '{ - (^.+?) # $1: Header text - (?:[ ]+\{\#([-_:a-zA-Z0-9]+)\})? # $2: Id attribute - [ ]*\n(=+|-+)[ ]*\n+ # $3: Header footer - }mx', - array(&$this, '_doHeaders_callback_setext'), $text); - - # atx-style headers: - # # Header 1 {#header1} - # ## Header 2 {#header2} - # ## Header 2 with closing hashes ## {#header3} - # ... - # ###### Header 6 {#header2} - # - $text = preg_replace_callback('{ - ^(\#{1,6}) # $1 = string of #\'s - [ ]* - (.+?) # $2 = Header text - [ ]* - \#* # optional closing #\'s (not counted) - (?:[ ]+\{\#([-_:a-zA-Z0-9]+)\})? # id attribute - [ ]* - \n+ - }xm', - array(&$this, '_doHeaders_callback_atx'), $text); - - return $text; - } - function _doHeaders_attr($attr) { - if (empty($attr)) return ""; - return " id=\"$attr\""; - } - function _doHeaders_callback_setext($matches) { - if ($matches[3] == '-' && preg_match('{^- }', $matches[1])) - return $matches[0]; - $level = $matches[3]{0} == '=' ? 1 : 2; - $attr = $this->_doHeaders_attr($id =& $matches[2]); - $block = "".$this->runSpanGamut($matches[1]).""; - return "\n" . $this->hashBlock($block) . "\n\n"; - } - function _doHeaders_callback_atx($matches) { - $level = strlen($matches[1]); - $attr = $this->_doHeaders_attr($id =& $matches[3]); - $block = "".$this->runSpanGamut($matches[2]).""; - return "\n" . $this->hashBlock($block) . "\n\n"; - } - - - function doTables($text) { - # - # Form HTML tables. - # - $less_than_tab = $this->tab_width - 1; - # - # Find tables with leading pipe. - # - # | Header 1 | Header 2 - # | -------- | -------- - # | Cell 1 | Cell 2 - # | Cell 3 | Cell 4 - # - $text = preg_replace_callback(' - { - ^ # Start of a line - [ ]{0,'.$less_than_tab.'} # Allowed whitespace. - [|] # Optional leading pipe (present) - (.+) \n # $1: Header row (at least one pipe) - - [ ]{0,'.$less_than_tab.'} # Allowed whitespace. - [|] ([ ]*[-:]+[-| :]*) \n # $2: Header underline - - ( # $3: Cells - (?> - [ ]* # Allowed whitespace. - [|] .* \n # Row content. - )* - ) - (?=\n|\Z) # Stop at final double newline. - }xm', - array(&$this, '_doTable_leadingPipe_callback'), $text); - - # - # Find tables without leading pipe. - # - # Header 1 | Header 2 - # -------- | -------- - # Cell 1 | Cell 2 - # Cell 3 | Cell 4 - # - $text = preg_replace_callback(' - { - ^ # Start of a line - [ ]{0,'.$less_than_tab.'} # Allowed whitespace. - (\S.*[|].*) \n # $1: Header row (at least one pipe) - - [ ]{0,'.$less_than_tab.'} # Allowed whitespace. - ([-:]+[ ]*[|][-| :]*) \n # $2: Header underline - - ( # $3: Cells - (?> - .* [|] .* \n # Row content - )* - ) - (?=\n|\Z) # Stop at final double newline. - }xm', - array(&$this, '_DoTable_callback'), $text); - - return $text; - } - function _doTable_leadingPipe_callback($matches) { - $head = $matches[1]; - $underline = $matches[2]; - $content = $matches[3]; - - # Remove leading pipe for each row. - $content = preg_replace('/^ *[|]/m', '', $content); - - return $this->_doTable_callback(array($matches[0], $head, $underline, $content)); - } - function _doTable_callback($matches) { - $head = $matches[1]; - $underline = $matches[2]; - $content = $matches[3]; - - # Remove any tailing pipes for each line. - $head = preg_replace('/[|] *$/m', '', $head); - $underline = preg_replace('/[|] *$/m', '', $underline); - $content = preg_replace('/[|] *$/m', '', $content); - - # Reading alignement from header underline. - $separators = preg_split('/ *[|] */', $underline); - foreach ($separators as $n => $s) { - if (preg_match('/^ *-+: *$/', $s)) $attr[$n] = ' align="right"'; - else if (preg_match('/^ *:-+: *$/', $s))$attr[$n] = ' align="center"'; - else if (preg_match('/^ *:-+ *$/', $s)) $attr[$n] = ' align="left"'; - else $attr[$n] = ''; - } - - # Parsing span elements, including code spans, character escapes, - # and inline HTML tags, so that pipes inside those gets ignored. - $head = $this->parseSpan($head); - $headers = preg_split('/ *[|] */', $head); - $col_count = count($headers); - - # Write column headers. - $text = "\n"; - $text .= "\n"; - $text .= "\n"; - foreach ($headers as $n => $header) - $text .= " ".$this->runSpanGamut(trim($header))."\n"; - $text .= "\n"; - $text .= "\n"; - - # Split content by row. - $rows = explode("\n", trim($content, "\n")); - - $text .= "\n"; - foreach ($rows as $row) { - # Parsing span elements, including code spans, character escapes, - # and inline HTML tags, so that pipes inside those gets ignored. - $row = $this->parseSpan($row); - - # Split row by cell. - $row_cells = preg_split('/ *[|] */', $row, $col_count); - $row_cells = array_pad($row_cells, $col_count, ''); - - $text .= "\n"; - foreach ($row_cells as $n => $cell) - $text .= " ".$this->runSpanGamut(trim($cell))."\n"; - $text .= "\n"; - } - $text .= "\n"; - $text .= "
    "; - - return $this->hashBlock($text) . "\n"; - } - - - function doDefLists($text) { - # - # Form HTML definition lists. - # - $less_than_tab = $this->tab_width - 1; - - # Re-usable pattern to match any entire dl list: - $whole_list_re = '(?> - ( # $1 = whole list - ( # $2 - [ ]{0,'.$less_than_tab.'} - ((?>.*\S.*\n)+) # $3 = defined term - \n? - [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition - ) - (?s:.+?) - ( # $4 - \z - | - \n{2,} - (?=\S) - (?! # Negative lookahead for another term - [ ]{0,'.$less_than_tab.'} - (?: \S.*\n )+? # defined term - \n? - [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition - ) - (?! # Negative lookahead for another definition - [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition - ) - ) - ) - )'; // mx - - $text = preg_replace_callback('{ - (?>\A\n?|(?<=\n\n)) - '.$whole_list_re.' - }mx', - array(&$this, '_doDefLists_callback'), $text); - - return $text; - } - function _doDefLists_callback($matches) { - # Re-usable patterns to match list item bullets and number markers: - $list = $matches[1]; - - # Turn double returns into triple returns, so that we can make a - # paragraph for the last item in a list, if necessary: - $result = trim($this->processDefListItems($list)); - $result = "
    \n" . $result . "\n
    "; - return $this->hashBlock($result) . "\n\n"; - } - - - function processDefListItems($list_str) { - # - # Process the contents of a single definition list, splitting it - # into individual term and definition list items. - # - $less_than_tab = $this->tab_width - 1; - - # trim trailing blank lines: - $list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str); - - # Process definition terms. - $list_str = preg_replace_callback('{ - (?>\A\n?|\n\n+) # leading line - ( # definition terms = $1 - [ ]{0,'.$less_than_tab.'} # leading whitespace - (?![:][ ]|[ ]) # negative lookahead for a definition - # mark (colon) or more whitespace. - (?> \S.* \n)+? # actual term (not whitespace). - ) - (?=\n?[ ]{0,3}:[ ]) # lookahead for following line feed - # with a definition mark. - }xm', - array(&$this, '_processDefListItems_callback_dt'), $list_str); - - # Process actual definitions. - $list_str = preg_replace_callback('{ - \n(\n+)? # leading line = $1 - ( # marker space = $2 - [ ]{0,'.$less_than_tab.'} # whitespace before colon - [:][ ]+ # definition mark (colon) - ) - ((?s:.+?)) # definition text = $3 - (?= \n+ # stop at next definition mark, - (?: # next term or end of text - [ ]{0,'.$less_than_tab.'} [:][ ] | -
    | \z - ) - ) - }xm', - array(&$this, '_processDefListItems_callback_dd'), $list_str); - - return $list_str; - } - function _processDefListItems_callback_dt($matches) { - $terms = explode("\n", trim($matches[1])); - $text = ''; - foreach ($terms as $term) { - $term = $this->runSpanGamut(trim($term)); - $text .= "\n
    " . $term . "
    "; - } - return $text . "\n"; - } - function _processDefListItems_callback_dd($matches) { - $leading_line = $matches[1]; - $marker_space = $matches[2]; - $def = $matches[3]; - - if ($leading_line || preg_match('/\n{2,}/', $def)) { - # Replace marker with the appropriate whitespace indentation - $def = str_repeat(' ', strlen($marker_space)) . $def; - $def = $this->runBlockGamut($this->outdent($def . "\n\n")); - $def = "\n". $def ."\n"; - } - else { - $def = rtrim($def); - $def = $this->runSpanGamut($this->outdent($def)); - } - - return "\n
    " . $def . "
    \n"; - } - - - function doFencedCodeBlocks($text) { - # - # Adding the fenced code block syntax to regular Markdown: - # - # ~~~ - # Code block - # ~~~ - # - $less_than_tab = $this->tab_width; - - $text = preg_replace_callback('{ - (?:\n|\A) - # 1: Opening marker - ( - ~{3,} # Marker: three tilde or more. - ) - [ ]* \n # Whitespace and newline following marker. - - # 2: Content - ( - (?> - (?!\1 [ ]* \n) # Not a closing marker. - .*\n+ - )+ - ) - - # Closing marker. - \1 [ ]* \n - }xm', - array(&$this, '_doFencedCodeBlocks_callback'), $text); - - return $text; - } - function _doFencedCodeBlocks_callback($matches) { - $codeblock = $matches[2]; - $codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES); - $codeblock = preg_replace_callback('/^\n+/', - array(&$this, '_doFencedCodeBlocks_newlines'), $codeblock); - $codeblock = "
    $codeblock
    "; - return "\n\n".$this->hashBlock($codeblock)."\n\n"; - } - function _doFencedCodeBlocks_newlines($matches) { - return str_repeat("empty_element_suffix", - strlen($matches[0])); - } - - - # - # Redefining emphasis markers so that emphasis by underscore does not - # work in the middle of a word. - # - var $em_relist = array( - '' => '(?:(? '(?<=\S)(? '(?<=\S)(? '(?:(? '(?<=\S)(? '(?<=\S)(? '(?:(? '(?<=\S)(? '(?<=\S)(? tags - # - # Strip leading and trailing lines: - $text = preg_replace('/\A\n+|\n+\z/', '', $text); - - $grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY); - - # - # Wrap

    tags and unhashify HTML blocks - # - foreach ($grafs as $key => $value) { - $value = trim($this->runSpanGamut($value)); - - # Check if this should be enclosed in a paragraph. - # Clean tag hashes & block tag hashes are left alone. - $is_p = !preg_match('/^B\x1A[0-9]+B|^C\x1A[0-9]+C$/', $value); - - if ($is_p) { - $value = "

    $value

    "; - } - $grafs[$key] = $value; - } - - # Join grafs in one text, then unhash HTML tags. - $text = implode("\n\n", $grafs); - - # Finish by removing any tag hashes still present in $text. - $text = $this->unhash($text); - - return $text; - } - - - ### Footnotes - - function stripFootnotes($text) { - # - # Strips link definitions from text, stores the URLs and titles in - # hash references. - # - $less_than_tab = $this->tab_width - 1; - - # Link defs are in the form: [^id]: url "optional title" - $text = preg_replace_callback('{ - ^[ ]{0,'.$less_than_tab.'}\[\^(.+?)\][ ]?: # note_id = $1 - [ ]* - \n? # maybe *one* newline - ( # text = $2 (no blank lines allowed) - (?: - .+ # actual text - | - \n # newlines but - (?!\[\^.+?\]:\s)# negative lookahead for footnote marker. - (?!\n+[ ]{0,3}\S)# ensure line is not blank and followed - # by non-indented content - )* - ) - }xm', - array(&$this, '_stripFootnotes_callback'), - $text); - return $text; - } - function _stripFootnotes_callback($matches) { - $note_id = $this->fn_id_prefix . $matches[1]; - $this->footnotes[$note_id] = $this->outdent($matches[2]); - return ''; # String that will replace the block - } - - - function doFootnotes($text) { - # - # Replace footnote references in $text [^id] with a special text-token - # which will be replaced by the actual footnote marker in appendFootnotes. - # - if (!$this->in_anchor) { - $text = preg_replace('{\[\^(.+?)\]}', "F\x1Afn:\\1\x1A:", $text); - } - return $text; - } - - - function appendFootnotes($text) { - # - # Append footnote list to text. - # - $text = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}', - array(&$this, '_appendFootnotes_callback'), $text); - - if (!empty($this->footnotes_ordered)) { - $text .= "\n\n"; - $text .= "
    \n"; - $text .= "fn_backlink_class != "") { - $class = $this->fn_backlink_class; - $class = $this->encodeAttribute($class); - $attr .= " class=\"$class\""; - } - if ($this->fn_backlink_title != "") { - $title = $this->fn_backlink_title; - $title = $this->encodeAttribute($title); - $attr .= " title=\"$title\""; - } - $num = 0; - - while (!empty($this->footnotes_ordered)) { - $footnote = reset($this->footnotes_ordered); - $note_id = key($this->footnotes_ordered); - unset($this->footnotes_ordered[$note_id]); - - $footnote .= "\n"; # Need to append newline before parsing. - $footnote = $this->runBlockGamut("$footnote\n"); - $footnote = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}', - array(&$this, '_appendFootnotes_callback'), $footnote); - - $attr = str_replace("%%", ++$num, $attr); - $note_id = $this->encodeAttribute($note_id); - - # Add backlink to last paragraph; create new paragraph if needed. - $backlink = ""; - if (preg_match('{

    $}', $footnote)) { - $footnote = substr($footnote, 0, -4) . " $backlink

    "; - } else { - $footnote .= "\n\n

    $backlink

    "; - } - - $text .= "
  • \n"; - $text .= $footnote . "\n"; - $text .= "
  • \n\n"; - } - - $text .= "\n"; - $text .= "
    "; - } - return $text; - } - function _appendFootnotes_callback($matches) { - $node_id = $this->fn_id_prefix . $matches[1]; - - # Create footnote marker only if it has a corresponding footnote *and* - # the footnote hasn't been used by another marker. - if (isset($this->footnotes[$node_id])) { - # Transfert footnote content to the ordered list. - $this->footnotes_ordered[$node_id] = $this->footnotes[$node_id]; - unset($this->footnotes[$node_id]); - - $num = $this->footnote_counter++; - $attr = " rel=\"footnote\""; - if ($this->fn_link_class != "") { - $class = $this->fn_link_class; - $class = $this->encodeAttribute($class); - $attr .= " class=\"$class\""; - } - if ($this->fn_link_title != "") { - $title = $this->fn_link_title; - $title = $this->encodeAttribute($title); - $attr .= " title=\"$title\""; - } - - $attr = str_replace("%%", $num, $attr); - $node_id = $this->encodeAttribute($node_id); - - return - "". - "$num". - ""; - } - - return "[^".$matches[1]."]"; - } - - - ### Abbreviations ### - - function stripAbbreviations($text) { - # - # Strips abbreviations from text, stores titles in hash references. - # - $less_than_tab = $this->tab_width - 1; - - # Link defs are in the form: [id]*: url "optional title" - $text = preg_replace_callback('{ - ^[ ]{0,'.$less_than_tab.'}\*\[(.+?)\][ ]?: # abbr_id = $1 - (.*) # text = $2 (no blank lines allowed) - }xm', - array(&$this, '_stripAbbreviations_callback'), - $text); - return $text; - } - function _stripAbbreviations_callback($matches) { - $abbr_word = $matches[1]; - $abbr_desc = $matches[2]; - if ($this->abbr_word_re) - $this->abbr_word_re .= '|'; - $this->abbr_word_re .= preg_quote($abbr_word); - $this->abbr_desciptions[$abbr_word] = trim($abbr_desc); - return ''; # String that will replace the block - } - - - function doAbbreviations($text) { - # - # Find defined abbreviations in text and wrap them in elements. - # - if ($this->abbr_word_re) { - // cannot use the /x modifier because abbr_word_re may - // contain significant spaces: - $text = preg_replace_callback('{'. - '(?abbr_word_re.')'. - '(?![\w\x1A])'. - '}', - array(&$this, '_doAbbreviations_callback'), $text); - } - return $text; - } - function _doAbbreviations_callback($matches) { - $abbr = $matches[0]; - if (isset($this->abbr_desciptions[$abbr])) { - $desc = $this->abbr_desciptions[$abbr]; - if (empty($desc)) { - return $this->hashPart("$abbr"); - } else { - $desc = $this->encodeAttribute($desc); - return $this->hashPart("$abbr"); - } - } else { - return $matches[0]; - } - } - -} - - -/* - -PHP Markdown Extra -================== - -Description ------------ - -This is a PHP port of the original Markdown formatter written in Perl -by John Gruber. This special "Extra" version of PHP Markdown features -further enhancements to the syntax for making additional constructs -such as tables and definition list. - -Markdown is a text-to-HTML filter; it translates an easy-to-read / -easy-to-write structured text format into HTML. Markdown's text format -is most similar to that of plain text email, and supports features such -as headers, *emphasis*, code blocks, blockquotes, and links. - -Markdown's syntax is designed not as a generic markup language, but -specifically to serve as a front-end to (X)HTML. You can use span-level -HTML tags anywhere in a Markdown document, and you can use block level -HTML tags (like
    and as well). - -For more information about Markdown's syntax, see: - - - - -Bugs ----- - -To file bug reports please send email to: - - - -Please include with your report: (1) the example input; (2) the output you -expected; (3) the output Markdown actually produced. - - -Version History ---------------- - -See the readme file for detailed release notes for this version. - - -Copyright and License ---------------------- - -PHP Markdown & Extra -Copyright (c) 2004-2008 Michel Fortin - -All rights reserved. - -Based on Markdown -Copyright (c) 2003-2006 John Gruber - -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - -* Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -* Neither the name "Markdown" nor the names of its contributors may - be used to endorse or promote products derived from this software - without specific prior written permission. - -This software is provided by the copyright holders and contributors "as -is" and any express or implied warranties, including, but not limited -to, the implied warranties of merchantability and fitness for a -particular purpose are disclaimed. In no event shall the copyright owner -or contributors be liable for any direct, indirect, incidental, special, -exemplary, or consequential damages (including, but not limited to, -procurement of substitute goods or services; loss of use, data, or -profits; or business interruption) however caused and on any theory of -liability, whether in contract, strict liability, or tort (including -negligence or otherwise) arising in any way out of the use of this -software, even if advised of the possibility of such damage. - -*/ -?> \ No newline at end of file -- cgit v1.2.3 From b79e0c9d572bd578c78b9ed6e3e23d3ffbf6bf0b Mon Sep 17 00:00:00 2001 From: Bharat Mediratta Date: Tue, 22 Dec 2009 22:15:13 -0800 Subject: Back out my temporary fix for http://dev.kohanaframework.org/issues/2384 now that the real fix is in. --- system/libraries/ORM.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'system/libraries') diff --git a/system/libraries/ORM.php b/system/libraries/ORM.php index 2f2feed5..e2f27bac 100644 --- a/system/libraries/ORM.php +++ b/system/libraries/ORM.php @@ -242,7 +242,7 @@ class ORM_Core { else { // Support for things like reset_select, reset_write, list_tables - $this->db_builder->$method(); + return $this->db_builder->$method(); } break; case 1: -- cgit v1.2.3 From 3e8e13bd2533ff7e3493b27c8a8587dfb65e1b26 Mon Sep 17 00:00:00 2001 From: Bharat Mediratta Date: Wed, 23 Dec 2009 12:42:57 -0800 Subject: Updated Kohana to r4728 --- system/core/Kohana.php | 7 ++--- system/core/Kohana_Exception.php | 26 ++++++++++-------- system/libraries/Input.php | 31 +-------------------- system/messages/core.php | 37 ------------------------- system/messages/kohana/core.php | 37 +++++++++++++++++++++++++ system/messages/validation/default.php | 42 ++++++++++++++++------------ system/views/kohana/error.php | 50 +++++++++++++++++----------------- 7 files changed, 104 insertions(+), 126 deletions(-) delete mode 100644 system/messages/core.php create mode 100644 system/messages/kohana/core.php (limited to 'system/libraries') diff --git a/system/core/Kohana.php b/system/core/Kohana.php index 740adb80..29ca708c 100644 --- a/system/core/Kohana.php +++ b/system/core/Kohana.php @@ -2,7 +2,7 @@ /** * Provides Kohana-specific helper functions. This is where the magic happens! * - * $Id: Kohana.php 4724 2009-12-21 16:28:54Z isaiah $ + * $Id: Kohana.php 4726 2009-12-23 18:58:53Z isaiah $ * * @package Core * @author Kohana Team @@ -810,11 +810,8 @@ abstract class Kohana_Core { { if ($required === TRUE) { - // Directory i18n key - $directory = 'core.'.inflector::singular($directory); - // If the file is required, throw an exception - throw new Kohana_Exception('The requested :resource:, :file:, could not be found', array(':resource:' => Kohana::message($directory), ':file:' =>$filename)); + throw new Kohana_Exception('The requested :resource:, :file:, could not be found', array(':resource:' => __($directory), ':file:' =>$filename)); } else { diff --git a/system/core/Kohana_Exception.php b/system/core/Kohana_Exception.php index 2eb28f75..0cbc472c 100644 --- a/system/core/Kohana_Exception.php +++ b/system/core/Kohana_Exception.php @@ -2,7 +2,7 @@ /** * Kohana Exceptions * - * $Id: Kohana_Exception.php 4692 2009-12-04 15:59:44Z cbandy $ + * $Id: Kohana_Exception.php 4726 2009-12-23 18:58:53Z isaiah $ * * @package Core * @author Kohana Team @@ -119,7 +119,7 @@ class Kohana_Exception_Core extends Exception { // Manually save logs after exceptions Kohana_Log::save(); - if (Kohana::config('core.display_errors') === FALSE) + if (Kohana::config('kohana/core.display_errors') === FALSE) { // Do not show the details $file = $line = NULL; @@ -146,7 +146,7 @@ class Kohana_Exception_Core extends Exception { } // Use the human-readable error name - $code = Kohana::message('core.errors.'.$code); + $code = Kohana::message('kohana/core.errors.'.$code); } else { @@ -160,7 +160,7 @@ class Kohana_Exception_Core extends Exception { if ($e instanceof ErrorException) { // Use the human-readable error name - $code = Kohana::message('core.errors.'.$e->getSeverity()); + $code = Kohana::message('kohana/core.errors.'.$e->getSeverity()); if (version_compare(PHP_VERSION, '5.3', '<')) { @@ -233,11 +233,12 @@ class Kohana_Exception_Core extends Exception { * * @param mixed variable to dump * @param integer maximum length of strings + * @param integer maximum levels of recursion * @return string */ - public static function dump($value, $length = 128) + public static function dump($value, $length = 128, $max_level = 5) { - return Kohana_Exception::_dump($value, $length); + return Kohana_Exception::_dump($value, $length, $max_level); } /** @@ -245,10 +246,11 @@ class Kohana_Exception_Core extends Exception { * * @param mixed variable to dump * @param integer maximum length of strings - * @param integer recursion level (internal) + * @param integer maximum levels of recursion + * @param integer current recursion level (internal) * @return string */ - private static function _dump( & $var, $length = 128, $level = 0) + private static function _dump( & $var, $length = 128, $max_level = 5, $level = 0) { if ($var === NULL) { @@ -327,7 +329,7 @@ class Kohana_Exception_Core extends Exception { { $output[] = "(\n$space$s*RECURSION*\n$space)"; } - elseif ($level < 5) + elseif ($level <= $max_level) { $output[] = "("; @@ -340,7 +342,7 @@ class Kohana_Exception_Core extends Exception { $key = '"'.$key.'"'; } - $output[] = "$space$s$key => ".Kohana_Exception::_dump($val, $length, $level + 1); + $output[] = "$space$s$key => ".Kohana_Exception::_dump($val, $length, $max_level, $level + 1); } unset($var[$marker]); @@ -377,7 +379,7 @@ class Kohana_Exception_Core extends Exception { { $output[] = "{\n$space$s*RECURSION*\n$space}"; } - elseif ($level < 5) + elseif ($level <= $max_level) { $output[] = "{"; @@ -397,7 +399,7 @@ class Kohana_Exception_Core extends Exception { $access = 'public'; } - $output[] = "$space$s$access $key => ".Kohana_Exception::_dump($val, $length, $level + 1); + $output[] = "$space$s$access $key => ".Kohana_Exception::_dump($val, $length, $max_level, $level + 1); } unset($objects[$hash]); diff --git a/system/libraries/Input.php b/system/libraries/Input.php index 04403854..7a277317 100644 --- a/system/libraries/Input.php +++ b/system/libraries/Input.php @@ -2,7 +2,7 @@ /** * Input library. * - * $Id: Input.php 4720 2009-12-17 21:15:03Z isaiah $ + * $Id: Input.php 4727 2009-12-23 19:03:05Z isaiah $ * * @package Core * @author Kohana Team @@ -79,35 +79,6 @@ class Input_Core { Kohana_Log::add('debug', 'Disable magic_quotes_gpc! It is evil and deprecated: http://php.net/magic_quotes'); } - // register_globals is enabled - if (ini_get('register_globals')) - { - if (isset($_REQUEST['GLOBALS'])) - { - // Prevent GLOBALS override attacks - exit('Global variable overload attack.'); - } - - // Destroy the REQUEST global - $_REQUEST = array(); - - // These globals are standard and should not be removed - $preserve = array('GLOBALS', '_REQUEST', '_GET', '_POST', '_FILES', '_COOKIE', '_SERVER', '_ENV', '_SESSION'); - - // This loop has the same effect as disabling register_globals - foreach (array_diff(array_keys($GLOBALS), $preserve) as $key) - { - global $$key; - $$key = NULL; - - // Unset the global variable - unset($GLOBALS[$key], $$key); - } - - // Warn the developer about register globals - Kohana_Log::add('debug', 'Disable register_globals! It is evil and deprecated: http://php.net/register_globals'); - } - if (is_array($_GET)) { foreach ($_GET as $key => $val) diff --git a/system/messages/core.php b/system/messages/core.php deleted file mode 100644 index 64f897e8..00000000 --- a/system/messages/core.php +++ /dev/null @@ -1,37 +0,0 @@ - array - ( - E_KOHANA => __('Framework Error'), // __('Please check the Kohana documentation for information about the following error.'), - E_PAGE_NOT_FOUND => __('Page Not Found'), // __('The requested page was not found. It may have moved, been deleted, or archived.'), - E_DATABASE_ERROR => __('Database Error'), // __('A database error occurred while performing the requested procedure. Please review the database error below for more information.'), - E_RECOVERABLE_ERROR => __('Recoverable Error'), // __('An error was detected which prevented the loading of this page. If this problem persists, please contact the website administrator.'), - E_ERROR => __('Fatal Error'), - E_COMPILE_ERROR => __('Fatal Error'), - E_CORE_ERROR => __('Fatal Error'), - E_USER_ERROR => __('Fatal Error'), - E_PARSE => __('Syntax Error'), - E_WARNING => __('Warning Message'), - E_COMPILE_WARNING => __('Warning Message'), - E_CORE_WARNING => __('Warning Message'), - E_USER_WARNING => __('Warning Message'), - E_STRICT => __('Strict Mode Error'), - E_NOTICE => __('Runtime Message'), - E_USER_NOTICE => __('Runtime Message'), - ), - 'config' => 'config file', - 'controller' => 'controller', - 'helper' => 'helper', - 'library' => 'library', - 'driver' => 'driver', - 'model' => 'model', - 'view' => 'view', -); - -// E_DEPRECATED is only defined in PHP >= 5.3.0 -if (defined('E_DEPRECATED')) -{ - $messages['errors'][E_DEPRECATED] = __('Deprecated'); -} \ No newline at end of file diff --git a/system/messages/kohana/core.php b/system/messages/kohana/core.php new file mode 100644 index 00000000..1361809b --- /dev/null +++ b/system/messages/kohana/core.php @@ -0,0 +1,37 @@ + array + ( + E_KOHANA => 'Framework Error', + E_PAGE_NOT_FOUND => 'Page Not Found', + E_DATABASE_ERROR => 'Database Error', + E_RECOVERABLE_ERROR => 'Recoverable Error', + E_ERROR => 'Fatal Error', + E_COMPILE_ERROR => 'Fatal Error', + E_CORE_ERROR => 'Fatal Error', + E_USER_ERROR => 'Fatal Error', + E_PARSE => 'Syntax Error', + E_WARNING => 'Warning Message', + E_COMPILE_WARNING => 'Warning Message', + E_CORE_WARNING => 'Warning Message', + E_USER_WARNING => 'Warning Message', + E_STRICT => 'Strict Mode Error', + E_NOTICE => 'Runtime Message', + E_USER_NOTICE => 'Runtime Message', + ), +); + +// E_DEPRECATED is only defined in PHP >= 5.3.0 +if (defined('E_DEPRECATED')) +{ + $messages['errors'][E_DEPRECATED] = 'Deprecated'; +} \ No newline at end of file diff --git a/system/messages/validation/default.php b/system/messages/validation/default.php index 2c59fa06..88580a6b 100644 --- a/system/messages/validation/default.php +++ b/system/messages/validation/default.php @@ -1,17 +1,25 @@ - 'The :field field is required', - 'length' => 'The :field field must be between :param1 and :param2 characters long', - 'depends_on' => 'The :field field requires the :param1 field', - 'matches' => 'The :field field must be the same as :param1', - 'email' => 'The :field field must be a valid email address', - 'decimal' => 'The :field field must be a decimal with :param1 places', - 'digit' => 'The :field field must be a digit', - 'in_array' => 'The :field field must be one of the available options', - 'alpha_numeric' => 'The :field field must consist only of alphabetical or numeric characters', - 'alpha_dash ' => 'The :field field must consist only of alphabetical, numeric, underscore and dash characters', - 'numeric ' => 'The :field field must be a valid number', - 'url' => 'The :field field must be a valid url', - 'phone' => 'The :field field must be a valid phone number', -); + 'The :field field is required', + 'length' => 'The :field field must be between :param1 and :param2 characters long', + 'depends_on' => 'The :field field requires the :param1 field', + 'matches' => 'The :field field must be the same as :param1', + 'email' => 'The :field field must be a valid email address', + 'decimal' => 'The :field field must be a decimal with :param1 places', + 'digit' => 'The :field field must be a digit', + 'in_array' => 'The :field field must be one of the available options', + 'alpha_numeric' => 'The :field field must consist only of alphabetical or numeric characters', + 'alpha_dash ' => 'The :field field must consist only of alphabetical, numeric, underscore and dash characters', + 'numeric ' => 'The :field field must be a valid number', + 'url' => 'The :field field must be a valid url', + 'phone' => 'The :field field must be a valid phone number', +); diff --git a/system/views/kohana/error.php b/system/views/kohana/error.php index b40c0f8a..aa6770c4 100644 --- a/system/views/kohana/error.php +++ b/system/views/kohana/error.php @@ -3,7 +3,7 @@ $error_id = uniqid('error'); ?>