diff options
Diffstat (limited to 'system')
167 files changed, 11364 insertions, 11038 deletions
diff --git a/system/KohanaLicense.html b/system/KohanaLicense.html index de2d5299..bc4bce28 100644 --- a/system/KohanaLicense.html +++ b/system/KohanaLicense.html @@ -12,7 +12,7 @@ <p>This license is a legal agreement between you and the Kohana Software Foundation for the use of Kohana Framework (the "Software"). By obtaining the Software you agree to comply with the terms and conditions of this license.</p> -<p>Copyright (c) 2007-2008 Kohana Team<br/>All rights reserved.</p> +<p>Copyright (c) 2007-2009 Kohana Team<br/>All rights reserved.</p> <p>Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:</p> diff --git a/system/config/cache.php b/system/config/cache.php index ccd3da4d..68682c0a 100644 --- a/system/config/cache.php +++ b/system/config/cache.php @@ -19,14 +19,10 @@ * thirty minutes. Specific lifetime can also be set when creating a new cache. * Setting this to 0 will never automatically delete caches. * - * requests - Average number of cache requests that will processed before all expired - * caches are deleted. This is commonly referred to as "garbage collection". - * Setting this to 0 or a negative number will disable automatic garbage collection. */ $config['default'] = array ( 'driver' => 'file', - 'params' => APPPATH.'cache', + 'params' => array('directory' => APPPATH.'cache', 'gc_probability' => 1000), 'lifetime' => 1800, - 'requests' => 1000 ); diff --git a/system/config/cache_memcache.php b/system/config/cache_memcache.php deleted file mode 100644 index 43d8f205..00000000 --- a/system/config/cache_memcache.php +++ /dev/null @@ -1,20 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * @package Cache:Memcache - * - * memcache server configuration. - */ -$config['servers'] = array -( - array - ( - 'host' => '127.0.0.1', - 'port' => 11211, - 'persistent' => FALSE, - ) -); - -/** - * Enable cache data compression. - */ -$config['compression'] = FALSE; diff --git a/system/config/cache_sqlite.php b/system/config/cache_sqlite.php deleted file mode 100644 index 818b8932..00000000 --- a/system/config/cache_sqlite.php +++ /dev/null @@ -1,10 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * @package Cache:SQLite - */ -$config['schema'] = -'CREATE TABLE caches( - id VARCHAR(127) PRIMARY KEY, - tags VARCHAR(255), - expiration INTEGER, - cache TEXT);';
\ No newline at end of file diff --git a/system/config/cache_xcache.php b/system/config/cache_xcache.php deleted file mode 100644 index 47aac25a..00000000 --- a/system/config/cache_xcache.php +++ /dev/null @@ -1,12 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * @package Cache:Xcache - * - * Xcache administrator username. - */ -$config['PHP_AUTH_USER'] = 'kohana'; - -/** - * Xcache administrator password. - */ -$config['PHP_AUTH_PW'] = 'kohana'; diff --git a/system/config/cookie.php b/system/config/cookie.php index b6ddfe4a..d370ecf0 100644 --- a/system/config/cookie.php +++ b/system/config/cookie.php @@ -26,7 +26,13 @@ $config['expire'] = 0; $config['secure'] = FALSE; /** - * Enable this option to disable the cookie from being accessed when using a - * secure protocol. This option is only available in PHP 5.2 and above. + * Enable this option to make the cookie accessible only through the + * HTTP protocol (e.g. no javascript access). This is not supported by all browsers. */ -$config['httponly'] = FALSE;
\ No newline at end of file +$config['httponly'] = FALSE; + +/** + * Cookie salt for signed cookies. + * Make sure you change this to a unique value. + */ +$config['salt'] = 'K0hAN4 15 Th3 B357';
\ No newline at end of file diff --git a/system/config/database.php b/system/config/database.php index 6519156a..2e53fa2b 100644 --- a/system/config/database.php +++ b/system/config/database.php @@ -25,7 +25,7 @@ */ $config['default'] = array ( - 'benchmark' => TRUE, + 'benchmark' => FALSE, 'persistent' => FALSE, 'connection' => array ( diff --git a/system/config/email.php b/system/config/email.php deleted file mode 100644 index c768367c..00000000 --- a/system/config/email.php +++ /dev/null @@ -1,22 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * SwiftMailer driver, used with the email helper. - * - * @see http://www.swiftmailer.org/wikidocs/v3/connections/nativemail - * @see http://www.swiftmailer.org/wikidocs/v3/connections/sendmail - * @see http://www.swiftmailer.org/wikidocs/v3/connections/smtp - * - * Valid drivers are: native, sendmail, smtp - */ -$config['driver'] = 'native'; - -/** - * To use secure connections with SMTP, set "port" to 465 instead of 25. - * To enable TLS, set "encryption" to "tls". - * - * Driver options: - * @param null native: no options - * @param string sendmail: executable path, with -bs or equivalent attached - * @param array smtp: hostname, (username), (password), (port), (auth), (encryption) - */ -$config['options'] = NULL; diff --git a/system/config/inflector.php b/system/config/inflector.php index 6dcfc2d3..b6cb8003 100644 --- a/system/config/inflector.php +++ b/system/config/inflector.php @@ -7,6 +7,7 @@ $config['uncountable'] = array 'art', 'baggage', 'dances', + 'data', 'equipment', 'fish', 'fuel', @@ -19,6 +20,7 @@ $config['uncountable'] = array 'information', 'knowledge', 'luggage', + 'metadata', 'money', 'music', 'news', diff --git a/system/config/locale.php b/system/config/locale.php index 3a268820..a62b4c3e 100644 --- a/system/config/locale.php +++ b/system/config/locale.php @@ -10,7 +10,8 @@ $config['language'] = array('en_US', 'English_United States'); /** - * Locale timezone. Defaults to use the server timezone. + * Locale timezone. Defaults to the timezone you have set in your php config + * This cannot be left empty, a valid timezone is required! * @see http://php.net/timezones */ -$config['timezone'] = '';
\ No newline at end of file +$config['timezone'] = ini_get('date.timezone');
\ No newline at end of file diff --git a/system/config/log.php b/system/config/log.php new file mode 100644 index 00000000..5be2c41e --- /dev/null +++ b/system/config/log.php @@ -0,0 +1,19 @@ +<?php defined('SYSPATH') OR die('No direct access allowed.'); + + +// Different log levels +$config['log_levels'] = array +( + 'error' => 1, + 'alert' => 2, + 'info' => 3, + 'debug' => 4, +); + +// See different log levels above +$config['log_threshold'] = 1; + +$config['date_format'] = 'Y-m-d H:i:s P'; + +// We can define multiple logging backends at the same time. +$config['drivers'] = array('file');
\ No newline at end of file diff --git a/system/config/mimes.php b/system/config/mimes.php index 6a960f24..c4318d58 100644 --- a/system/config/mimes.php +++ b/system/config/mimes.php @@ -6,7 +6,7 @@ * the operating system MIME list. * * If there are any missing options, please create a ticket on our issue tracker, - * http://kohanaphp.com/trac/newticket. Be sure to give the filename and + * http://dev.kohanaphp.com/projects/kohana2. Be sure to give the filename and * expected MIME type, as well as any additional information you can provide. */ $config = array diff --git a/system/config/pagination.php b/system/config/pagination.php deleted file mode 100644 index 808fc315..00000000 --- a/system/config/pagination.php +++ /dev/null @@ -1,25 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * @package Pagination - * - * Pagination configuration is defined in groups which allows you to easily switch - * between different pagination settings for different website sections. - * Note: all groups inherit and overwrite the default group. - * - * Group Options: - * directory - Views folder in which your pagination style templates reside - * style - Pagination style template (matches view filename) - * uri_segment - URI segment (int or 'label') in which the current page number can be found - * query_string - Alternative to uri_segment: query string key that contains the page number - * items_per_page - Number of items to display per page - * auto_hide - Automatically hides pagination for single pages - */ -$config['default'] = array -( - 'directory' => 'pagination', - 'style' => 'classic', - 'uri_segment' => 3, - 'query_string' => '', - 'items_per_page' => 20, - 'auto_hide' => FALSE, -); diff --git a/system/config/profiler.php b/system/config/profiler.php index 98ab5a49..90532f07 100644 --- a/system/config/profiler.php +++ b/system/config/profiler.php @@ -6,3 +6,7 @@ * Built in sections are benchmarks, database, session, post and cookies, custom sections can be used too. */ $config['show'] = TRUE; + +$config['time_decimals'] = 3; + +$config['memory_decimals'] = 2;
\ No newline at end of file diff --git a/system/config/session.php b/system/config/session.php index e287bae8..29eeafbd 100644 --- a/system/config/session.php +++ b/system/config/session.php @@ -25,7 +25,6 @@ $config['validate'] = array('user_agent'); /** * Enable or disable session encryption. * Note: this has no effect on the native session driver. - * Note: the cookie driver always encrypts session data. Set to TRUE for stronger encryption. */ $config['encryption'] = FALSE; @@ -38,8 +37,10 @@ $config['expiration'] = 7200; /** * Number of page loads before the session id is regenerated. * A value of 0 will disable automatic session id regeneration. + * NOTE: Enabling automatic session regeneration can cause a race condition see the + * docs for details: http://docs.kohanaphp.com/libraries/session#regenerate */ -$config['regenerate'] = 3; +$config['regenerate'] = 0; /** * Percentage probability that the gc (garbage collection) routine is started. diff --git a/system/config/sql_types.php b/system/config/sql_types.php index 4034c6f5..43a13c27 100644 --- a/system/config/sql_types.php +++ b/system/config/sql_types.php @@ -4,55 +4,90 @@ * * SQL data types. If there are missing values, please report them: * - * @link http://trac.kohanaphp.com/newticket + * @link http://dev.kohanaphp.com/projects/kohana2 */ $config = array ( - 'tinyint' => array('type' => 'int', 'max' => 127), - 'smallint' => array('type' => 'int', 'max' => 32767), - 'mediumint' => array('type' => 'int', 'max' => 8388607), - 'int' => array('type' => 'int', 'max' => 2147483647), - 'integer' => array('type' => 'int', 'max' => 2147483647), - 'bigint' => array('type' => 'int', 'max' => 9223372036854775807), - 'float' => array('type' => 'float'), - 'float unsigned' => array('type' => 'float', 'min' => 0), - 'boolean' => array('type' => 'boolean'), - 'time' => array('type' => 'string', 'format' => '00:00:00'), - 'time with time zone' => array('type' => 'string'), - 'date' => array('type' => 'string', 'format' => '0000-00-00'), - 'year' => array('type' => 'string', 'format' => '0000'), - 'datetime' => array('type' => 'string', 'format' => '0000-00-00 00:00:00'), + // SQL-92 + 'bit' => array('type' => 'string', 'exact' => TRUE), + 'bit varying' => array('type' => 'string'), + 'character' => array('type' => 'string', 'exact' => TRUE), + 'character varying' => array('type' => 'string'), + 'date' => array('type' => 'string'), + 'decimal' => array('type' => 'float', 'exact' => TRUE), + 'double precision' => array('type' => 'float'), + 'float' => array('type' => 'float'), + 'integer' => array('type' => 'int', 'min' => -2147483648, 'max' => 2147483647), + 'interval' => array('type' => 'string'), + 'national character' => array('type' => 'string', 'exact' => TRUE), + 'national character varying' => array('type' => 'string'), + 'numeric' => array('type' => 'float', 'exact' => TRUE), + 'real' => array('type' => 'float'), + 'smallint' => array('type' => 'int', 'min' => -32768, 'max' => 32767), + 'time' => array('type' => 'string'), + 'time with time zone' => array('type' => 'string'), + 'timestamp' => array('type' => 'string'), 'timestamp with time zone' => array('type' => 'string'), - 'char' => array('type' => 'string', 'exact' => TRUE), - 'binary' => array('type' => 'string', 'binary' => TRUE, 'exact' => TRUE), - 'varchar' => array('type' => 'string'), - 'varbinary' => array('type' => 'string', 'binary' => TRUE), - 'blob' => array('type' => 'string', 'binary' => TRUE), - 'text' => array('type' => 'string') -); -// DOUBLE -$config['double'] = $config['double precision'] = $config['decimal'] = $config['real'] = $config['numeric'] = $config['float']; -$config['double unsigned'] = $config['float unsigned']; + // SQL:1999 + //'array','ref','row' + 'binary large object' => array('type' => 'string', 'binary' => TRUE), + 'boolean' => array('type' => 'boolean'), + 'character large object' => array('type' => 'string'), + 'national character large object' => array('type' => 'string'), -// BIT -$config['bit'] = $config['boolean']; + // SQL:2003 + 'bigint' => array('type' => 'int', 'min' => -9223372036854775808, 'max' => 9223372036854775807), -// TIMESTAMP -$config['timestamp'] = $config['timestamp without time zone'] = $config['datetime']; + // SQL:2008 + 'binary' => array('type' => 'string', 'binary' => TRUE, 'exact' => TRUE), + 'binary varying' => array('type' => 'string', 'binary' => TRUE), -// ENUM -$config['enum'] = $config['set'] = $config['varchar']; + // MySQL + 'bigint unsigned' => array('type' => 'int', 'min' => 0, 'max' => 18446744073709551615), + 'decimal unsigned' => array('type' => 'float', 'exact' => TRUE, 'min' => 0.0), + 'double unsigned' => array('type' => 'float', 'min' => 0.0), + 'float unsigned' => array('type' => 'float', 'min' => 0.0), + 'integer unsigned' => array('type' => 'int', 'min' => 0, 'max' => 4294967295), + 'mediumint' => array('type' => 'int', 'min' => -8388608, 'max' => 8388607), + 'mediumint unsigned' => array('type' => 'int', 'min' => 0, 'max' => 16777215), + 'real unsigned' => array('type' => 'float', 'min' => 0.0), + 'smallint unsigned' => array('type' => 'int', 'min' => 0, 'max' => 65535), + 'text' => array('type' => 'string'), + 'tinyint' => array('type' => 'int', 'min' => -128, 'max' => 127), + 'tinyint unsigned' => array('type' => 'int', 'min' => 0, 'max' => 255), + 'year' => array('type' => 'string'), +); -// TEXT -$config['tinytext'] = $config['mediumtext'] = $config['longtext'] = $config['text']; +// SQL-92 +$config['char'] = $config['character']; +$config['char varying'] = $config['character varying']; +$config['dec'] = $config['decimal']; +$config['int'] = $config['integer']; +$config['nchar'] = $config['national char'] = $config['national character']; +$config['nchar varying'] = $config['national char varying'] = $config['national character varying']; +$config['varchar'] = $config['character varying']; -// BLOB -$config['tsvector'] = $config['tinyblob'] = $config['mediumblob'] = $config['longblob'] = $config['clob'] = $config['bytea'] = $config['blob']; +// SQL:1999 +$config['blob'] = $config['binary large object']; +$config['clob'] = $config['char large object'] = $config['character large object']; +$config['nclob'] = $config['nchar large object'] = $config['national character large object']; +$config['time without time zone'] = $config['time']; +$config['timestamp without time zone'] = $config['timestamp']; -// CHARACTER -$config['character'] = $config['char']; -$config['character varying'] = $config['varchar']; +// SQL:2008 +$config['varbinary'] = $config['binary varying']; -// TIME -$config['time without time zone'] = $config['time']; +// MySQL +$config['bool'] = $config['boolean']; +$config['datetime'] = $config['timestamp']; +$config['double'] = $config['double precision']; +$config['double precision unsigned'] = $config['double unsigned']; +$config['enum'] = $config['set'] = $config['character varying']; +$config['fixed'] = $config['decimal']; +$config['fixed unsigned'] = $config['decimal unsigned']; +$config['int unsigned'] = $config['integer unsigned']; +$config['longblob'] = $config['mediumblob'] = $config['tinyblob'] = $config['binary large object']; +$config['longtext'] = $config['mediumtext'] = $config['tinytext'] = $config['text']; +$config['numeric unsigned'] = $config['decimal unsigned']; +$config['nvarchar'] = $config['national varchar'] = $config['national character varying']; diff --git a/system/config/view.php b/system/config/view.php index 6bed22e0..a77af878 100644 --- a/system/config/view.php +++ b/system/config/view.php @@ -3,6 +3,7 @@ * @package Core * * Allowed non-php view types. Most file extensions are supported. + * Do not forget to add a valid MIME type in mimes.php */ $config['allowed_filetypes'] = array ( diff --git a/system/controllers/template.php b/system/controllers/template.php index 34d1a226..4267ee16 100644 --- a/system/controllers/template.php +++ b/system/controllers/template.php @@ -7,12 +7,12 @@ * To use it, declare your controller to extend this class: * `class Your_Controller extends Template_Controller` * - * $Id: template.php 3769 2008-12-15 00:48:56Z zombor $ + * $Id: template.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 */ abstract class Template_Controller extends Controller { diff --git a/system/core/Benchmark.php b/system/core/Benchmark.php index ce230f11..4b2f78b0 100644 --- a/system/core/Benchmark.php +++ b/system/core/Benchmark.php @@ -2,12 +2,12 @@ /** * Simple benchmarking. * - * $Id: Benchmark.php 4149 2009-04-01 13:32:50Z Shadowhand $ + * $Id: Benchmark.php 4679 2009-11-10 01:45:52Z isaiah $ * * @package Core * @author Kohana Team - * @copyright (c) 2007 Kohana Team - * @license http://kohanaphp.com/license.html + * @copyright (c) 2007-2009 Kohana Team + * @license http://kohanaphp.com/license */ final class Benchmark { @@ -22,6 +22,9 @@ final class Benchmark { */ public static function start($name) { + if (isset(self::$marks[$name]) AND self::$marks[$name][0]['stop'] === FALSE) + throw new Kohana_Exception('A benchmark named :name is already running.', array(':name' => $name)); + if ( ! isset(self::$marks[$name])) { self::$marks[$name] = array(); diff --git a/system/core/Bootstrap.php b/system/core/Bootstrap.php deleted file mode 100644 index edfb086d..00000000 --- a/system/core/Bootstrap.php +++ /dev/null @@ -1,58 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * Kohana process control file, loaded by the front controller. - * - * $Id: Bootstrap.php 4135 2009-03-28 17:51:04Z zombor $ - * - * @package Core - * @author Kohana Team - * @copyright (c) 2007 Kohana Team - * @license http://kohanaphp.com/license.html - */ - -define('KOHANA_VERSION', '2.3.3'); -define('KOHANA_CODENAME', 'aegolius'); - -// Test of Kohana is running in Windows -define('KOHANA_IS_WIN', DIRECTORY_SEPARATOR === '\\'); - -// Kohana benchmarks are prefixed to prevent collisions -define('SYSTEM_BENCHMARK', 'system_benchmark'); - -// Load benchmarking support -require SYSPATH.'core/Benchmark'.EXT; - -// Start total_execution -Benchmark::start(SYSTEM_BENCHMARK.'_total_execution'); - -// Start kohana_loading -Benchmark::start(SYSTEM_BENCHMARK.'_kohana_loading'); - -// Load core files -require SYSPATH.'core/utf8'.EXT; -require SYSPATH.'core/Event'.EXT; -require SYSPATH.'core/Kohana'.EXT; - -// Prepare the environment -Kohana::setup(); - -// End kohana_loading -Benchmark::stop(SYSTEM_BENCHMARK.'_kohana_loading'); - -// Start system_initialization -Benchmark::start(SYSTEM_BENCHMARK.'_system_initialization'); - -// Prepare the system -Event::run('system.ready'); - -// Determine routing -Event::run('system.routing'); - -// End system_initialization -Benchmark::stop(SYSTEM_BENCHMARK.'_system_initialization'); - -// Make the magic happen! -Event::run('system.execute'); - -// Clean up and exit -Event::run('system.shutdown');
\ No newline at end of file diff --git a/system/core/Event.php b/system/core/Event.php index 06468a8d..a9b88034 100644 --- a/system/core/Event.php +++ b/system/core/Event.php @@ -4,21 +4,21 @@ * to be added to 'events'. Events can be run multiple times, and can also * process event-specific data. By default, Kohana has several system events. * - * $Id: Event.php 4390 2009-06-04 03:05:36Z zombor $ + * $Id: Event.php 4679 2009-11-10 01:45:52Z isaiah $ * * @package Core * @author Kohana Team - * @copyright (c) 2007 Kohana Team - * @license http://kohanaphp.com/license.html + * @copyright (c) 2007-2009 Kohana Team + * @license http://kohanaphp.com/license * @link http://docs.kohanaphp.com/general/events */ -final class Event { +abstract class Event_Core { // Event callbacks - private static $events = array(); + protected static $events = array(); // Cache of events that have been run - private static $has_run = array(); + protected static $has_run = array(); // Data that can be processed during events public static $data; @@ -26,25 +26,26 @@ final class Event { /** * Add a callback to an event queue. * - * @param string event name - * @param array http://php.net/callback + * @param string event name + * @param array http://php.net/callback + * @param boolean prevent duplicates * @return boolean */ - public static function add($name, $callback) + public static function add($name, $callback, $unique = FALSE) { - if ( ! isset(self::$events[$name])) + if ( ! isset(Event::$events[$name])) { // Create an empty event if it is not yet defined - self::$events[$name] = array(); + Event::$events[$name] = array(); } - elseif (in_array($callback, self::$events[$name], TRUE)) + elseif ($unique AND in_array($callback, Event::$events[$name], TRUE)) { // The event already exists return FALSE; } // Add the event - self::$events[$name][] = $callback; + Event::$events[$name][] = $callback; return TRUE; } @@ -59,15 +60,15 @@ final class Event { */ public static function add_before($name, $existing, $callback) { - if (empty(self::$events[$name]) OR ($key = array_search($existing, self::$events[$name])) === FALSE) + if (empty(Event::$events[$name]) OR ($key = array_search($existing, Event::$events[$name])) === FALSE) { // Just add the event if there are no events - return self::add($name, $callback); + return Event::add($name, $callback); } else { // Insert the event immediately before the existing event - return self::insert_event($name, $key, $callback); + return Event::insert_event($name, $key, $callback); } } @@ -81,15 +82,15 @@ final class Event { */ public static function add_after($name, $existing, $callback) { - if (empty(self::$events[$name]) OR ($key = array_search($existing, self::$events[$name])) === FALSE) + if (empty(Event::$events[$name]) OR ($key = array_search($existing, Event::$events[$name])) === FALSE) { // Just add the event if there are no events - return self::add($name, $callback); + return Event::add($name, $callback); } else { // Insert the event immediately after the existing event - return self::insert_event($name, $key + 1, $callback); + return Event::insert_event($name, $key + 1, $callback); } } @@ -103,18 +104,18 @@ final class Event { */ private static function insert_event($name, $key, $callback) { - if (in_array($callback, self::$events[$name], TRUE)) + if (in_array($callback, Event::$events[$name], TRUE)) return FALSE; // Add the new event at the given key location - self::$events[$name] = array_merge + Event::$events[$name] = array_merge ( // Events before the key - array_slice(self::$events[$name], 0, $key), + array_slice(Event::$events[$name], 0, $key), // New event callback array($callback), // Events after the key - array_slice(self::$events[$name], $key) + array_slice(Event::$events[$name], $key) ); return TRUE; @@ -130,21 +131,21 @@ final class Event { */ public static function replace($name, $existing, $callback) { - if (empty(self::$events[$name]) OR ($key = array_search($existing, self::$events[$name], TRUE)) === FALSE) + if (empty(Event::$events[$name]) OR ($key = array_search($existing, Event::$events[$name], TRUE)) === FALSE) return FALSE; - if ( ! in_array($callback, self::$events[$name], TRUE)) + if ( ! in_array($callback, Event::$events[$name], TRUE)) { // Replace the exisiting event with the new event - self::$events[$name][$key] = $callback; + Event::$events[$name][$key] = $callback; } else { // Remove the existing event from the queue - unset(self::$events[$name][$key]); + unset(Event::$events[$name][$key]); // Reset the array so the keys are ordered properly - self::$events[$name] = array_values(self::$events[$name]); + Event::$events[$name] = array_values(Event::$events[$name]); } return TRUE; @@ -158,7 +159,7 @@ final class Event { */ public static function get($name) { - return empty(self::$events[$name]) ? array() : self::$events[$name]; + return empty(Event::$events[$name]) ? array() : Event::$events[$name]; } /** @@ -172,18 +173,18 @@ final class Event { { if ($callback === FALSE) { - self::$events[$name] = array(); + Event::$events[$name] = array(); } - elseif (isset(self::$events[$name])) + elseif (isset(Event::$events[$name])) { // Loop through each of the event callbacks and compare it to the // callback requested for removal. The callback is removed if it // matches. - foreach (self::$events[$name] as $i => $event_callback) + foreach (Event::$events[$name] as $i => $event_callback) { if ($callback === $event_callback) { - unset(self::$events[$name][$i]); + unset(Event::$events[$name][$i]); } } } @@ -198,24 +199,24 @@ final class Event { */ public static function run($name, & $data = NULL) { - if ( ! empty(self::$events[$name])) + if ( ! empty(Event::$events[$name])) { // So callbacks can access Event::$data - self::$data =& $data; - $callbacks = self::get($name); + Event::$data =& $data; + $callbacks = Event::get($name); foreach ($callbacks as $callback) { - call_user_func($callback); + call_user_func_array($callback, array(&$data)); } // Do this to prevent data from getting 'stuck' $clear_data = ''; - self::$data =& $clear_data; + Event::$data =& $clear_data; } // The event has been run! - self::$has_run[$name] = $name; + Event::$has_run[$name] = $name; } /** @@ -226,7 +227,7 @@ final class Event { */ public static function has_run($name) { - return isset(self::$has_run[$name]); + return isset(Event::$has_run[$name]); } } // End Event
\ No newline at end of file diff --git a/system/core/Kohana.php b/system/core/Kohana.php index 8027975d..5258d635 100644 --- a/system/core/Kohana.php +++ b/system/core/Kohana.php @@ -2,60 +2,48 @@ /** * Provides Kohana-specific helper functions. This is where the magic happens! * - * $Id: Kohana.php 4372 2009-05-28 17:00:34Z ixmatus $ + * $Id: Kohana.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 */ -final class Kohana { + +// Test of Kohana is running in Windows +define('KOHANA_IS_WIN', DIRECTORY_SEPARATOR === '\\'); + +abstract class Kohana_Core { + + const VERSION = '2.4'; + const CODENAME = 'no_codename'; + const CHARSET = 'UTF-8'; + const LOCALE = 'en_US'; // The singleton instance of the controller public static $instance; // Output buffering level - private static $buffer_level; - - // Will be set to TRUE when an exception is caught - public static $has_error = FALSE; + protected static $buffer_level; // The final output that will displayed by Kohana public static $output = ''; - // The current user agent - public static $user_agent; - // The current locale public static $locale; - // Configuration - private static $configuration; - // Include paths - private static $include_paths; - - // Logged messages - private static $log; + protected static $include_paths; // Cache lifetime - private static $cache_lifetime; - - // Log levels - private static $log_levels = array - ( - 'error' => 1, - 'alert' => 2, - 'info' => 3, - 'debug' => 4, - ); + protected static $cache_lifetime; // Internal caches and write status - private static $internal_cache = array(); - private static $write_cache; - private static $internal_cache_path; - private static $internal_cache_key; - private static $internal_cache_encrypt; + protected static $internal_cache = array(); + protected static $write_cache; + protected static $internal_cache_path; + protected static $internal_cache_key; + protected static $internal_cache_encrypt; /** * Sets up the PHP environment. Adds error/exception handling, output @@ -75,10 +63,12 @@ final class Kohana { { static $run; - // This function can only be run once + // Only run this function once if ($run === TRUE) return; + $run = TRUE; + // Start the environment setup benchmark Benchmark::start(SYSTEM_BENCHMARK.'_environment_setup'); @@ -91,89 +81,86 @@ final class Kohana { // Define database error constant define('E_DATABASE_ERROR', 44); - if (self::$cache_lifetime = self::config('core.internal_cache')) + // Set the default charset for mb_* functions + mb_internal_encoding(Kohana::CHARSET); + + if (Kohana_Config::instance()->loaded() === FALSE) + { + // Re-parse the include paths + Kohana::include_paths(TRUE); + } + + if (Kohana::$cache_lifetime = Kohana::config('core.internal_cache')) { // Are we using encryption for caches? - self::$internal_cache_encrypt = self::config('core.internal_cache_encrypt'); - - if(self::$internal_cache_encrypt===TRUE) + Kohana::$internal_cache_encrypt = Kohana::config('core.internal_cache_encrypt'); + + if(Kohana::$internal_cache_encrypt===TRUE) { - self::$internal_cache_key = self::config('core.internal_cache_key'); - + Kohana::$internal_cache_key = Kohana::config('core.internal_cache_key'); + // Be sure the key is of acceptable length for the mcrypt algorithm used - self::$internal_cache_key = substr(self::$internal_cache_key, 0, 24); + Kohana::$internal_cache_key = substr(Kohana::$internal_cache_key, 0, 24); } - + // Set the directory to be used for the internal cache - if ( ! self::$internal_cache_path = self::config('core.internal_cache_path')) + if ( ! Kohana::$internal_cache_path = Kohana::config('core.internal_cache_path')) { - self::$internal_cache_path = APPPATH.'cache/'; + Kohana::$internal_cache_path = APPPATH.'cache/'; } // Load cached configuration and language files - self::$internal_cache['configuration'] = self::cache('configuration', self::$cache_lifetime); - self::$internal_cache['language'] = self::cache('language', self::$cache_lifetime); + Kohana::$internal_cache['configuration'] = Kohana::cache('configuration', Kohana::$cache_lifetime); + Kohana::$internal_cache['language'] = Kohana::cache('language', Kohana::$cache_lifetime); // Load cached file paths - self::$internal_cache['find_file_paths'] = self::cache('find_file_paths', self::$cache_lifetime); + Kohana::$internal_cache['find_file_paths'] = Kohana::cache('find_file_paths', Kohana::$cache_lifetime); // Enable cache saving - Event::add('system.shutdown', array(__CLASS__, 'internal_cache_save')); - } - - // Disable notices and "strict" errors - $ER = error_reporting(~E_NOTICE & ~E_STRICT); - - // Set the user agent - self::$user_agent = ( ! empty($_SERVER['HTTP_USER_AGENT']) ? trim($_SERVER['HTTP_USER_AGENT']) : ''); - - if (function_exists('date_default_timezone_set')) - { - $timezone = self::config('locale.timezone'); - - // Set default timezone, due to increased validation of date settings - // which cause massive amounts of E_NOTICEs to be generated in PHP 5.2+ - date_default_timezone_set(empty($timezone) ? date_default_timezone_get() : $timezone); + Event::add('system.shutdown', array('Kohana', 'internal_cache_save')); } - // Restore error reporting - error_reporting($ER); - // Start output buffering - ob_start(array(__CLASS__, 'output_buffer')); + ob_start(array('Kohana', 'output_buffer')); // Save buffering level - self::$buffer_level = ob_get_level(); + Kohana::$buffer_level = ob_get_level(); // Set autoloader spl_autoload_register(array('Kohana', 'auto_load')); - // Set error handler - set_error_handler(array('Kohana', 'exception_handler')); - - // Set exception handler - set_exception_handler(array('Kohana', 'exception_handler')); + // Register a shutdown function to handle system.shutdown events + register_shutdown_function(array('Kohana', 'shutdown')); // Send default text/html UTF-8 header - header('Content-Type: text/html; charset=UTF-8'); + header('Content-Type: text/html; charset='.Kohana::CHARSET); + + // Load i18n + new I18n; + + // Enable exception handling + Kohana_Exception::enable(); + + // Enable error handling + Kohana_PHP_Exception::enable(); // Load locales - $locales = self::config('locale.language'); + $locales = Kohana::config('locale.language'); - // Make first locale UTF-8 - $locales[0] .= '.UTF-8'; + // Make first locale the defined Kohana charset + $locales[0] .= '.'.Kohana::CHARSET; // Set locale information - self::$locale = setlocale(LC_ALL, $locales); + Kohana::$locale = setlocale(LC_ALL, $locales); - if (self::$configuration['core']['log_threshold'] > 0) - { - // Set the log directory - self::log_directory(self::$configuration['core']['log_directory']); + // Default to the default locale when none of the user defined ones where accepted + Kohana::$locale = ! Kohana::$locale ? Kohana::LOCALE.'.'.Kohana::CHARSET : Kohana::$locale; - // Enable log writing at shutdown - register_shutdown_function(array(__CLASS__, 'log_save')); - } + // Set locale for the I18n system + I18n::set_locale(Kohana::$locale); + + // Set and validate the timezone + date_default_timezone_set(Kohana::config('locale.timezone')); // Enable Kohana routing Event::add('system.routing', array('Router', 'find_uri')); @@ -183,15 +170,12 @@ final class Kohana { Event::add('system.execute', array('Kohana', 'instance')); // Enable Kohana 404 pages - Event::add('system.404', array('Kohana', 'show_404')); - - // Enable Kohana output handling - Event::add('system.shutdown', array('Kohana', 'shutdown')); + Event::add('system.404', array('Kohana_404_Exception', 'trigger')); - if (self::config('core.enable_hooks') === TRUE) + if (Kohana::config('core.enable_hooks') === TRUE) { // Find all the hook files - $hooks = self::list_files('hooks', TRUE); + $hooks = Kohana::list_files('hooks', TRUE); foreach ($hooks as $file) { @@ -200,14 +184,39 @@ final class Kohana { } } - // Setup is complete, prevent it from being run again - $run = TRUE; - // Stop the environment setup routine Benchmark::stop(SYSTEM_BENCHMARK.'_environment_setup'); } /** + * Cleans up the PHP environment. Disables error/exception handling and the + * auto-loading method and closes the output buffer. + * + * This method does not need to be called during normal system execution, + * however in some advanced situations it can be helpful. @see #1781 + * + * @return void + */ + public static function cleanup() + { + static $run; + + // Only run this function once + if ($run === TRUE) + return; + + $run = TRUE; + + Kohana_Exception::disable(); + + Kohana_PHP_Exception::disable(); + + spl_autoload_unregister(array('Kohana', 'auto_load')); + + Kohana::close_buffers(); + } + + /** * Loads the controller and initializes it. Runs the pre_controller, * post_controller_constructor, and post_controller events. Triggers * a system.404 event when the route cannot be mapped to a controller. @@ -218,12 +227,12 @@ final class Kohana { */ public static function & instance() { - if (self::$instance === NULL) + if (Kohana::$instance === NULL) { Benchmark::start(SYSTEM_BENCHMARK.'_controller_setup'); // Include the Controller file - require Router::$controller_path; + require_once Router::$controller_path; try { @@ -297,7 +306,7 @@ final class Kohana { Benchmark::stop(SYSTEM_BENCHMARK.'_controller_execution'); } - return self::$instance; + return Kohana::$instance; } /** @@ -312,279 +321,44 @@ final class Kohana { if ($process === TRUE) { // Add APPPATH as the first path - self::$include_paths = array(APPPATH); + Kohana::$include_paths = array(APPPATH); - foreach (self::$configuration['core']['modules'] as $path) + foreach (Kohana::config('core.modules') as $path) { if ($path = str_replace('\\', '/', realpath($path))) { // Add a valid path - self::$include_paths[] = $path.'/'; + Kohana::$include_paths[] = $path.'/'; } } // Add SYSPATH as the last path - self::$include_paths[] = SYSPATH; - - // Local fix for Kohana Ticket:2276 - self::$internal_cache['find_file_paths'] = array(); - if ( ! isset(self::$write_cache['find_file_paths'])) - { - // Write cache at shutdown - self::$write_cache['find_file_paths'] = TRUE; - } - } - - return self::$include_paths; - } - - /** - * Get a config item or group. - * - * @param string item name - * @param boolean force a forward slash (/) at the end of the item - * @param boolean is the item required? - * @return mixed - */ - public static function config($key, $slash = FALSE, $required = TRUE) - { - if (self::$configuration === NULL) - { - // Load core configuration - self::$configuration['core'] = self::config_load('core'); - - // Re-parse the include paths - self::include_paths(TRUE); - } - - // Get the group name from the key - $group = explode('.', $key, 2); - $group = $group[0]; + Kohana::$include_paths[] = SYSPATH; - if ( ! isset(self::$configuration[$group])) - { - // Load the configuration group - self::$configuration[$group] = self::config_load($group, $required); - } - - // Get the value of the key string - $value = self::key_string(self::$configuration, $key); - - if ($slash === TRUE AND is_string($value) AND $value !== '') - { - // Force the value to end with "/" - $value = rtrim($value, '/').'/'; - } - - return $value; - } - - /** - * Sets a configuration item, if allowed. - * - * @param string config key string - * @param string config value - * @return boolean - */ - public static function config_set($key, $value) - { - // Do this to make sure that the config array is already loaded - self::config($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 =& self::$configuration; - $last = count($keys) - 1; - - foreach ($keys as $i => $k) - { - if ($i === $last) + // Clear cached include paths + self::$internal_cache['find_file_paths'] = array(); + if ( ! isset(self::$write_cache['find_file_paths'])) { - $conf[$k] = $value; + // Write cache at shutdown + self::$write_cache['find_file_paths'] = TRUE; } - else - { - $conf =& $conf[$k]; - } - } - if ($key === 'core.modules') - { - // Reprocess the include paths - self::include_paths(TRUE); } - return TRUE; + return Kohana::$include_paths; } /** - * Load a config file. + * Get a config item or group proxies Kohana_Config. * - * @param string config filename, without extension - * @param boolean is the file required? - * @return array - */ - public static function config_load($name, $required = TRUE) - { - if ($name === 'core') - { - // Load the application configuration file - require APPPATH.'config/config'.EXT; - - if ( ! isset($config['site_domain'])) - { - // Invalid config file - die('Your Kohana application configuration file is not valid.'); - } - - return $config; - } - - if (isset(self::$internal_cache['configuration'][$name])) - return self::$internal_cache['configuration'][$name]; - - // Load matching configs - $configuration = array(); - - if ($files = self::find_file('config', $name, $required)) - { - foreach ($files as $file) - { - require $file; - - if (isset($config) AND is_array($config)) - { - // Merge in configuration - $configuration = array_merge($configuration, $config); - } - } - } - - if ( ! isset(self::$write_cache['configuration'])) - { - // Cache has changed - self::$write_cache['configuration'] = TRUE; - } - - return self::$internal_cache['configuration'][$name] = $configuration; - } - - /** - * Clears a config group from the cached configuration. - * - * @param string config group - * @return void - */ - public static function config_clear($group) - { - // Remove the group from config - unset(self::$configuration[$group], self::$internal_cache['configuration'][$group]); - - if ( ! isset(self::$write_cache['configuration'])) - { - // Cache has changed - self::$write_cache['configuration'] = TRUE; - } - } - - /** - * Add a new message to the log. - * - * @param string type of message - * @param string message text - * @return void - */ - public static function log($type, $message) - { - if (self::$log_levels[$type] <= self::$configuration['core']['log_threshold']) - { - $message = array(date('Y-m-d H:i:s P'), $type, $message); - - // Run the system.log event - Event::run('system.log', $message); - - self::$log[] = $message; - } - } - - /** - * Save all currently logged messages. - * - * @return void - */ - public static function log_save() - { - if (empty(self::$log) OR self::$configuration['core']['log_threshold'] < 1) - return; - - // Filename of the log - $filename = self::log_directory().date('Y-m-d').'.log'.EXT; - - if ( ! is_file($filename)) - { - // Write the SYSPATH checking header - file_put_contents($filename, - '<?php defined(\'SYSPATH\') or die(\'No direct script access.\'); ?>'.PHP_EOL.PHP_EOL); - - // Prevent external writes - chmod($filename, 0644); - } - - // Messages to write - $messages = array(); - - do - { - // Load the next mess - list ($date, $type, $text) = array_shift(self::$log); - - // Add a new message line - $messages[] = $date.' --- '.$type.': '.$text; - } - while ( ! empty(self::$log)); - - // Write messages to log file - file_put_contents($filename, implode(PHP_EOL, $messages).PHP_EOL, FILE_APPEND); - } - - /** - * Get or set the logging directory. - * - * @param string new log directory - * @return string + * @param string item name + * @param boolean force a forward slash (/) at the end of the item + * @param boolean is the item required? + * @return mixed */ - public static function log_directory($dir = NULL) + public static function config($key, $slash = FALSE, $required = FALSE) { - static $directory; - - if ( ! empty($dir)) - { - // Get the directory path - $dir = realpath($dir); - - if (is_dir($dir) AND is_writable($dir)) - { - // Change the log directory - $directory = str_replace('\\', '/', $dir).'/'; - } - else - { - // Log directory is invalid - throw new Kohana_Exception('core.log_dir_unwritable', $dir); - } - } - - return $directory; + return Kohana_Config::instance()->get($key,$slash,$required); } /** @@ -599,7 +373,7 @@ final class Kohana { { if ($lifetime > 0) { - $path = self::$internal_cache_path.'kohana_'.$name; + $path = Kohana::$internal_cache_path.'kohana_'.$name; if (is_file($path)) { @@ -607,17 +381,17 @@ final class Kohana { if ((time() - filemtime($path)) < $lifetime) { // Cache is valid! Now, do we need to decrypt it? - if(self::$internal_cache_encrypt===TRUE) + if(Kohana::$internal_cache_encrypt===TRUE) { $data = file_get_contents($path); - + $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB); $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND); - - $decrypted_text = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, self::$internal_cache_key, $data, MCRYPT_MODE_ECB, $iv); - + + $decrypted_text = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, Kohana::$internal_cache_key, $data, MCRYPT_MODE_ECB, $iv); + $cache = unserialize($decrypted_text); - + // If the key changed, delete the cache file if(!$cache) unlink($path); @@ -656,7 +430,7 @@ final class Kohana { if ($lifetime < 1) return FALSE; - $path = self::$internal_cache_path.'kohana_'.$name; + $path = Kohana::$internal_cache_path.'kohana_'.$name; if ($data === NULL) { @@ -666,15 +440,15 @@ final class Kohana { else { // Using encryption? Encrypt the data when we write it - if(self::$internal_cache_encrypt===TRUE) + if(Kohana::$internal_cache_encrypt===TRUE) { // Encrypt and write data to cache file $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB); $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND); - + // Serialize and encrypt! - $encrypted_text = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, self::$internal_cache_key, serialize($data), MCRYPT_MODE_ECB, $iv); - + $encrypted_text = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, Kohana::$internal_cache_key, serialize($data), MCRYPT_MODE_ECB, $iv); + return (bool) file_put_contents($path, $encrypted_text); } else @@ -699,15 +473,16 @@ final class Kohana { // Run the send_headers event Event::run('system.send_headers'); } - - self::$output = $output; - + + // Set final output + Kohana::$output = $output; + // Set and return the final output - return self::$output; + return Kohana::$output; } /** - * Closes all open output buffers, either by flushing or cleaning, and stores the Kohana + * Closes all open output buffers, either by flushing or cleaning, and stores * output buffer for display during shutdown. * * @param boolean disable to clear buffers, rather than flushing @@ -715,12 +490,12 @@ final class Kohana { */ public static function close_buffers($flush = TRUE) { - if (ob_get_level() >= self::$buffer_level) + if (ob_get_level() >= Kohana::$buffer_level) { // Set the close function $close = ($flush === TRUE) ? 'ob_end_flush' : 'ob_end_clean'; - while (ob_get_level() > self::$buffer_level) + while (ob_get_level() > Kohana::$buffer_level) { // Flush or clean the buffer $close(); @@ -738,14 +513,25 @@ final class Kohana { */ public static function shutdown() { + static $run; + + // Only run this function once + if ($run === TRUE) + return; + + $run = TRUE; + + // Run system.shutdown event + Event::run('system.shutdown'); + // Close output buffers - self::close_buffers(TRUE); + Kohana::close_buffers(TRUE); // Run the output event - Event::run('system.display', self::$output); + Event::run('system.display', Kohana::$output); // Render the final output - self::render(self::$output); + Kohana::render(Kohana::$output); } /** @@ -756,7 +542,7 @@ final class Kohana { */ public static function render($output) { - if (self::config('core.render_stats') === TRUE) + if (Kohana::config('core.render_stats') === TRUE) { // Fetch memory usage in MB $memory = function_exists('memory_get_usage') ? (memory_get_usage() / 1024 / 1024) : 0; @@ -776,8 +562,8 @@ final class Kohana { ), array ( - KOHANA_VERSION, - KOHANA_CODENAME, + KOHANA::VERSION, + KOHANA::CODENAME, $benchmark['time'], number_format($memory, 2).'MB', count(get_included_files()), @@ -786,215 +572,44 @@ final class Kohana { ); } - if ($level = self::config('core.output_compression') AND ini_get('output_handler') !== 'ob_gzhandler' AND (int) ini_get('zlib.output_compression') === 0) + if ($level = Kohana::config('core.output_compression') AND ini_get('output_handler') !== 'ob_gzhandler' AND (int) ini_get('zlib.output_compression') === 0) { - if ($level < 1 OR $level > 9) - { - // Normalize the level to be an integer between 1 and 9. This - // step must be done to prevent gzencode from triggering an error - $level = max(1, min($level, 9)); - } - - if (stripos(@$_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE) + if ($compress = request::preferred_encoding(array('gzip','deflate'), TRUE)) { - $compress = 'gzip'; - } - elseif (stripos(@$_SERVER['HTTP_ACCEPT_ENCODING'], 'deflate') !== FALSE) - { - $compress = 'deflate'; - } - } + if ($level < 1 OR $level > 9) + { + // Normalize the level to be an integer between 1 and 9. This + // step must be done to prevent gzencode from triggering an error + $level = max(1, min($level, 9)); + } - if (isset($compress) AND $level > 0) - { - switch ($compress) - { - case 'gzip': + if ($compress === 'gzip') + { // Compress output using gzip $output = gzencode($output, $level); - break; - case 'deflate': + } + elseif ($compress === 'deflate') + { // Compress output using zlib (HTTP deflate) $output = gzdeflate($output, $level); - break; - } - - // This header must be sent with compressed content to prevent - // browser caches from breaking - header('Vary: Accept-Encoding'); - - // Send the content encoding header - header('Content-Encoding: '.$compress); + } - // Sending Content-Length in CGI can result in unexpected behavior - if (stripos(PHP_SAPI, 'cgi') === FALSE) - { - header('Content-Length: '.strlen($output)); - } - } + // This header must be sent with compressed content to prevent + // browser caches from breaking + header('Vary: Accept-Encoding'); - echo $output; - } + // Send the content encoding header + header('Content-Encoding: '.$compress); - /** - * Displays a 404 page. - * - * @throws Kohana_404_Exception - * @param string URI of page - * @param string custom template - * @return void - */ - public static function show_404($page = FALSE, $template = FALSE) - { - throw new Kohana_404_Exception($page, $template); - } - - /** - * Dual-purpose PHP error and exception handler. Uses the kohana_error_page - * view to display the message. - * - * @param integer|object exception object or error code - * @param string error message - * @param string filename - * @param integer line number - * @return void - */ - public static function exception_handler($exception, $message = NULL, $file = NULL, $line = NULL) - { - try - { - // PHP errors have 5 args, always - $PHP_ERROR = (func_num_args() === 5); - - // Test to see if errors should be displayed - if ($PHP_ERROR AND (error_reporting() & $exception) === 0) - return; - - // This is useful for hooks to determine if a page has an error - self::$has_error = TRUE; - - // Error handling will use exactly 5 args, every time - if ($PHP_ERROR) - { - $code = $exception; - $type = 'PHP Error'; - $template = 'kohana_error_page'; - } - else - { - $code = $exception->getCode(); - $type = get_class($exception); - $message = $exception->getMessage(); - $file = $exception->getFile(); - $line = $exception->getLine(); - $template = ($exception instanceof Kohana_Exception) ? $exception->getTemplate() : 'kohana_error_page'; - } - - if (is_numeric($code)) - { - $codes = self::lang('errors'); - - if ( ! empty($codes[$code])) - { - list($level, $error, $description) = $codes[$code]; - } - else - { - $level = 1; - $error = $PHP_ERROR ? 'Unknown Error' : get_class($exception); - $description = ''; - } - } - else - { - // Custom error message, this will never be logged - $level = 5; - $error = $code; - $description = ''; - } - - // Remove the DOCROOT from the path, as a security precaution - $file = str_replace('\\', '/', realpath($file)); - $file = preg_replace('|^'.preg_quote(DOCROOT).'|', '', $file); - - if ($level <= self::$configuration['core']['log_threshold']) - { - // Log the error - self::log('error', self::lang('core.uncaught_exception', $type, $message, $file, $line)); - } - - if ($PHP_ERROR) - { - $description = self::lang('errors.'.E_RECOVERABLE_ERROR); - $description = is_array($description) ? $description[2] : ''; - - if ( ! headers_sent()) + // Sending Content-Length in CGI can result in unexpected behavior + if (stripos(PHP_SAPI, 'cgi') === FALSE) { - // Send the 500 header - header('HTTP/1.1 500 Internal Server Error'); + header('Content-Length: '.strlen($output)); } } - else - { - if (method_exists($exception, 'sendHeaders') AND ! headers_sent()) - { - // Send the headers if they have not already been sent - $exception->sendHeaders(); - } - } - - // Close all output buffers except for Kohana - while (ob_get_level() > self::$buffer_level) - { - ob_end_clean(); - } - - // Test if display_errors is on - if (self::$configuration['core']['display_errors'] === TRUE) - { - if ( ! IN_PRODUCTION AND $line != FALSE) - { - // Remove the first entry of debug_backtrace(), it is the exception_handler call - $trace = $PHP_ERROR ? array_slice(debug_backtrace(), 1) : $exception->getTrace(); - - // Beautify backtrace - $trace = self::backtrace($trace); - } - - // Load the error - require self::find_file('views', empty($template) ? 'kohana_error_page' : $template); - } - else - { - // Get the i18n messages - $error = self::lang('core.generic_error'); - $message = self::lang('core.errors_disabled', url::site(), url::site(Router::$current_uri)); - - // Load the errors_disabled view - require self::find_file('views', 'kohana_error_disabled'); - } - - if ( ! Event::has_run('system.shutdown')) - { - // Run the shutdown even to ensure a clean exit - Event::run('system.shutdown'); - } - - // Turn off error reporting - error_reporting(0); - exit; - } - catch (Exception $e) - { - if (IN_PRODUCTION) - { - die('Fatal Error'); - } - else - { - die('Fatal Error: '.$e->getMessage().' File: '.$e->getFile().' Line: '.$e->getLine()); - } } + + echo $output; } /** @@ -1006,7 +621,7 @@ final class Kohana { */ public static function auto_load($class) { - if (class_exists($class, FALSE)) + if (class_exists($class, FALSE) OR interface_exists($class, FALSE)) return TRUE; if (($suffix = strrpos($class, '_')) > 0) @@ -1051,7 +666,7 @@ final class Kohana { $file = $class; } - if ($filename = self::find_file($type, $file)) + if ($filename = Kohana::find_file($type, $file)) { // Load the class require $filename; @@ -1062,7 +677,7 @@ final class Kohana { return FALSE; } - if ($filename = self::find_file($type, self::$configuration['core']['extension_prefix'].$class)) + if ($filename = Kohana::find_file($type, Kohana::config('core.extension_prefix').$class)) { // Load the class extension require $filename; @@ -1121,16 +736,16 @@ final class Kohana { // Search path $search = $directory.'/'.$filename.$ext; - if (isset(self::$internal_cache['find_file_paths'][$search])) - return self::$internal_cache['find_file_paths'][$search]; + if (isset(Kohana::$internal_cache['find_file_paths'][$search])) + return Kohana::$internal_cache['find_file_paths'][$search]; // Load include paths - $paths = self::$include_paths; + $paths = Kohana::$include_paths; // Nothing found, yet $found = NULL; - if ($directory === 'config' OR $directory === 'i18n') + if ($directory === 'config' OR $directory === 'messages' OR $directory === 'i18n') { // Search in reverse, for merging $paths = array_reverse($paths); @@ -1167,7 +782,7 @@ final class Kohana { $directory = 'core.'.inflector::singular($directory); // If the file is required, throw an exception - throw new Kohana_Exception('core.resource_not_found', self::lang($directory), $filename); + throw new Kohana_Exception('The requested :resource:, :file:, could not be found', array(':resource:' => Kohana::message($directory), ':file:' =>$filename)); } else { @@ -1176,13 +791,13 @@ final class Kohana { } } - if ( ! isset(self::$write_cache['find_file_paths'])) + if ( ! isset(Kohana::$write_cache['find_file_paths'])) { // Write cache at shutdown - self::$write_cache['find_file_paths'] = TRUE; + Kohana::$write_cache['find_file_paths'] = TRUE; } - return self::$internal_cache['find_file_paths'][$search] = $found; + return Kohana::$internal_cache['find_file_paths'][$search] = $found; } /** @@ -1190,45 +805,54 @@ final class Kohana { * * @param string directory to search * @param boolean list all files to the maximum depth? + * @param string list all files having extension $ext * @param string full path to search (used for recursion, *never* set this manually) * @return array filenames and directories */ - public static function list_files($directory, $recursive = FALSE, $path = FALSE) + public static function list_files($directory, $recursive = FALSE, $ext = EXT, $path = FALSE) { $files = array(); if ($path === FALSE) { - $paths = array_reverse(self::include_paths()); + $paths = array_reverse(Kohana::include_paths()); foreach ($paths as $path) { // Recursively get and merge all files - $files = array_merge($files, self::list_files($directory, $recursive, $path.$directory)); + $files = array_merge($files, Kohana::list_files($directory, $recursive, $ext, $path.$directory)); } } else { $path = rtrim($path, '/').'/'; - if (is_readable($path)) + if (is_readable($path) AND $items = glob($path.'*')) { - $items = (array) glob($path.'*'); + $ext_pos = 0 - strlen($ext); - if ( ! empty($items)) + foreach ($items as $index => $item) { - foreach ($items as $index => $item) - { - $files[] = $item = str_replace('\\', '/', $item); + $item = str_replace('\\', '/', $item); + if (is_dir($item)) + { // Handle recursion - if (is_dir($item) AND $recursive == TRUE) + if ($recursive === TRUE) { // Filename should only be the basename $item = pathinfo($item, PATHINFO_BASENAME); // Append sub-directory search - $files = array_merge($files, self::list_files($directory, TRUE, $path.$item)); + $files = array_merge($files, Kohana::list_files($directory, TRUE, $ext, $path.$item)); + } + } + else + { + // File extension must match + if ($ext_pos === 0 OR substr($item, $ext_pos) === $ext) + { + $files[] = $item; } } } @@ -1238,59 +862,48 @@ final class Kohana { return $files; } + /** - * Fetch an i18n language item. + * Fetch a message item. * * @param string language key to fetch * @param array additional information to insert into the line * @return string i18n language string, or the requested key if the i18n item is not found */ - public static function lang($key, $args = array()) + public static function message($key, $args = array()) { // Extract the main group from the key $group = explode('.', $key, 2); $group = $group[0]; // Get locale name - $locale = self::config('locale.language.0'); + $locale = Kohana::config('locale.language.0'); - if ( ! isset(self::$internal_cache['language'][$locale][$group])) + if ( ! isset(Kohana::$internal_cache['messages'][$group])) { // Messages for this group $messages = array(); - if ($files = self::find_file('i18n', $locale.'/'.$group)) + if ($file = Kohana::find_file('messages', $group)) { - foreach ($files as $file) - { - include $file; - - // Merge in configuration - if ( ! empty($lang) AND is_array($lang)) - { - foreach ($lang as $k => $v) - { - $messages[$k] = $v; - } - } - } + include $file[0]; } - if ( ! isset(self::$write_cache['language'])) + if ( ! isset(Kohana::$write_cache['messages'])) { // Write language cache - self::$write_cache['language'] = TRUE; + Kohana::$write_cache['messages'] = TRUE; } - self::$internal_cache['language'][$locale][$group] = $messages; + Kohana::$internal_cache['messages'][$group] = $messages; } // Get the line from cache - $line = self::key_string(self::$internal_cache['language'][$locale], $key); + $line = Kohana::key_string(Kohana::$internal_cache['messages'], $key); if ($line === NULL) { - self::log('error', 'Missing i18n entry '.$key.' for language '.$locale); + Kohana_Log::add('error', 'Missing messages entry '.$key.' for message '.$group); // Return the key string as fallback return $key; @@ -1427,121 +1040,6 @@ final class Kohana { } /** - * Retrieves current user agent information: - * keys: browser, version, platform, mobile, robot, referrer, languages, charsets - * tests: is_browser, is_mobile, is_robot, accept_lang, accept_charset - * - * @param string key or test name - * @param string used with "accept" tests: user_agent(accept_lang, en) - * @return array languages and charsets - * @return string all other keys - * @return boolean all tests - */ - public static function user_agent($key = 'agent', $compare = NULL) - { - static $info; - - // Return the raw string - if ($key === 'agent') - return self::$user_agent; - - if ($info === NULL) - { - // Parse the user agent and extract basic information - $agents = self::config('user_agents'); - - foreach ($agents as $type => $data) - { - foreach ($data as $agent => $name) - { - if (stripos(self::$user_agent, $agent) !== FALSE) - { - if ($type === 'browser' AND preg_match('|'.preg_quote($agent).'[^0-9.]*+([0-9.][0-9.a-z]*)|i', self::$user_agent, $match)) - { - // Set the browser version - $info['version'] = $match[1]; - } - - // Set the agent name - $info[$type] = $name; - break; - } - } - } - } - - if (empty($info[$key])) - { - switch ($key) - { - case 'is_robot': - case 'is_browser': - case 'is_mobile': - // A boolean result - $return = ! empty($info[substr($key, 3)]); - break; - case 'languages': - $return = array(); - if ( ! empty($_SERVER['HTTP_ACCEPT_LANGUAGE'])) - { - if (preg_match_all('/[-a-z]{2,}/', strtolower(trim($_SERVER['HTTP_ACCEPT_LANGUAGE'])), $matches)) - { - // Found a result - $return = $matches[0]; - } - } - break; - case 'charsets': - $return = array(); - if ( ! empty($_SERVER['HTTP_ACCEPT_CHARSET'])) - { - if (preg_match_all('/[-a-z0-9]{2,}/', strtolower(trim($_SERVER['HTTP_ACCEPT_CHARSET'])), $matches)) - { - // Found a result - $return = $matches[0]; - } - } - break; - case 'referrer': - if ( ! empty($_SERVER['HTTP_REFERER'])) - { - // Found a result - $return = trim($_SERVER['HTTP_REFERER']); - } - break; - } - - // Cache the return value - isset($return) and $info[$key] = $return; - } - - if ( ! empty($compare)) - { - // The comparison must always be lowercase - $compare = strtolower($compare); - - switch ($key) - { - case 'accept_lang': - // Check if the lange is accepted - return in_array($compare, self::user_agent('languages')); - break; - case 'accept_charset': - // Check if the charset is accepted - return in_array($compare, self::user_agent('charsets')); - break; - default: - // Invalid comparison - return FALSE; - break; - } - } - - // Return the key, if set - return isset($info[$key]) ? $info[$key] : NULL; - } - - /** * Quick debugging of any variable. Any number of parameters can be set. * * @return string @@ -1557,98 +1055,35 @@ final class Kohana { foreach ($params as $var) { - $output[] = '<pre>('.gettype($var).') '.html::specialchars(print_r($var, TRUE)).'</pre>'; + $value = is_bool($var) ? ($var ? 'true' : 'false') : print_r($var, TRUE); + $output[] = '<pre>('.gettype($var).') '.htmlspecialchars($value, ENT_QUOTES, Kohana::CHARSET).'</pre>'; } return implode("\n", $output); } /** - * Displays nice backtrace information. - * @see http://php.net/debug_backtrace - * - * @param array backtrace generated by an exception or debug_backtrace - * @return string - */ - public static function backtrace($trace) - { - if ( ! is_array($trace)) - return; - - // Final output - $output = array(); - - foreach ($trace as $entry) - { - $temp = '<li>'; - - if (isset($entry['file'])) - { - $temp .= self::lang('core.error_file_line', preg_replace('!^'.preg_quote(DOCROOT).'!', '', $entry['file']), $entry['line']); - } - - $temp .= '<pre>'; - - if (isset($entry['class'])) - { - // Add class and call type - $temp .= $entry['class'].$entry['type']; - } - - // Add function - $temp .= $entry['function'].'( '; - - // Add function args - if (isset($entry['args']) AND is_array($entry['args'])) - { - // Separator starts as nothing - $sep = ''; - - while ($arg = array_shift($entry['args'])) - { - if (is_string($arg) AND is_file($arg)) - { - // Remove docroot from filename - $arg = preg_replace('!^'.preg_quote(DOCROOT).'!', '', $arg); - } - - $temp .= $sep.html::specialchars(print_r($arg, TRUE)); - - // Change separator to a comma - $sep = ', '; - } - } - - $temp .= ' )</pre></li>'; - - $output[] = $temp; - } - - return '<ul class="backtrace">'.implode("\n", $output).'</ul>'; - } - - /** * Saves the internal caches: configuration, include paths, etc. * * @return boolean */ public static function internal_cache_save() { - if ( ! is_array(self::$write_cache)) + if ( ! is_array(Kohana::$write_cache)) return FALSE; // Get internal cache names - $caches = array_keys(self::$write_cache); + $caches = array_keys(Kohana::$write_cache); // Nothing written $written = FALSE; foreach ($caches as $cache) { - if (isset(self::$internal_cache[$cache])) + if (isset(Kohana::$internal_cache[$cache])) { // Write the cache file - self::cache_save($cache, self::$internal_cache[$cache], self::$configuration['core']['internal_cache']); + Kohana::cache_save($cache, Kohana::$internal_cache[$cache], Kohana::config('core.internal_cache')); // A cache has been written $written = TRUE; @@ -1659,138 +1094,3 @@ final class Kohana { } } // End Kohana - -/** - * Creates a generic i18n exception. - */ -class Kohana_Exception extends Exception { - - // Template file - protected $template = 'kohana_error_page'; - - // Header - protected $header = FALSE; - - // Error code - protected $code = E_KOHANA; - - /** - * Set exception message. - * - * @param string i18n language key for the message - * @param array addition line parameters - */ - public function __construct($error) - { - $args = array_slice(func_get_args(), 1); - - // Fetch the error message - $message = Kohana::lang($error, $args); - - if ($message === $error OR empty($message)) - { - // Unable to locate the message for the error - $message = 'Unknown Exception: '.$error; - } - - // Sets $this->message the proper way - parent::__construct($message); - } - - /** - * Magic method for converting an object to a string. - * - * @return string i18n message - */ - public function __toString() - { - return (string) $this->message; - } - - /** - * Fetch the template name. - * - * @return string - */ - public function getTemplate() - { - return $this->template; - } - - /** - * Sends an Internal Server Error header. - * - * @return void - */ - public function sendHeaders() - { - // Send the 500 header - header('HTTP/1.1 500 Internal Server Error'); - } - -} // End Kohana Exception - -/** - * Creates a custom exception. - */ -class Kohana_User_Exception extends Kohana_Exception { - - /** - * Set exception title and message. - * - * @param string exception title string - * @param string exception message string - * @param string custom error template - */ - public function __construct($title, $message, $template = FALSE) - { - Exception::__construct($message); - - $this->code = $title; - - if ($template !== FALSE) - { - $this->template = $template; - } - } - -} // End Kohana PHP Exception - -/** - * Creates a Page Not Found exception. - */ -class Kohana_404_Exception extends Kohana_Exception { - - protected $code = E_PAGE_NOT_FOUND; - - /** - * Set internal properties. - * - * @param string URL of page - * @param string custom error template - */ - public function __construct($page = FALSE, $template = FALSE) - { - if ($page === FALSE) - { - // Construct the page URI using Router properties - $page = Router::$current_uri.Router::$url_suffix.Router::$query_string; - } - - Exception::__construct(Kohana::lang('core.page_not_found', $page)); - - $this->template = $template; - } - - /** - * Sends "File Not Found" 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 diff --git a/system/core/Kohana_Config.php b/system/core/Kohana_Config.php new file mode 100644 index 00000000..f961f391 --- /dev/null +++ b/system/core/Kohana_Config.php @@ -0,0 +1,331 @@ +<?php defined('SYSPATH') or die('No direct script access.'); +/** + * Provides a driver-based interface for setting and getting + * configuration options for the Kohana environment + * + * $Id: Kohana_Config.php 4679 2009-11-10 01:45:52Z isaiah $ + * + * @package KohanaConfig + * @author Kohana Team + * @copyright (c) 2007-2009 Kohana Team + * @license http://kohanaphp.com/license + */ +class Kohana_Config_Core implements ArrayAccess { + + /** + * The default Kohana_Config driver + * to use for system setup + * + * @var string + * @static + */ + public static $default_driver = 'array'; + + /** + * Kohana_Config instance + * + * @var array + * @static + */ + protected static $instance; + + /** + * Returns a new instance of the Kohana_Config library + * based on the singleton pattern + * + * @param string driver + * @return Kohana_Config + * @access public + * @static + */ + public static function & instance() + { + // If the driver has not been initialised, intialise it + if ( empty(Kohana_Config::$instance)) + { + //call a 1 time non singleton of Kohana_Config to get a list of drivers + $config = new Kohana_Config(array( + 'config_drivers'=>array( + ), 'internal_cache'=>FALSE + )); + $core_config = $config->get('core'); + Kohana_Config::$instance = new Kohana_Config($core_config); + } + + // Return the Kohana_Config driver requested + return Kohana_Config::$instance; + } + + /** + * The drivers for this object + * + * @var Kohana_Config_Driver + */ + protected $drivers; + + /** + * Kohana_Config constructor to load the supplied driver. + * Enforces the singleton pattern. + * + * @param string driver + * @access protected + */ + protected function __construct(array $core_config) + { + $drivers = $core_config['config_drivers']; + + //remove array if it's found in config + if (in_array('array', $drivers)) + unset($drivers[array_search('array', $drivers)]); + + //add array at the very end + $this->drivers = $drivers = array_merge($drivers, array( + 'array' + )); + + foreach ($this->drivers as & $driver) + { + // Create the driver name + $driver = 'Config_'.ucfirst($driver).'_Driver'; + + // Ensure the driver loads correctly + if (!Kohana::auto_load($driver)) + throw new Kohana_Exception('The :driver: driver for the :library: library could not be found.', array( + ':driver:' => $driver, ':library:' => get_class($this) + )); + + // Load the new driver + $driver = new $driver($core_config); + + // Ensure the new driver is valid + if (!$driver instanceof Config_Driver) + throw new Kohana_Exception('The :driver: driver for the :library: library must implement the :interface: interface', array( + ':driver:' => $driver, ':library:' => get_class($this), ':interface:' => 'Config_Driver' + )); + } + } + + /** + * Gets a value from the configuration driver + * + * @param string key + * @param bool slash + * @param bool required + * @return mixed + * @access public + */ + public function get($key, $slash = FALSE, $required = FALSE) + { + foreach ($this->drivers as $driver) + { + try + { + return $driver->get($key, $slash, $required); + } + catch (Kohana_Config_Exception $e) + { + //if it's the last driver in the list and it threw an exception, re throw it + if ($driver === $this->drivers[(count($this->drivers) - 1)]) + throw $e; + } + } + } + + /** + * Sets a value to the configuration drivers + * + * @param string key + * @param mixed value + * @return bool + * @access public + */ + public function set($key, $value) + { + foreach ($this->drivers as $driver) + { + try + { + $driver->set($key, $value); + } + catch (Kohana_Config_Exception $e) + { + //if it's the last driver in the list and it threw an exception, re throw it + if ($driver === $this->drivers[(count($this->drivers) - 1)]) + throw $e; + } + } + return TRUE; + } + + /** + * Clears a group from configuration + * + * @param string group + * @return bool + * @access public + */ + public function clear($group) + { + foreach ($this->drivers as $driver) + { + try + { + $driver->clear($group); + } + catch (Kohana_Config_Exception $e) + { + //if it's the last driver in the list and it threw an exception, re throw it + if ($driver === $this->drivers[(count($this->drivers) - 1)]) + throw $e; + } + } + return TRUE; + } + + /** + * Loads a configuration group + * + * @param string group + * @param bool required + * @return array + * @access public + */ + public function load($group, $required = FALSE) + { + foreach ($this->drivers as $driver) + { + try + { + return $driver->load($group, $required); + } + catch (Kohana_Config_Exception $e) + { + //if it's the last driver in the list and it threw an exception, re throw it + if ($driver === $this->drivers[(count($this->drivers) - 1)]) + throw $e; + } + } + } + + /** + * Returns true or false if any config has been loaded(either manually or from cache) + * + * @return boolean + */ + public function loaded() + { + return $this->drivers[(count($this->drivers) - 1)]->loaded; + } + + /** + * The following allows access using + * array syntax. + * + * @example $config['core.site_domain'] + */ + + /** + * Allows access to configuration settings + * using the ArrayAccess interface + * + * @param string key + * @return mixed + * @access public + */ + public function offsetGet($key) + { + foreach ($this->drivers as $driver) + { + try + { + return $driver->get($key); + } + catch (Kohana_Config_Exception $e) + { + //if it's the last driver in the list and it threw an exception, re throw it + if ($driver === $this->drivers[(count($this->drivers) - 1)]) + throw $e; + } + } + } + + /** + * Allows access to configuration settings + * using the ArrayAccess interface + * + * @param string key + * @param mixed value + * @return bool + * @access public + */ + public function offsetSet($key, $value) + { + foreach ($this->drivers as $driver) + { + try + { + $driver->set($key, $value); + } + catch (Kohana_Config_Exception $e) + { + //if it's the last driver in the list and it threw an exception, re throw it + if ($driver === $this->drivers[(count($this->drivers) - 1)]) + throw $e; + } + } + return TRUE; + } + + /** + * Allows access to configuration settings + * using the ArrayAccess interface + * + * @param string key + * @return bool + * @access public + */ + public function offsetExists($key) + { + foreach ($this->drivers as $driver) + { + try + { + return $driver->setting_exists($key); + } + catch (Kohana_Config_Exception $e) + { + //if it's the last driver in the list and it threw an exception, re throw it + if ($driver === $this->drivers[(count($this->drivers) - 1)]) + throw $e; + } + } + } + + /** + * Allows access to configuration settings + * using the ArrayAccess interface + * + * @param string key + * @return bool + * @access public + */ + public function offsetUnset($key) + { + foreach ($this->drivers as $driver) + { + try + { + return $driver->set($key, NULL); + } + catch (Kohana_Config_Exception $e) + { + //if it's the last driver in the list and it threw an exception, re throw it + if ($driver === $this->drivers[(count($this->drivers) - 1)]) + throw $e; + } + } + return TRUE; + } +} // End KohanaConfig + +class Kohana_Config_Exception extends Kohana_Exception {} diff --git a/system/core/Kohana_Exception.php b/system/core/Kohana_Exception.php new file mode 100644 index 00000000..cdb06a62 --- /dev/null +++ b/system/core/Kohana_Exception.php @@ -0,0 +1,619 @@ +<?php defined('SYSPATH') OR die('No direct access allowed.'); +/** + * Kohana Exceptions + * + * $Id: Kohana_Exception.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 + */ + +class Kohana_Exception_Core extends Exception { + + public static $enabled = FALSE; + + // Template file + public static $template = 'kohana/error'; + + // Show stack traces in errors + public static $trace_output = TRUE; + + // Show source code in errors + public static $source_output = TRUE; + + // To hold unique identifier to distinguish error output + protected $instance_identifier; + + // Error code + protected $code = E_KOHANA; + + /** + * Creates a new translated exception. + * + * @param string error message + * @param array translation variables + * @return void + */ + public function __construct($message, array $variables = NULL, $code = 0) + { + $this->instance_identifier = uniqid(); + + // Translate the error message + $message = __($message, $variables); + + // Sets $this->message the proper way + parent::__construct($message, $code); + } + + /** + * Enable Kohana exception handling. + * + * @uses Kohana_Exception::$template + * @return void + */ + public static function enable() + { + if ( ! Kohana_Exception::$enabled) + { + set_exception_handler(array('Kohana_Exception', 'handle')); + + Kohana_Exception::$enabled = TRUE; + } + } + + /** + * Disable Kohana exception handling. + * + * @return void + */ + public static function disable() + { + if (Kohana_Exception::$enabled) + { + restore_exception_handler(); + + Kohana_Exception::$enabled = FALSE; + } + } + + /** + * Get a single line of text representing the exception: + * + * Error [ Code ]: Message ~ File [ Line ] + * + * @param object Exception + * @return string + */ + public static function text($e) + { + return sprintf('%s [ %s ]: %s ~ %s [ %d ]', + get_class($e), $e->getCode(), strip_tags($e->getMessage()), Kohana_Exception::debug_path($e->getFile()), $e->getLine()); + } + + /** + * exception handler, displays the error message, source of the + * exception, and the stack trace of the error. + * + * @uses Kohana::message() + * @uses Kohana_Exception::text() + * @param object exception object + * @return void + */ + public static function handle(Exception $e) + { + try + { + // Get the exception information + $type = get_class($e); + $code = $e->getCode(); + $message = $e->getMessage(); + + // Create a text version of the exception + $error = Kohana_Exception::text($e); + + // Add this exception to the log + Kohana_Log::add('error', $error); + + // Manually save logs after exceptions + Kohana_Log::save(); + + if (Kohana::config('core.display_errors') === FALSE) + { + // Do not show the details + $file = $line = NULL; + $trace = array(); + + $template = '_disabled'; + } + else + { + $file = $e->getFile(); + $line = $e->getLine(); + $trace = $e->getTrace(); + + $template = ''; + } + + if ($e instanceof Kohana_Exception) + { + $template = $e->getTemplate().$template; + + if ( ! headers_sent()) + { + $e->sendHeaders(); + } + + // Use the human-readable error name + $code = Kohana::message('core.errors.'.$code); + } + else + { + $template = Kohana_Exception::$template.$template; + + if ( ! headers_sent()) + { + header('HTTP/1.1 500 Internal Server Error'); + } + + if ($e instanceof ErrorException) + { + // Use the human-readable error name + $code = Kohana::message('core.errors.'.$e->getSeverity()); + + if (version_compare(PHP_VERSION, '5.3', '<')) + { + // Workaround for a bug in ErrorException::getTrace() that exists in + // all PHP 5.2 versions. @see http://bugs.php.net/45895 + for ($i = count($trace) - 1; $i > 0; --$i) + { + if (isset($trace[$i - 1]['args'])) + { + // Re-position the arguments + $trace[$i]['args'] = $trace[$i - 1]['args']; + + unset($trace[$i - 1]['args']); + } + } + } + } + } + + // Clean the output buffer if one exists + ob_get_level() and ob_clean(); + + if ($template = Kohana::find_file('views', $template)) + { + include $template; + } + } + catch (Exception $e) + { + // Clean the output buffer if one exists + ob_get_level() and ob_clean(); + + // Display the exception text + echo Kohana_Exception::text($e), "\n"; + } + + if (PHP_SAPI === 'cli') + { + // Exit with an error status + exit(1); + } + } + + /** + * Returns the template for this exception. + * + * @uses Kohana_Exception::$template + * @return string + */ + public function getTemplate() + { + return Kohana_Exception::$template; + } + + /** + * Sends an Internal Server Error header. + * + * @return void + */ + public function sendHeaders() + { + // Send the 500 header + header('HTTP/1.1 500 Internal Server Error'); + } + + /** + * Returns an HTML string of information about a single variable. + * + * Borrows heavily on concepts from the Debug class of {@link http://nettephp.com/ Nette}. + * + * @param mixed variable to dump + * @param integer maximum length of strings + * @return string + */ + public static function dump($value, $length = 128) + { + return Kohana_Exception::_dump($value, $length); + } + + /** + * Helper for Kohana_Exception::dump(), handles recursion in arrays and objects. + * + * @param mixed variable to dump + * @param integer maximum length of strings + * @param integer recursion level (internal) + * @return string + */ + private static function _dump( & $var, $length = 128, $level = 0) + { + if ($var === NULL) + { + return '<small>NULL</small>'; + } + elseif (is_bool($var)) + { + return '<small>bool</small> '.($var ? 'TRUE' : 'FALSE'); + } + elseif (is_float($var)) + { + return '<small>float</small> '.$var; + } + elseif (is_resource($var)) + { + if (($type = get_resource_type($var)) === 'stream' AND $meta = stream_get_meta_data($var)) + { + $meta = stream_get_meta_data($var); + + if (isset($meta['uri'])) + { + $file = $meta['uri']; + + if (function_exists('stream_is_local')) + { + // Only exists on PHP >= 5.2.4 + if (stream_is_local($file)) + { + $file = Kohana_Exception::debug_path($file); + } + } + + return '<small>resource</small><span>('.$type.')</span> '.htmlspecialchars($file, ENT_NOQUOTES, Kohana::CHARSET); + } + } + else + { + return '<small>resource</small><span>('.$type.')</span>'; + } + } + elseif (is_string($var)) + { + if (strlen($var) > $length) + { + // Encode the truncated string + $str = htmlspecialchars(substr($var, 0, $length), ENT_NOQUOTES, Kohana::CHARSET).' …'; + } + else + { + // Encode the string + $str = htmlspecialchars($var, ENT_NOQUOTES, Kohana::CHARSET); + } + + return '<small>string</small><span>('.strlen($var).')</span> "'.$str.'"'; + } + elseif (is_array($var)) + { + $output = array(); + + // Indentation for this variable + $space = str_repeat($s = ' ', $level); + + static $marker; + + if ($marker === NULL) + { + // Make a unique marker + $marker = uniqid("\x00"); + } + + if (empty($var)) + { + // Do nothing + } + elseif (isset($var[$marker])) + { + $output[] = "(\n$space$s*RECURSION*\n$space)"; + } + elseif ($level < 5) + { + $output[] = "<span>("; + + $var[$marker] = TRUE; + foreach ($var as $key => & $val) + { + if ($key === $marker) continue; + if ( ! is_int($key)) + { + $key = '"'.$key.'"'; + } + + $output[] = "$space$s$key => ".Kohana_Exception::_dump($val, $length, $level + 1); + } + unset($var[$marker]); + + $output[] = "$space)</span>"; + } + else + { + // Depth too great + $output[] = "(\n$space$s...\n$space)"; + } + + return '<small>array</small><span>('.count($var).')</span> '.implode("\n", $output); + } + elseif (is_object($var)) + { + // Copy the object as an array + $array = (array) $var; + + $output = array(); + + // Indentation for this variable + $space = str_repeat($s = ' ', $level); + + $hash = spl_object_hash($var); + + // Objects that are being dumped + static $objects = array(); + + if (empty($var)) + { + // Do nothing + } + elseif (isset($objects[$hash])) + { + $output[] = "{\n$space$s*RECURSION*\n$space}"; + } + elseif ($level < 5) + { + $output[] = "<code>{"; + + $objects[$hash] = TRUE; + foreach ($array as $key => & $val) + { + if ($key[0] === "\x00") + { + // Determine if the access is private or protected + $access = '<small>'.($key[1] === '*' ? 'protected' : 'private').'</small>'; + + // Remove the access level from the variable name + $key = substr($key, strrpos($key, "\x00") + 1); + } + else + { + $access = '<small>public</small>'; + } + + $output[] = "$space$s$access $key => ".Kohana_Exception::_dump($val, $length, $level + 1); + } + unset($objects[$hash]); + + $output[] = "$space}</code>"; + } + else + { + // Depth too great + $output[] = "{\n$space$s...\n$space}"; + } + + return '<small>object</small> <span>'.get_class($var).'('.count($array).')</span> '.implode("\n", $output); + } + else + { + return '<small>'.gettype($var).'</small> '.htmlspecialchars(print_r($var, TRUE), ENT_NOQUOTES, Kohana::CHARSET); + } + } + + /** + * Removes APPPATH, SYSPATH, MODPATH, and DOCROOT from filenames, replacing + * them with the plain text equivalents. + * + * @param string path to sanitize + * @return string + */ + public static function debug_path($file) + { + if (strpos($file, APPPATH) === 0) + { + $file = 'APPPATH/'.substr($file, strlen(APPPATH)); + } + elseif (strpos($file, SYSPATH) === 0) + { + $file = 'SYSPATH/'.substr($file, strlen(SYSPATH)); + } + elseif (strpos($file, MODPATH) === 0) + { + $file = 'MODPATH/'.substr($file, strlen(MODPATH)); + } + elseif (strpos($file, DOCROOT) === 0) + { + $file = 'DOCROOT/'.substr($file, strlen(DOCROOT)); + } + + return $file; + } + + /** + * Returns an array of lines from a file. + * + * // Returns the current line of the current file + * echo Kohana_Exception::debug_source(__FILE__, __LINE__); + * + * @param string file to open + * @param integer line number to find + * @param integer number of padding lines + * @return array + */ + public static function debug_source($file, $line_number, $padding = 5) + { + // Make sure we can read the source file + if ( ! is_readable($file)) + return array(); + + // Open the file and set the line position + $file = fopen($file, 'r'); + $line = 0; + + // Set the reading range + $range = array('start' => $line_number - $padding, 'end' => $line_number + $padding); + + // Set the zero-padding amount for line numbers + $format = '% '.strlen($range['end']).'d'; + + $source = array(); + while (($row = fgets($file)) !== FALSE) + { + // Increment the line number + if (++$line > $range['end']) + break; + + if ($line >= $range['start']) + { + $source[sprintf($format, $line)] = $row; + } + } + + // Close the file + fclose($file); + + return $source; + } + + /** + * Returns an array of strings that represent each step in the backtrace. + * + * @param array trace to analyze + * @return array + */ + public static function trace($trace = NULL) + { + if ($trace === NULL) + { + // Start a new trace + $trace = debug_backtrace(); + } + + // Non-standard function calls + $statements = array('include', 'include_once', 'require', 'require_once'); + + $output = array(); + foreach ($trace as $step) + { + if ( ! isset($step['function'])) + { + // Invalid trace step + continue; + } + + if (isset($step['file']) AND isset($step['line'])) + { + // Include the source of this step + $source = Kohana_Exception::debug_source($step['file'], $step['line']); + } + + if (isset($step['file'])) + { + $file = $step['file']; + + if (isset($step['line'])) + { + $line = $step['line']; + } + } + + // function() + $function = $step['function']; + + if (in_array($step['function'], $statements)) + { + if (empty($step['args'])) + { + // No arguments + $args = array(); + } + else + { + // Sanitize the file path + $args = array($step['args'][0]); + } + } + elseif (isset($step['args'])) + { + if ($step['function'] === '{closure}') + { + // Introspection on closures in a stack trace is impossible + $params = NULL; + } + else + { + if (isset($step['class'])) + { + if (method_exists($step['class'], $step['function'])) + { + $reflection = new ReflectionMethod($step['class'], $step['function']); + } + else + { + $reflection = new ReflectionMethod($step['class'], '__call'); + } + } + else + { + $reflection = new ReflectionFunction($step['function']); + } + + // Get the function parameters + $params = $reflection->getParameters(); + } + + $args = array(); + + foreach ($step['args'] as $i => $arg) + { + if (isset($params[$i])) + { + // Assign the argument by the parameter name + $args[$params[$i]->name] = $arg; + } + else + { + // Assign the argument by number + $args[$i] = $arg; + } + } + } + + if (isset($step['class'])) + { + // Class->method() or Class::method() + $function = $step['class'].$step['type'].$step['function']; + } + + $output[] = array( + 'function' => $function, + 'args' => isset($args) ? $args : NULL, + 'file' => isset($file) ? $file : NULL, + 'line' => isset($line) ? $line : NULL, + 'source' => isset($source) ? $source : NULL, + ); + + unset($function, $args, $file, $line, $source); + } + + return $output; + } + +} // End Kohana Exception diff --git a/system/core/utf8.php b/system/core/utf8.php deleted file mode 100644 index 9f20f421..00000000 --- a/system/core/utf8.php +++ /dev/null @@ -1,743 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * A port of phputf8 to a unified file/class. Checks PHP status to ensure that - * UTF-8 support is available and normalize global variables to UTF-8. It also - * provides multi-byte aware replacement string functions. - * - * This file is licensed differently from the rest of Kohana. As a port of - * phputf8, which is LGPL software, this file is released under the LGPL. - * - * PCRE needs to be compiled with UTF-8 support (--enable-utf8). - * Support for Unicode properties is highly recommended (--enable-unicode-properties). - * @see http://php.net/manual/reference.pcre.pattern.modifiers.php - * - * UTF-8 conversion will be much more reliable if the iconv extension is loaded. - * @see http://php.net/iconv - * - * The mbstring extension is highly recommended, but must not be overloading - * string functions. - * @see http://php.net/mbstring - * - * $Id: utf8.php 3769 2008-12-15 00:48:56Z zombor $ - * - * @package Core - * @author Kohana Team - * @copyright (c) 2007 Kohana Team - * @copyright (c) 2005 Harry Fuecks - * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt - */ - -if ( ! preg_match('/^.$/u', 'ñ')) -{ - trigger_error - ( - '<a href="http://php.net/pcre">PCRE</a> has not been compiled with UTF-8 support. '. - 'See <a href="http://php.net/manual/reference.pcre.pattern.modifiers.php">PCRE Pattern Modifiers</a> '. - 'for more information. This application cannot be run without UTF-8 support.', - E_USER_ERROR - ); -} - -if ( ! extension_loaded('iconv')) -{ - trigger_error - ( - 'The <a href="http://php.net/iconv">iconv</a> extension is not loaded. '. - 'Without iconv, strings cannot be properly translated to UTF-8 from user input. '. - 'This application cannot be run without UTF-8 support.', - E_USER_ERROR - ); -} - -if (extension_loaded('mbstring') AND (ini_get('mbstring.func_overload') & MB_OVERLOAD_STRING)) -{ - trigger_error - ( - 'The <a href="http://php.net/mbstring">mbstring</a> extension is overloading PHP\'s native string functions. '. - 'Disable this by setting mbstring.func_overload to 0, 1, 4 or 5 in php.ini or a .htaccess file.'. - 'This application cannot be run without UTF-8 support.', - E_USER_ERROR - ); -} - -// Check PCRE support for Unicode properties such as \p and \X. -$ER = error_reporting(0); -define('PCRE_UNICODE_PROPERTIES', (bool) preg_match('/^\pL$/u', 'ñ')); -error_reporting($ER); - -// SERVER_UTF8 ? use mb_* functions : use non-native functions -if (extension_loaded('mbstring')) -{ - mb_internal_encoding('UTF-8'); - define('SERVER_UTF8', TRUE); -} -else -{ - define('SERVER_UTF8', FALSE); -} - -// Convert all global variables to UTF-8. -$_GET = utf8::clean($_GET); -$_POST = utf8::clean($_POST); -$_COOKIE = utf8::clean($_COOKIE); -$_SERVER = utf8::clean($_SERVER); - -if (PHP_SAPI == 'cli') -{ - // Convert command line arguments - $_SERVER['argv'] = utf8::clean($_SERVER['argv']); -} - -final class utf8 { - - // Called methods - static $called = array(); - - /** - * 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[self::clean($key)] = self::clean($val); - } - } - elseif (is_string($str) AND $str !== '') - { - // Remove control characters - $str = self::strip_ascii_ctrl($str); - - if ( ! self::is_ascii($str)) - { - // Disable notices - $ER = error_reporting(~E_NOTICE); - - // iconv is expensive, so it is only used when needed - $str = iconv('UTF-8', 'UTF-8//IGNORE', $str); - - // Turn notices back on - error_reporting($ER); - } - } - - return $str; - } - - /** - * Tests whether a string contains only 7bit ASCII bytes. This is used to - * determine when to use native functions or UTF-8 functions. - * - * @param string string to check - * @return bool - */ - public static function is_ascii($str) - { - return ! preg_match('/[^\x00-\x7F]/S', $str); - } - - /** - * Strips out device control codes in the ASCII range. - * - * @param string string to clean - * @return string - */ - public static function strip_ascii_ctrl($str) - { - return preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S', '', $str); - } - - /** - * Strips out all non-7bit ASCII bytes. - * - * @param string string to clean - * @return string - */ - public static function strip_non_ascii($str) - { - return preg_replace('/[^\x00-\x7F]+/S', '', $str); - } - - /** - * Replaces special/accented UTF-8 characters by ASCII-7 'equivalents'. - * - * @author Andreas Gohr <andi@splitbrain.org> - * - * @param string string to transliterate - * @param integer -1 lowercase only, +1 uppercase only, 0 both cases - * @return string - */ - public static function transliterate_to_ascii($str, $case = 0) - { - if ( ! isset(self::$called[__FUNCTION__])) - { - require SYSPATH.'core/utf8/'.__FUNCTION__.EXT; - - // Function has been called - self::$called[__FUNCTION__] = TRUE; - } - - return _transliterate_to_ascii($str, $case); - } - - /** - * Returns the length of the given string. - * @see http://php.net/strlen - * - * @param string string being measured for length - * @return integer - */ - public static function strlen($str) - { - if ( ! isset(self::$called[__FUNCTION__])) - { - require SYSPATH.'core/utf8/'.__FUNCTION__.EXT; - - // Function has been called - self::$called[__FUNCTION__] = TRUE; - } - - return _strlen($str); - } - - /** - * Finds position of first occurrence of a UTF-8 string. - * @see http://php.net/strlen - * - * @author Harry Fuecks <hfuecks@gmail.com> - * - * @param string haystack - * @param string needle - * @param integer offset from which character in haystack to start searching - * @return integer position of needle - * @return boolean FALSE if the needle is not found - */ - public static function strpos($str, $search, $offset = 0) - { - if ( ! isset(self::$called[__FUNCTION__])) - { - require SYSPATH.'core/utf8/'.__FUNCTION__.EXT; - - // Function has been called - self::$called[__FUNCTION__] = TRUE; - } - - return _strpos($str, $search, $offset); - } - - /** - * Finds position of last occurrence of a char in a UTF-8 string. - * @see http://php.net/strrpos - * - * @author Harry Fuecks <hfuecks@gmail.com> - * - * @param string haystack - * @param string needle - * @param integer offset from which character in haystack to start searching - * @return integer position of needle - * @return boolean FALSE if the needle is not found - */ - public static function strrpos($str, $search, $offset = 0) - { - if ( ! isset(self::$called[__FUNCTION__])) - { - require SYSPATH.'core/utf8/'.__FUNCTION__.EXT; - - // Function has been called - self::$called[__FUNCTION__] = TRUE; - } - - return _strrpos($str, $search, $offset); - } - - /** - * Returns part of a UTF-8 string. - * @see http://php.net/substr - * - * @author Chris Smith <chris@jalakai.co.uk> - * - * @param string input string - * @param integer offset - * @param integer length limit - * @return string - */ - public static function substr($str, $offset, $length = NULL) - { - if ( ! isset(self::$called[__FUNCTION__])) - { - require SYSPATH.'core/utf8/'.__FUNCTION__.EXT; - - // Function has been called - self::$called[__FUNCTION__] = TRUE; - } - - return _substr($str, $offset, $length); - } - - /** - * Replaces text within a portion of a UTF-8 string. - * @see http://php.net/substr_replace - * - * @author Harry Fuecks <hfuecks@gmail.com> - * - * @param string input string - * @param string replacement string - * @param integer offset - * @return string - */ - public static function substr_replace($str, $replacement, $offset, $length = NULL) - { - if ( ! isset(self::$called[__FUNCTION__])) - { - require SYSPATH.'core/utf8/'.__FUNCTION__.EXT; - - // Function has been called - self::$called[__FUNCTION__] = TRUE; - } - - return _substr_replace($str, $replacement, $offset, $length); - } - - /** - * Makes a UTF-8 string lowercase. - * @see http://php.net/strtolower - * - * @author Andreas Gohr <andi@splitbrain.org> - * - * @param string mixed case string - * @return string - */ - public static function strtolower($str) - { - if ( ! isset(self::$called[__FUNCTION__])) - { - require SYSPATH.'core/utf8/'.__FUNCTION__.EXT; - - // Function has been called - self::$called[__FUNCTION__] = TRUE; - } - - return _strtolower($str); - } - - /** - * Makes a UTF-8 string uppercase. - * @see http://php.net/strtoupper - * - * @author Andreas Gohr <andi@splitbrain.org> - * - * @param string mixed case string - * @return string - */ - public static function strtoupper($str) - { - if ( ! isset(self::$called[__FUNCTION__])) - { - require SYSPATH.'core/utf8/'.__FUNCTION__.EXT; - - // Function has been called - self::$called[__FUNCTION__] = TRUE; - } - - return _strtoupper($str); - } - - /** - * Makes a UTF-8 string's first character uppercase. - * @see http://php.net/ucfirst - * - * @author Harry Fuecks <hfuecks@gmail.com> - * - * @param string mixed case string - * @return string - */ - public static function ucfirst($str) - { - if ( ! isset(self::$called[__FUNCTION__])) - { - require SYSPATH.'core/utf8/'.__FUNCTION__.EXT; - - // Function has been called - self::$called[__FUNCTION__] = TRUE; - } - - return _ucfirst($str); - } - - /** - * Makes the first character of every word in a UTF-8 string uppercase. - * @see http://php.net/ucwords - * - * @author Harry Fuecks <hfuecks@gmail.com> - * - * @param string mixed case string - * @return string - */ - public static function ucwords($str) - { - if ( ! isset(self::$called[__FUNCTION__])) - { - require SYSPATH.'core/utf8/'.__FUNCTION__.EXT; - - // Function has been called - self::$called[__FUNCTION__] = TRUE; - } - - return _ucwords($str); - } - - /** - * Case-insensitive UTF-8 string comparison. - * @see http://php.net/strcasecmp - * - * @author Harry Fuecks <hfuecks@gmail.com> - * - * @param string string to compare - * @param string string to compare - * @return integer less than 0 if str1 is less than str2 - * @return integer greater than 0 if str1 is greater than str2 - * @return integer 0 if they are equal - */ - public static function strcasecmp($str1, $str2) - { - if ( ! isset(self::$called[__FUNCTION__])) - { - require SYSPATH.'core/utf8/'.__FUNCTION__.EXT; - - // Function has been called - self::$called[__FUNCTION__] = TRUE; - } - - return _strcasecmp($str1, $str2); - } - - /** - * Returns a string or an array with all occurrences of search in subject (ignoring case). - * replaced with the given replace value. - * @see http://php.net/str_ireplace - * - * @note It's not fast and gets slower if $search and/or $replace are arrays. - * @author Harry Fuecks <hfuecks@gmail.com - * - * @param string|array text to replace - * @param string|array replacement text - * @param string|array subject text - * @param integer number of matched and replaced needles will be returned via this parameter which is passed by reference - * @return string if the input was a string - * @return array if the input was an array - */ - public static function str_ireplace($search, $replace, $str, & $count = NULL) - { - if ( ! isset(self::$called[__FUNCTION__])) - { - require SYSPATH.'core/utf8/'.__FUNCTION__.EXT; - - // Function has been called - self::$called[__FUNCTION__] = TRUE; - } - - return _str_ireplace($search, $replace, $str, $count); - } - - /** - * Case-insenstive UTF-8 version of strstr. Returns all of input string - * from the first occurrence of needle to the end. - * @see http://php.net/stristr - * - * @author Harry Fuecks <hfuecks@gmail.com> - * - * @param string input string - * @param string needle - * @return string matched substring if found - * @return boolean FALSE if the substring was not found - */ - public static function stristr($str, $search) - { - if ( ! isset(self::$called[__FUNCTION__])) - { - require SYSPATH.'core/utf8/'.__FUNCTION__.EXT; - - // Function has been called - self::$called[__FUNCTION__] = TRUE; - } - - return _stristr($str, $search); - } - - /** - * Finds the length of the initial segment matching mask. - * @see http://php.net/strspn - * - * @author Harry Fuecks <hfuecks@gmail.com> - * - * @param string input string - * @param string mask for search - * @param integer start position of the string to examine - * @param integer length of the string to examine - * @return integer length of the initial segment that contains characters in the mask - */ - public static function strspn($str, $mask, $offset = NULL, $length = NULL) - { - if ( ! isset(self::$called[__FUNCTION__])) - { - require SYSPATH.'core/utf8/'.__FUNCTION__.EXT; - - // Function has been called - self::$called[__FUNCTION__] = TRUE; - } - - return _strspn($str, $mask, $offset, $length); - } - - /** - * Finds the length of the initial segment not matching mask. - * @see http://php.net/strcspn - * - * @author Harry Fuecks <hfuecks@gmail.com> - * - * @param string input string - * @param string mask for search - * @param integer start position of the string to examine - * @param integer length of the string to examine - * @return integer length of the initial segment that contains characters not in the mask - */ - public static function strcspn($str, $mask, $offset = NULL, $length = NULL) - { - if ( ! isset(self::$called[__FUNCTION__])) - { - require SYSPATH.'core/utf8/'.__FUNCTION__.EXT; - - // Function has been called - self::$called[__FUNCTION__] = TRUE; - } - - return _strcspn($str, $mask, $offset, $length); - } - - /** - * Pads a UTF-8 string to a certain length with another string. - * @see http://php.net/str_pad - * - * @author Harry Fuecks <hfuecks@gmail.com> - * - * @param string input string - * @param integer desired string length after padding - * @param string string to use as padding - * @param string padding type: STR_PAD_RIGHT, STR_PAD_LEFT, or STR_PAD_BOTH - * @return string - */ - public static function str_pad($str, $final_str_length, $pad_str = ' ', $pad_type = STR_PAD_RIGHT) - { - if ( ! isset(self::$called[__FUNCTION__])) - { - require SYSPATH.'core/utf8/'.__FUNCTION__.EXT; - - // Function has been called - self::$called[__FUNCTION__] = TRUE; - } - - return _str_pad($str, $final_str_length, $pad_str, $pad_type); - } - - /** - * Converts a UTF-8 string to an array. - * @see http://php.net/str_split - * - * @author Harry Fuecks <hfuecks@gmail.com> - * - * @param string input string - * @param integer maximum length of each chunk - * @return array - */ - public static function str_split($str, $split_length = 1) - { - if ( ! isset(self::$called[__FUNCTION__])) - { - require SYSPATH.'core/utf8/'.__FUNCTION__.EXT; - - // Function has been called - self::$called[__FUNCTION__] = TRUE; - } - - return _str_split($str, $split_length); - } - - /** - * Reverses a UTF-8 string. - * @see http://php.net/strrev - * - * @author Harry Fuecks <hfuecks@gmail.com> - * - * @param string string to be reversed - * @return string - */ - public static function strrev($str) - { - if ( ! isset(self::$called[__FUNCTION__])) - { - require SYSPATH.'core/utf8/'.__FUNCTION__.EXT; - - // Function has been called - self::$called[__FUNCTION__] = TRUE; - } - - return _strrev($str); - } - - /** - * Strips whitespace (or other UTF-8 characters) from the beginning and - * end of a string. - * @see http://php.net/trim - * - * @author Andreas Gohr <andi@splitbrain.org> - * - * @param string input string - * @param string string of characters to remove - * @return string - */ - public static function trim($str, $charlist = NULL) - { - if ( ! isset(self::$called[__FUNCTION__])) - { - require SYSPATH.'core/utf8/'.__FUNCTION__.EXT; - - // Function has been called - self::$called[__FUNCTION__] = TRUE; - } - - return _trim($str, $charlist); - } - - /** - * Strips whitespace (or other UTF-8 characters) from the beginning of a string. - * @see http://php.net/ltrim - * - * @author Andreas Gohr <andi@splitbrain.org> - * - * @param string input string - * @param string string of characters to remove - * @return string - */ - public static function ltrim($str, $charlist = NULL) - { - if ( ! isset(self::$called[__FUNCTION__])) - { - require SYSPATH.'core/utf8/'.__FUNCTION__.EXT; - - // Function has been called - self::$called[__FUNCTION__] = TRUE; - } - - return _ltrim($str, $charlist); - } - - /** - * Strips whitespace (or other UTF-8 characters) from the end of a string. - * @see http://php.net/rtrim - * - * @author Andreas Gohr <andi@splitbrain.org> - * - * @param string input string - * @param string string of characters to remove - * @return string - */ - public static function rtrim($str, $charlist = NULL) - { - if ( ! isset(self::$called[__FUNCTION__])) - { - require SYSPATH.'core/utf8/'.__FUNCTION__.EXT; - - // Function has been called - self::$called[__FUNCTION__] = TRUE; - } - - return _rtrim($str, $charlist); - } - - /** - * Returns the unicode ordinal for a character. - * @see http://php.net/ord - * - * @author Harry Fuecks <hfuecks@gmail.com> - * - * @param string UTF-8 encoded character - * @return integer - */ - public static function ord($chr) - { - if ( ! isset(self::$called[__FUNCTION__])) - { - require SYSPATH.'core/utf8/'.__FUNCTION__.EXT; - - // Function has been called - self::$called[__FUNCTION__] = TRUE; - } - - return _ord($chr); - } - - /** - * Takes an UTF-8 string and returns an array of ints representing the Unicode characters. - * Astral planes are supported i.e. the ints in the output can be > 0xFFFF. - * Occurrances of the BOM are ignored. Surrogates are not allowed. - * - * The Original Code is Mozilla Communicator client code. - * The Initial Developer of the Original Code is Netscape Communications Corporation. - * Portions created by the Initial Developer are Copyright (C) 1998 the Initial Developer. - * Ported to PHP by Henri Sivonen <hsivonen@iki.fi>, see http://hsivonen.iki.fi/php-utf8/. - * Slight modifications to fit with phputf8 library by Harry Fuecks <hfuecks@gmail.com>. - * - * @param string UTF-8 encoded string - * @return array unicode code points - * @return boolean FALSE if the string is invalid - */ - public static function to_unicode($str) - { - if ( ! isset(self::$called[__FUNCTION__])) - { - require SYSPATH.'core/utf8/'.__FUNCTION__.EXT; - - // Function has been called - self::$called[__FUNCTION__] = TRUE; - } - - return _to_unicode($str); - } - - /** - * Takes an array of ints representing the Unicode characters and returns a UTF-8 string. - * Astral planes are supported i.e. the ints in the input can be > 0xFFFF. - * Occurrances of the BOM are ignored. Surrogates are not allowed. - * - * The Original Code is Mozilla Communicator client code. - * The Initial Developer of the Original Code is Netscape Communications Corporation. - * Portions created by the Initial Developer are Copyright (C) 1998 the Initial Developer. - * Ported to PHP by Henri Sivonen <hsivonen@iki.fi>, see http://hsivonen.iki.fi/php-utf8/. - * Slight modifications to fit with phputf8 library by Harry Fuecks <hfuecks@gmail.com>. - * - * @param array unicode code points representing a string - * @return string utf8 string of characters - * @return boolean FALSE if a code point cannot be found - */ - public static function from_unicode($arr) - { - if ( ! isset(self::$called[__FUNCTION__])) - { - require SYSPATH.'core/utf8/'.__FUNCTION__.EXT; - - // Function has been called - self::$called[__FUNCTION__] = TRUE; - } - - return _from_unicode($arr); - } - -} // End utf8
\ No newline at end of file diff --git a/system/core/utf8/from_unicode.php b/system/core/utf8/from_unicode.php deleted file mode 100644 index 66c6742d..00000000 --- a/system/core/utf8/from_unicode.php +++ /dev/null @@ -1,68 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * utf8::from_unicode - * - * @package Core - * @author Kohana Team - * @copyright (c) 2007 Kohana Team - * @copyright (c) 2005 Harry Fuecks - * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt - */ -function _from_unicode($arr) -{ - ob_start(); - - $keys = array_keys($arr); - - foreach ($keys as $k) - { - // ASCII range (including control chars) - if (($arr[$k] >= 0) AND ($arr[$k] <= 0x007f)) - { - echo chr($arr[$k]); - } - // 2 byte sequence - elseif ($arr[$k] <= 0x07ff) - { - echo chr(0xc0 | ($arr[$k] >> 6)); - echo chr(0x80 | ($arr[$k] & 0x003f)); - } - // Byte order mark (skip) - elseif ($arr[$k] == 0xFEFF) - { - // nop -- zap the BOM - } - // Test for illegal surrogates - elseif ($arr[$k] >= 0xD800 AND $arr[$k] <= 0xDFFF) - { - // Found a surrogate - trigger_error('utf8::from_unicode: Illegal surrogate at index: '.$k.', value: '.$arr[$k], E_USER_WARNING); - return FALSE; - } - // 3 byte sequence - elseif ($arr[$k] <= 0xffff) - { - echo chr(0xe0 | ($arr[$k] >> 12)); - echo chr(0x80 | (($arr[$k] >> 6) & 0x003f)); - echo chr(0x80 | ($arr[$k] & 0x003f)); - } - // 4 byte sequence - elseif ($arr[$k] <= 0x10ffff) - { - echo chr(0xf0 | ($arr[$k] >> 18)); - echo chr(0x80 | (($arr[$k] >> 12) & 0x3f)); - echo chr(0x80 | (($arr[$k] >> 6) & 0x3f)); - echo chr(0x80 | ($arr[$k] & 0x3f)); - } - // Out of range - else - { - trigger_error('utf8::from_unicode: Codepoint out of Unicode range at index: '.$k.', value: '.$arr[$k], E_USER_WARNING); - return FALSE; - } - } - - $result = ob_get_contents(); - ob_end_clean(); - return $result; -} diff --git a/system/core/utf8/ltrim.php b/system/core/utf8/ltrim.php deleted file mode 100644 index 556fe07f..00000000 --- a/system/core/utf8/ltrim.php +++ /dev/null @@ -1,22 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * utf8::ltrim - * - * @package Core - * @author Kohana Team - * @copyright (c) 2007 Kohana Team - * @copyright (c) 2005 Harry Fuecks - * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt - */ -function _ltrim($str, $charlist = NULL) -{ - if ($charlist === NULL) - return ltrim($str); - - if (utf8::is_ascii($charlist)) - return ltrim($str, $charlist); - - $charlist = preg_replace('#[-\[\]:\\\\^/]#', '\\\\$0', $charlist); - - return preg_replace('/^['.$charlist.']+/u', '', $str); -}
\ No newline at end of file diff --git a/system/core/utf8/ord.php b/system/core/utf8/ord.php deleted file mode 100644 index 76e31872..00000000 --- a/system/core/utf8/ord.php +++ /dev/null @@ -1,88 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * utf8::ord - * - * @package Core - * @author Kohana Team - * @copyright (c) 2007 Kohana Team - * @copyright (c) 2005 Harry Fuecks - * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt - */ -function _ord($chr) -{ - $ord0 = ord($chr); - - if ($ord0 >= 0 AND $ord0 <= 127) - { - return $ord0; - } - - if ( ! isset($chr[1])) - { - trigger_error('Short sequence - at least 2 bytes expected, only 1 seen', E_USER_WARNING); - return FALSE; - } - - $ord1 = ord($chr[1]); - - if ($ord0 >= 192 AND $ord0 <= 223) - { - return ($ord0 - 192) * 64 + ($ord1 - 128); - } - - if ( ! isset($chr[2])) - { - trigger_error('Short sequence - at least 3 bytes expected, only 2 seen', E_USER_WARNING); - return FALSE; - } - - $ord2 = ord($chr[2]); - - if ($ord0 >= 224 AND $ord0 <= 239) - { - return ($ord0 - 224) * 4096 + ($ord1 - 128) * 64 + ($ord2 - 128); - } - - if ( ! isset($chr[3])) - { - trigger_error('Short sequence - at least 4 bytes expected, only 3 seen', E_USER_WARNING); - return FALSE; - } - - $ord3 = ord($chr[3]); - - if ($ord0 >= 240 AND $ord0 <= 247) - { - return ($ord0 - 240) * 262144 + ($ord1 - 128) * 4096 + ($ord2-128) * 64 + ($ord3 - 128); - } - - if ( ! isset($chr[4])) - { - trigger_error('Short sequence - at least 5 bytes expected, only 4 seen', E_USER_WARNING); - return FALSE; - } - - $ord4 = ord($chr[4]); - - if ($ord0 >= 248 AND $ord0 <= 251) - { - return ($ord0 - 248) * 16777216 + ($ord1-128) * 262144 + ($ord2 - 128) * 4096 + ($ord3 - 128) * 64 + ($ord4 - 128); - } - - if ( ! isset($chr[5])) - { - trigger_error('Short sequence - at least 6 bytes expected, only 5 seen', E_USER_WARNING); - return FALSE; - } - - if ($ord0 >= 252 AND $ord0 <= 253) - { - return ($ord0 - 252) * 1073741824 + ($ord1 - 128) * 16777216 + ($ord2 - 128) * 262144 + ($ord3 - 128) * 4096 + ($ord4 - 128) * 64 + (ord($chr[5]) - 128); - } - - if ($ord0 >= 254 AND $ord0 <= 255) - { - trigger_error('Invalid UTF-8 with surrogate ordinal '.$ord0, E_USER_WARNING); - return FALSE; - } -}
\ No newline at end of file diff --git a/system/core/utf8/rtrim.php b/system/core/utf8/rtrim.php deleted file mode 100644 index efa0e19d..00000000 --- a/system/core/utf8/rtrim.php +++ /dev/null @@ -1,22 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * utf8::rtrim - * - * @package Core - * @author Kohana Team - * @copyright (c) 2007 Kohana Team - * @copyright (c) 2005 Harry Fuecks - * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt - */ -function _rtrim($str, $charlist = NULL) -{ - if ($charlist === NULL) - return rtrim($str); - - if (utf8::is_ascii($charlist)) - return rtrim($str, $charlist); - - $charlist = preg_replace('#[-\[\]:\\\\^/]#', '\\\\$0', $charlist); - - return preg_replace('/['.$charlist.']++$/uD', '', $str); -}
\ No newline at end of file diff --git a/system/core/utf8/str_ireplace.php b/system/core/utf8/str_ireplace.php deleted file mode 100644 index 97ad71a5..00000000 --- a/system/core/utf8/str_ireplace.php +++ /dev/null @@ -1,70 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * utf8::str_ireplace - * - * @package Core - * @author Kohana Team - * @copyright (c) 2007 Kohana Team - * @copyright (c) 2005 Harry Fuecks - * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt - */ -function _str_ireplace($search, $replace, $str, & $count = NULL) -{ - if (utf8::is_ascii($search) AND utf8::is_ascii($replace) AND utf8::is_ascii($str)) - return str_ireplace($search, $replace, $str, $count); - - if (is_array($str)) - { - foreach ($str as $key => $val) - { - $str[$key] = utf8::str_ireplace($search, $replace, $val, $count); - } - return $str; - } - - if (is_array($search)) - { - $keys = array_keys($search); - - foreach ($keys as $k) - { - if (is_array($replace)) - { - if (array_key_exists($k, $replace)) - { - $str = utf8::str_ireplace($search[$k], $replace[$k], $str, $count); - } - else - { - $str = utf8::str_ireplace($search[$k], '', $str, $count); - } - } - else - { - $str = utf8::str_ireplace($search[$k], $replace, $str, $count); - } - } - return $str; - } - - $search = utf8::strtolower($search); - $str_lower = utf8::strtolower($str); - - $total_matched_strlen = 0; - $i = 0; - - while (preg_match('/(.*?)'.preg_quote($search, '/').'/s', $str_lower, $matches)) - { - $matched_strlen = strlen($matches[0]); - $str_lower = substr($str_lower, $matched_strlen); - - $offset = $total_matched_strlen + strlen($matches[1]) + ($i * (strlen($replace) - 1)); - $str = substr_replace($str, $replace, $offset, strlen($search)); - - $total_matched_strlen += $matched_strlen; - $i++; - } - - $count += $i; - return $str; -} diff --git a/system/core/utf8/str_pad.php b/system/core/utf8/str_pad.php deleted file mode 100644 index aab4ccc7..00000000 --- a/system/core/utf8/str_pad.php +++ /dev/null @@ -1,54 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * utf8::str_pad - * - * @package Core - * @author Kohana Team - * @copyright (c) 2007 Kohana Team - * @copyright (c) 2005 Harry Fuecks - * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt - */ -function _str_pad($str, $final_str_length, $pad_str = ' ', $pad_type = STR_PAD_RIGHT) -{ - if (utf8::is_ascii($str) AND utf8::is_ascii($pad_str)) - { - return str_pad($str, $final_str_length, $pad_str, $pad_type); - } - - $str_length = utf8::strlen($str); - - if ($final_str_length <= 0 OR $final_str_length <= $str_length) - { - return $str; - } - - $pad_str_length = utf8::strlen($pad_str); - $pad_length = $final_str_length - $str_length; - - if ($pad_type == STR_PAD_RIGHT) - { - $repeat = ceil($pad_length / $pad_str_length); - return utf8::substr($str.str_repeat($pad_str, $repeat), 0, $final_str_length); - } - - if ($pad_type == STR_PAD_LEFT) - { - $repeat = ceil($pad_length / $pad_str_length); - return utf8::substr(str_repeat($pad_str, $repeat), 0, floor($pad_length)).$str; - } - - if ($pad_type == STR_PAD_BOTH) - { - $pad_length /= 2; - $pad_length_left = floor($pad_length); - $pad_length_right = ceil($pad_length); - $repeat_left = ceil($pad_length_left / $pad_str_length); - $repeat_right = ceil($pad_length_right / $pad_str_length); - - $pad_left = utf8::substr(str_repeat($pad_str, $repeat_left), 0, $pad_length_left); - $pad_right = utf8::substr(str_repeat($pad_str, $repeat_right), 0, $pad_length_left); - return $pad_left.$str.$pad_right; - } - - trigger_error('utf8::str_pad: Unknown padding type (' . $pad_type . ')', E_USER_ERROR); -}
\ No newline at end of file diff --git a/system/core/utf8/str_split.php b/system/core/utf8/str_split.php deleted file mode 100644 index bc382cb6..00000000 --- a/system/core/utf8/str_split.php +++ /dev/null @@ -1,33 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * utf8::str_split - * - * @package Core - * @author Kohana Team - * @copyright (c) 2007 Kohana Team - * @copyright (c) 2005 Harry Fuecks - * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt - */ -function _str_split($str, $split_length = 1) -{ - $split_length = (int) $split_length; - - if (utf8::is_ascii($str)) - { - return str_split($str, $split_length); - } - - if ($split_length < 1) - { - return FALSE; - } - - if (utf8::strlen($str) <= $split_length) - { - return array($str); - } - - preg_match_all('/.{'.$split_length.'}|[^\x00]{1,'.$split_length.'}$/us', $str, $matches); - - return $matches[0]; -}
\ No newline at end of file diff --git a/system/core/utf8/strcasecmp.php b/system/core/utf8/strcasecmp.php deleted file mode 100644 index d06d3ebd..00000000 --- a/system/core/utf8/strcasecmp.php +++ /dev/null @@ -1,19 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * utf8::strcasecmp - * - * @package Core - * @author Kohana Team - * @copyright (c) 2007 Kohana Team - * @copyright (c) 2005 Harry Fuecks - * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt - */ -function _strcasecmp($str1, $str2) -{ - if (utf8::is_ascii($str1) AND utf8::is_ascii($str2)) - return strcasecmp($str1, $str2); - - $str1 = utf8::strtolower($str1); - $str2 = utf8::strtolower($str2); - return strcmp($str1, $str2); -}
\ No newline at end of file diff --git a/system/core/utf8/strcspn.php b/system/core/utf8/strcspn.php deleted file mode 100644 index 20d3e802..00000000 --- a/system/core/utf8/strcspn.php +++ /dev/null @@ -1,30 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * utf8::strcspn - * - * @package Core - * @author Kohana Team - * @copyright (c) 2007 Kohana Team - * @copyright (c) 2005 Harry Fuecks - * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt - */ -function _strcspn($str, $mask, $offset = NULL, $length = NULL) -{ - if ($str == '' OR $mask == '') - return 0; - - if (utf8::is_ascii($str) AND utf8::is_ascii($mask)) - return ($offset === NULL) ? strcspn($str, $mask) : (($length === NULL) ? strcspn($str, $mask, $offset) : strcspn($str, $mask, $offset, $length)); - - if ($str !== NULL OR $length !== NULL) - { - $str = utf8::substr($str, $offset, $length); - } - - // Escape these characters: - [ ] . : \ ^ / - // The . and : are escaped to prevent possible warnings about POSIX regex elements - $mask = preg_replace('#[-[\].:\\\\^/]#', '\\\\$0', $mask); - preg_match('/^[^'.$mask.']+/u', $str, $matches); - - return isset($matches[0]) ? utf8::strlen($matches[0]) : 0; -}
\ No newline at end of file diff --git a/system/core/utf8/stristr.php b/system/core/utf8/stristr.php deleted file mode 100644 index 73ea1394..00000000 --- a/system/core/utf8/stristr.php +++ /dev/null @@ -1,28 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * utf8::stristr - * - * @package Core - * @author Kohana Team - * @copyright (c) 2007 Kohana Team - * @copyright (c) 2005 Harry Fuecks - * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt - */ -function _stristr($str, $search) -{ - if (utf8::is_ascii($str) AND utf8::is_ascii($search)) - return stristr($str, $search); - - if ($search == '') - return $str; - - $str_lower = utf8::strtolower($str); - $search_lower = utf8::strtolower($search); - - preg_match('/^(.*?)'.preg_quote($search, '/').'/s', $str_lower, $matches); - - if (isset($matches[1])) - return substr($str, strlen($matches[1])); - - return FALSE; -}
\ No newline at end of file diff --git a/system/core/utf8/strlen.php b/system/core/utf8/strlen.php deleted file mode 100644 index f186a9c5..00000000 --- a/system/core/utf8/strlen.php +++ /dev/null @@ -1,21 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * utf8::strlen - * - * @package Core - * @author Kohana Team - * @copyright (c) 2007 Kohana Team - * @copyright (c) 2005 Harry Fuecks - * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt - */ -function _strlen($str) -{ - // Try mb_strlen() first because it's faster than combination of is_ascii() and strlen() - if (SERVER_UTF8) - return mb_strlen($str); - - if (utf8::is_ascii($str)) - return strlen($str); - - return strlen(utf8_decode($str)); -}
\ No newline at end of file diff --git a/system/core/utf8/strpos.php b/system/core/utf8/strpos.php deleted file mode 100644 index 4a34dbe9..00000000 --- a/system/core/utf8/strpos.php +++ /dev/null @@ -1,30 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * utf8::strpos - * - * @package Core - * @author Kohana Team - * @copyright (c) 2007 Kohana Team - * @copyright (c) 2005 Harry Fuecks - * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt - */ -function _strpos($str, $search, $offset = 0) -{ - $offset = (int) $offset; - - if (SERVER_UTF8) - return mb_strpos($str, $search, $offset); - - if (utf8::is_ascii($str) AND utf8::is_ascii($search)) - return strpos($str, $search, $offset); - - if ($offset == 0) - { - $array = explode($search, $str, 2); - return isset($array[1]) ? utf8::strlen($array[0]) : FALSE; - } - - $str = utf8::substr($str, $offset); - $pos = utf8::strpos($str, $search); - return ($pos === FALSE) ? FALSE : $pos + $offset; -}
\ No newline at end of file diff --git a/system/core/utf8/strrev.php b/system/core/utf8/strrev.php deleted file mode 100644 index 89c428cd..00000000 --- a/system/core/utf8/strrev.php +++ /dev/null @@ -1,18 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * utf8::strrev - * - * @package Core - * @author Kohana Team - * @copyright (c) 2007 Kohana Team - * @copyright (c) 2005 Harry Fuecks - * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt - */ -function _strrev($str) -{ - if (utf8::is_ascii($str)) - return strrev($str); - - preg_match_all('/./us', $str, $matches); - return implode('', array_reverse($matches[0])); -}
\ No newline at end of file diff --git a/system/core/utf8/strrpos.php b/system/core/utf8/strrpos.php deleted file mode 100644 index 1ac8f9c8..00000000 --- a/system/core/utf8/strrpos.php +++ /dev/null @@ -1,30 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * utf8::strrpos - * - * @package Core - * @author Kohana Team - * @copyright (c) 2007 Kohana Team - * @copyright (c) 2005 Harry Fuecks - * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt - */ -function _strrpos($str, $search, $offset = 0) -{ - $offset = (int) $offset; - - if (SERVER_UTF8) - return mb_strrpos($str, $search, $offset); - - if (utf8::is_ascii($str) AND utf8::is_ascii($search)) - return strrpos($str, $search, $offset); - - if ($offset == 0) - { - $array = explode($search, $str, -1); - return isset($array[0]) ? utf8::strlen(implode($search, $array)) : FALSE; - } - - $str = utf8::substr($str, $offset); - $pos = utf8::strrpos($str, $search); - return ($pos === FALSE) ? FALSE : $pos + $offset; -}
\ No newline at end of file diff --git a/system/core/utf8/strspn.php b/system/core/utf8/strspn.php deleted file mode 100644 index bc7e52f6..00000000 --- a/system/core/utf8/strspn.php +++ /dev/null @@ -1,30 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * utf8::strspn - * - * @package Core - * @author Kohana Team - * @copyright (c) 2007 Kohana Team - * @copyright (c) 2005 Harry Fuecks - * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt - */ -function _strspn($str, $mask, $offset = NULL, $length = NULL) -{ - if ($str == '' OR $mask == '') - return 0; - - if (utf8::is_ascii($str) AND utf8::is_ascii($mask)) - return ($offset === NULL) ? strspn($str, $mask) : (($length === NULL) ? strspn($str, $mask, $offset) : strspn($str, $mask, $offset, $length)); - - if ($offset !== NULL OR $length !== NULL) - { - $str = utf8::substr($str, $offset, $length); - } - - // Escape these characters: - [ ] . : \ ^ / - // The . and : are escaped to prevent possible warnings about POSIX regex elements - $mask = preg_replace('#[-[\].:\\\\^/]#', '\\\\$0', $mask); - preg_match('/^[^'.$mask.']+/u', $str, $matches); - - return isset($matches[0]) ? utf8::strlen($matches[0]) : 0; -}
\ No newline at end of file diff --git a/system/core/utf8/strtolower.php b/system/core/utf8/strtolower.php deleted file mode 100644 index ff4da857..00000000 --- a/system/core/utf8/strtolower.php +++ /dev/null @@ -1,84 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * utf8::strtolower - * - * @package Core - * @author Kohana Team - * @copyright (c) 2007 Kohana Team - * @copyright (c) 2005 Harry Fuecks - * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt - */ -function _strtolower($str) -{ - if (SERVER_UTF8) - return mb_strtolower($str); - - if (utf8::is_ascii($str)) - return strtolower($str); - - static $UTF8_UPPER_TO_LOWER = NULL; - - if ($UTF8_UPPER_TO_LOWER === NULL) - { - $UTF8_UPPER_TO_LOWER = array( - 0x0041=>0x0061, 0x03A6=>0x03C6, 0x0162=>0x0163, 0x00C5=>0x00E5, 0x0042=>0x0062, - 0x0139=>0x013A, 0x00C1=>0x00E1, 0x0141=>0x0142, 0x038E=>0x03CD, 0x0100=>0x0101, - 0x0490=>0x0491, 0x0394=>0x03B4, 0x015A=>0x015B, 0x0044=>0x0064, 0x0393=>0x03B3, - 0x00D4=>0x00F4, 0x042A=>0x044A, 0x0419=>0x0439, 0x0112=>0x0113, 0x041C=>0x043C, - 0x015E=>0x015F, 0x0143=>0x0144, 0x00CE=>0x00EE, 0x040E=>0x045E, 0x042F=>0x044F, - 0x039A=>0x03BA, 0x0154=>0x0155, 0x0049=>0x0069, 0x0053=>0x0073, 0x1E1E=>0x1E1F, - 0x0134=>0x0135, 0x0427=>0x0447, 0x03A0=>0x03C0, 0x0418=>0x0438, 0x00D3=>0x00F3, - 0x0420=>0x0440, 0x0404=>0x0454, 0x0415=>0x0435, 0x0429=>0x0449, 0x014A=>0x014B, - 0x0411=>0x0431, 0x0409=>0x0459, 0x1E02=>0x1E03, 0x00D6=>0x00F6, 0x00D9=>0x00F9, - 0x004E=>0x006E, 0x0401=>0x0451, 0x03A4=>0x03C4, 0x0423=>0x0443, 0x015C=>0x015D, - 0x0403=>0x0453, 0x03A8=>0x03C8, 0x0158=>0x0159, 0x0047=>0x0067, 0x00C4=>0x00E4, - 0x0386=>0x03AC, 0x0389=>0x03AE, 0x0166=>0x0167, 0x039E=>0x03BE, 0x0164=>0x0165, - 0x0116=>0x0117, 0x0108=>0x0109, 0x0056=>0x0076, 0x00DE=>0x00FE, 0x0156=>0x0157, - 0x00DA=>0x00FA, 0x1E60=>0x1E61, 0x1E82=>0x1E83, 0x00C2=>0x00E2, 0x0118=>0x0119, - 0x0145=>0x0146, 0x0050=>0x0070, 0x0150=>0x0151, 0x042E=>0x044E, 0x0128=>0x0129, - 0x03A7=>0x03C7, 0x013D=>0x013E, 0x0422=>0x0442, 0x005A=>0x007A, 0x0428=>0x0448, - 0x03A1=>0x03C1, 0x1E80=>0x1E81, 0x016C=>0x016D, 0x00D5=>0x00F5, 0x0055=>0x0075, - 0x0176=>0x0177, 0x00DC=>0x00FC, 0x1E56=>0x1E57, 0x03A3=>0x03C3, 0x041A=>0x043A, - 0x004D=>0x006D, 0x016A=>0x016B, 0x0170=>0x0171, 0x0424=>0x0444, 0x00CC=>0x00EC, - 0x0168=>0x0169, 0x039F=>0x03BF, 0x004B=>0x006B, 0x00D2=>0x00F2, 0x00C0=>0x00E0, - 0x0414=>0x0434, 0x03A9=>0x03C9, 0x1E6A=>0x1E6B, 0x00C3=>0x00E3, 0x042D=>0x044D, - 0x0416=>0x0436, 0x01A0=>0x01A1, 0x010C=>0x010D, 0x011C=>0x011D, 0x00D0=>0x00F0, - 0x013B=>0x013C, 0x040F=>0x045F, 0x040A=>0x045A, 0x00C8=>0x00E8, 0x03A5=>0x03C5, - 0x0046=>0x0066, 0x00DD=>0x00FD, 0x0043=>0x0063, 0x021A=>0x021B, 0x00CA=>0x00EA, - 0x0399=>0x03B9, 0x0179=>0x017A, 0x00CF=>0x00EF, 0x01AF=>0x01B0, 0x0045=>0x0065, - 0x039B=>0x03BB, 0x0398=>0x03B8, 0x039C=>0x03BC, 0x040C=>0x045C, 0x041F=>0x043F, - 0x042C=>0x044C, 0x00DE=>0x00FE, 0x00D0=>0x00F0, 0x1EF2=>0x1EF3, 0x0048=>0x0068, - 0x00CB=>0x00EB, 0x0110=>0x0111, 0x0413=>0x0433, 0x012E=>0x012F, 0x00C6=>0x00E6, - 0x0058=>0x0078, 0x0160=>0x0161, 0x016E=>0x016F, 0x0391=>0x03B1, 0x0407=>0x0457, - 0x0172=>0x0173, 0x0178=>0x00FF, 0x004F=>0x006F, 0x041B=>0x043B, 0x0395=>0x03B5, - 0x0425=>0x0445, 0x0120=>0x0121, 0x017D=>0x017E, 0x017B=>0x017C, 0x0396=>0x03B6, - 0x0392=>0x03B2, 0x0388=>0x03AD, 0x1E84=>0x1E85, 0x0174=>0x0175, 0x0051=>0x0071, - 0x0417=>0x0437, 0x1E0A=>0x1E0B, 0x0147=>0x0148, 0x0104=>0x0105, 0x0408=>0x0458, - 0x014C=>0x014D, 0x00CD=>0x00ED, 0x0059=>0x0079, 0x010A=>0x010B, 0x038F=>0x03CE, - 0x0052=>0x0072, 0x0410=>0x0430, 0x0405=>0x0455, 0x0402=>0x0452, 0x0126=>0x0127, - 0x0136=>0x0137, 0x012A=>0x012B, 0x038A=>0x03AF, 0x042B=>0x044B, 0x004C=>0x006C, - 0x0397=>0x03B7, 0x0124=>0x0125, 0x0218=>0x0219, 0x00DB=>0x00FB, 0x011E=>0x011F, - 0x041E=>0x043E, 0x1E40=>0x1E41, 0x039D=>0x03BD, 0x0106=>0x0107, 0x03AB=>0x03CB, - 0x0426=>0x0446, 0x00DE=>0x00FE, 0x00C7=>0x00E7, 0x03AA=>0x03CA, 0x0421=>0x0441, - 0x0412=>0x0432, 0x010E=>0x010F, 0x00D8=>0x00F8, 0x0057=>0x0077, 0x011A=>0x011B, - 0x0054=>0x0074, 0x004A=>0x006A, 0x040B=>0x045B, 0x0406=>0x0456, 0x0102=>0x0103, - 0x039B=>0x03BB, 0x00D1=>0x00F1, 0x041D=>0x043D, 0x038C=>0x03CC, 0x00C9=>0x00E9, - 0x00D0=>0x00F0, 0x0407=>0x0457, 0x0122=>0x0123, - ); - } - - $uni = utf8::to_unicode($str); - - if ($uni === FALSE) - return FALSE; - - for ($i = 0, $c = count($uni); $i < $c; $i++) - { - if (isset($UTF8_UPPER_TO_LOWER[$uni[$i]])) - { - $uni[$i] = $UTF8_UPPER_TO_LOWER[$uni[$i]]; - } - } - - return utf8::from_unicode($uni); -}
\ No newline at end of file diff --git a/system/core/utf8/strtoupper.php b/system/core/utf8/strtoupper.php deleted file mode 100644 index f3ded739..00000000 --- a/system/core/utf8/strtoupper.php +++ /dev/null @@ -1,84 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * utf8::strtoupper - * - * @package Core - * @author Kohana Team - * @copyright (c) 2007 Kohana Team - * @copyright (c) 2005 Harry Fuecks - * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt - */ -function _strtoupper($str) -{ - if (SERVER_UTF8) - return mb_strtoupper($str); - - if (utf8::is_ascii($str)) - return strtoupper($str); - - static $UTF8_LOWER_TO_UPPER = NULL; - - if ($UTF8_LOWER_TO_UPPER === NULL) - { - $UTF8_LOWER_TO_UPPER = array( - 0x0061=>0x0041, 0x03C6=>0x03A6, 0x0163=>0x0162, 0x00E5=>0x00C5, 0x0062=>0x0042, - 0x013A=>0x0139, 0x00E1=>0x00C1, 0x0142=>0x0141, 0x03CD=>0x038E, 0x0101=>0x0100, - 0x0491=>0x0490, 0x03B4=>0x0394, 0x015B=>0x015A, 0x0064=>0x0044, 0x03B3=>0x0393, - 0x00F4=>0x00D4, 0x044A=>0x042A, 0x0439=>0x0419, 0x0113=>0x0112, 0x043C=>0x041C, - 0x015F=>0x015E, 0x0144=>0x0143, 0x00EE=>0x00CE, 0x045E=>0x040E, 0x044F=>0x042F, - 0x03BA=>0x039A, 0x0155=>0x0154, 0x0069=>0x0049, 0x0073=>0x0053, 0x1E1F=>0x1E1E, - 0x0135=>0x0134, 0x0447=>0x0427, 0x03C0=>0x03A0, 0x0438=>0x0418, 0x00F3=>0x00D3, - 0x0440=>0x0420, 0x0454=>0x0404, 0x0435=>0x0415, 0x0449=>0x0429, 0x014B=>0x014A, - 0x0431=>0x0411, 0x0459=>0x0409, 0x1E03=>0x1E02, 0x00F6=>0x00D6, 0x00F9=>0x00D9, - 0x006E=>0x004E, 0x0451=>0x0401, 0x03C4=>0x03A4, 0x0443=>0x0423, 0x015D=>0x015C, - 0x0453=>0x0403, 0x03C8=>0x03A8, 0x0159=>0x0158, 0x0067=>0x0047, 0x00E4=>0x00C4, - 0x03AC=>0x0386, 0x03AE=>0x0389, 0x0167=>0x0166, 0x03BE=>0x039E, 0x0165=>0x0164, - 0x0117=>0x0116, 0x0109=>0x0108, 0x0076=>0x0056, 0x00FE=>0x00DE, 0x0157=>0x0156, - 0x00FA=>0x00DA, 0x1E61=>0x1E60, 0x1E83=>0x1E82, 0x00E2=>0x00C2, 0x0119=>0x0118, - 0x0146=>0x0145, 0x0070=>0x0050, 0x0151=>0x0150, 0x044E=>0x042E, 0x0129=>0x0128, - 0x03C7=>0x03A7, 0x013E=>0x013D, 0x0442=>0x0422, 0x007A=>0x005A, 0x0448=>0x0428, - 0x03C1=>0x03A1, 0x1E81=>0x1E80, 0x016D=>0x016C, 0x00F5=>0x00D5, 0x0075=>0x0055, - 0x0177=>0x0176, 0x00FC=>0x00DC, 0x1E57=>0x1E56, 0x03C3=>0x03A3, 0x043A=>0x041A, - 0x006D=>0x004D, 0x016B=>0x016A, 0x0171=>0x0170, 0x0444=>0x0424, 0x00EC=>0x00CC, - 0x0169=>0x0168, 0x03BF=>0x039F, 0x006B=>0x004B, 0x00F2=>0x00D2, 0x00E0=>0x00C0, - 0x0434=>0x0414, 0x03C9=>0x03A9, 0x1E6B=>0x1E6A, 0x00E3=>0x00C3, 0x044D=>0x042D, - 0x0436=>0x0416, 0x01A1=>0x01A0, 0x010D=>0x010C, 0x011D=>0x011C, 0x00F0=>0x00D0, - 0x013C=>0x013B, 0x045F=>0x040F, 0x045A=>0x040A, 0x00E8=>0x00C8, 0x03C5=>0x03A5, - 0x0066=>0x0046, 0x00FD=>0x00DD, 0x0063=>0x0043, 0x021B=>0x021A, 0x00EA=>0x00CA, - 0x03B9=>0x0399, 0x017A=>0x0179, 0x00EF=>0x00CF, 0x01B0=>0x01AF, 0x0065=>0x0045, - 0x03BB=>0x039B, 0x03B8=>0x0398, 0x03BC=>0x039C, 0x045C=>0x040C, 0x043F=>0x041F, - 0x044C=>0x042C, 0x00FE=>0x00DE, 0x00F0=>0x00D0, 0x1EF3=>0x1EF2, 0x0068=>0x0048, - 0x00EB=>0x00CB, 0x0111=>0x0110, 0x0433=>0x0413, 0x012F=>0x012E, 0x00E6=>0x00C6, - 0x0078=>0x0058, 0x0161=>0x0160, 0x016F=>0x016E, 0x03B1=>0x0391, 0x0457=>0x0407, - 0x0173=>0x0172, 0x00FF=>0x0178, 0x006F=>0x004F, 0x043B=>0x041B, 0x03B5=>0x0395, - 0x0445=>0x0425, 0x0121=>0x0120, 0x017E=>0x017D, 0x017C=>0x017B, 0x03B6=>0x0396, - 0x03B2=>0x0392, 0x03AD=>0x0388, 0x1E85=>0x1E84, 0x0175=>0x0174, 0x0071=>0x0051, - 0x0437=>0x0417, 0x1E0B=>0x1E0A, 0x0148=>0x0147, 0x0105=>0x0104, 0x0458=>0x0408, - 0x014D=>0x014C, 0x00ED=>0x00CD, 0x0079=>0x0059, 0x010B=>0x010A, 0x03CE=>0x038F, - 0x0072=>0x0052, 0x0430=>0x0410, 0x0455=>0x0405, 0x0452=>0x0402, 0x0127=>0x0126, - 0x0137=>0x0136, 0x012B=>0x012A, 0x03AF=>0x038A, 0x044B=>0x042B, 0x006C=>0x004C, - 0x03B7=>0x0397, 0x0125=>0x0124, 0x0219=>0x0218, 0x00FB=>0x00DB, 0x011F=>0x011E, - 0x043E=>0x041E, 0x1E41=>0x1E40, 0x03BD=>0x039D, 0x0107=>0x0106, 0x03CB=>0x03AB, - 0x0446=>0x0426, 0x00FE=>0x00DE, 0x00E7=>0x00C7, 0x03CA=>0x03AA, 0x0441=>0x0421, - 0x0432=>0x0412, 0x010F=>0x010E, 0x00F8=>0x00D8, 0x0077=>0x0057, 0x011B=>0x011A, - 0x0074=>0x0054, 0x006A=>0x004A, 0x045B=>0x040B, 0x0456=>0x0406, 0x0103=>0x0102, - 0x03BB=>0x039B, 0x00F1=>0x00D1, 0x043D=>0x041D, 0x03CC=>0x038C, 0x00E9=>0x00C9, - 0x00F0=>0x00D0, 0x0457=>0x0407, 0x0123=>0x0122, - ); - } - - $uni = utf8::to_unicode($str); - - if ($uni === FALSE) - return FALSE; - - for ($i = 0, $c = count($uni); $i < $c; $i++) - { - if (isset($UTF8_LOWER_TO_UPPER[$uni[$i]])) - { - $uni[$i] = $UTF8_LOWER_TO_UPPER[$uni[$i]]; - } - } - - return utf8::from_unicode($uni); -}
\ No newline at end of file diff --git a/system/core/utf8/substr.php b/system/core/utf8/substr.php deleted file mode 100644 index daf66b81..00000000 --- a/system/core/utf8/substr.php +++ /dev/null @@ -1,75 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * utf8::substr - * - * @package Core - * @author Kohana Team - * @copyright (c) 2007 Kohana Team - * @copyright (c) 2005 Harry Fuecks - * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt - */ -function _substr($str, $offset, $length = NULL) -{ - if (SERVER_UTF8) - return ($length === NULL) ? mb_substr($str, $offset) : mb_substr($str, $offset, $length); - - if (utf8::is_ascii($str)) - return ($length === NULL) ? substr($str, $offset) : substr($str, $offset, $length); - - // Normalize params - $str = (string) $str; - $strlen = utf8::strlen($str); - $offset = (int) ($offset < 0) ? max(0, $strlen + $offset) : $offset; // Normalize to positive offset - $length = ($length === NULL) ? NULL : (int) $length; - - // Impossible - if ($length === 0 OR $offset >= $strlen OR ($length < 0 AND $length <= $offset - $strlen)) - return ''; - - // Whole string - if ($offset == 0 AND ($length === NULL OR $length >= $strlen)) - return $str; - - // Build regex - $regex = '^'; - - // Create an offset expression - if ($offset > 0) - { - // PCRE repeating quantifiers must be less than 65536, so repeat when necessary - $x = (int) ($offset / 65535); - $y = (int) ($offset % 65535); - $regex .= ($x == 0) ? '' : '(?:.{65535}){'.$x.'}'; - $regex .= ($y == 0) ? '' : '.{'.$y.'}'; - } - - // Create a length expression - if ($length === NULL) - { - $regex .= '(.*)'; // No length set, grab it all - } - // Find length from the left (positive length) - elseif ($length > 0) - { - // Reduce length so that it can't go beyond the end of the string - $length = min($strlen - $offset, $length); - - $x = (int) ($length / 65535); - $y = (int) ($length % 65535); - $regex .= '('; - $regex .= ($x == 0) ? '' : '(?:.{65535}){'.$x.'}'; - $regex .= '.{'.$y.'})'; - } - // Find length from the right (negative length) - else - { - $x = (int) (-$length / 65535); - $y = (int) (-$length % 65535); - $regex .= '(.*)'; - $regex .= ($x == 0) ? '' : '(?:.{65535}){'.$x.'}'; - $regex .= '.{'.$y.'}'; - } - - preg_match('/'.$regex.'/us', $str, $matches); - return $matches[1]; -}
\ No newline at end of file diff --git a/system/core/utf8/substr_replace.php b/system/core/utf8/substr_replace.php deleted file mode 100644 index 45e2d2a3..00000000 --- a/system/core/utf8/substr_replace.php +++ /dev/null @@ -1,22 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * utf8::substr_replace - * - * @package Core - * @author Kohana Team - * @copyright (c) 2007 Kohana Team - * @copyright (c) 2005 Harry Fuecks - * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt - */ -function _substr_replace($str, $replacement, $offset, $length = NULL) -{ - if (utf8::is_ascii($str)) - return ($length === NULL) ? substr_replace($str, $replacement, $offset) : substr_replace($str, $replacement, $offset, $length); - - $length = ($length === NULL) ? utf8::strlen($str) : (int) $length; - preg_match_all('/./us', $str, $str_array); - preg_match_all('/./us', $replacement, $replacement_array); - - array_splice($str_array[0], $offset, $length, $replacement_array[0]); - return implode('', $str_array[0]); -}
\ No newline at end of file diff --git a/system/core/utf8/to_unicode.php b/system/core/utf8/to_unicode.php deleted file mode 100644 index 93f741a6..00000000 --- a/system/core/utf8/to_unicode.php +++ /dev/null @@ -1,141 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * utf8::to_unicode - * - * @package Core - * @author Kohana Team - * @copyright (c) 2007 Kohana Team - * @copyright (c) 2005 Harry Fuecks - * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt - */ -function _to_unicode($str) -{ - $mState = 0; // cached expected number of octets after the current octet until the beginning of the next UTF8 character sequence - $mUcs4 = 0; // cached Unicode character - $mBytes = 1; // cached expected number of octets in the current sequence - - $out = array(); - - $len = strlen($str); - - for ($i = 0; $i < $len; $i++) - { - $in = ord($str[$i]); - - if ($mState == 0) - { - // When mState is zero we expect either a US-ASCII character or a - // multi-octet sequence. - if (0 == (0x80 & $in)) - { - // US-ASCII, pass straight through. - $out[] = $in; - $mBytes = 1; - } - elseif (0xC0 == (0xE0 & $in)) - { - // First octet of 2 octet sequence - $mUcs4 = $in; - $mUcs4 = ($mUcs4 & 0x1F) << 6; - $mState = 1; - $mBytes = 2; - } - elseif (0xE0 == (0xF0 & $in)) - { - // First octet of 3 octet sequence - $mUcs4 = $in; - $mUcs4 = ($mUcs4 & 0x0F) << 12; - $mState = 2; - $mBytes = 3; - } - elseif (0xF0 == (0xF8 & $in)) - { - // First octet of 4 octet sequence - $mUcs4 = $in; - $mUcs4 = ($mUcs4 & 0x07) << 18; - $mState = 3; - $mBytes = 4; - } - elseif (0xF8 == (0xFC & $in)) - { - // First octet of 5 octet sequence. - // - // This is illegal because the encoded codepoint must be either - // (a) not the shortest form or - // (b) outside the Unicode range of 0-0x10FFFF. - // Rather than trying to resynchronize, we will carry on until the end - // of the sequence and let the later error handling code catch it. - $mUcs4 = $in; - $mUcs4 = ($mUcs4 & 0x03) << 24; - $mState = 4; - $mBytes = 5; - } - elseif (0xFC == (0xFE & $in)) - { - // First octet of 6 octet sequence, see comments for 5 octet sequence. - $mUcs4 = $in; - $mUcs4 = ($mUcs4 & 1) << 30; - $mState = 5; - $mBytes = 6; - } - else - { - // Current octet is neither in the US-ASCII range nor a legal first octet of a multi-octet sequence. - trigger_error('utf8::to_unicode: Illegal sequence identifier in UTF-8 at byte '.$i, E_USER_WARNING); - return FALSE; - } - } - else - { - // When mState is non-zero, we expect a continuation of the multi-octet sequence - if (0x80 == (0xC0 & $in)) - { - // Legal continuation - $shift = ($mState - 1) * 6; - $tmp = $in; - $tmp = ($tmp & 0x0000003F) << $shift; - $mUcs4 |= $tmp; - - // End of the multi-octet sequence. mUcs4 now contains the final Unicode codepoint to be output - if (0 == --$mState) - { - // Check for illegal sequences and codepoints - - // From Unicode 3.1, non-shortest form is illegal - if (((2 == $mBytes) AND ($mUcs4 < 0x0080)) OR - ((3 == $mBytes) AND ($mUcs4 < 0x0800)) OR - ((4 == $mBytes) AND ($mUcs4 < 0x10000)) OR - (4 < $mBytes) OR - // From Unicode 3.2, surrogate characters are illegal - (($mUcs4 & 0xFFFFF800) == 0xD800) OR - // Codepoints outside the Unicode range are illegal - ($mUcs4 > 0x10FFFF)) - { - trigger_error('utf8::to_unicode: Illegal sequence or codepoint in UTF-8 at byte '.$i, E_USER_WARNING); - return FALSE; - } - - if (0xFEFF != $mUcs4) - { - // BOM is legal but we don't want to output it - $out[] = $mUcs4; - } - - // Initialize UTF-8 cache - $mState = 0; - $mUcs4 = 0; - $mBytes = 1; - } - } - else - { - // ((0xC0 & (*in) != 0x80) AND (mState != 0)) - // Incomplete multi-octet sequence - trigger_error('utf8::to_unicode: Incomplete multi-octet sequence in UTF-8 at byte '.$i, E_USER_WARNING); - return FALSE; - } - } - } - - return $out; -}
\ No newline at end of file diff --git a/system/core/utf8/transliterate_to_ascii.php b/system/core/utf8/transliterate_to_ascii.php deleted file mode 100644 index 07461fbb..00000000 --- a/system/core/utf8/transliterate_to_ascii.php +++ /dev/null @@ -1,77 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * utf8::transliterate_to_ascii - * - * @package Core - * @author Kohana Team - * @copyright (c) 2007 Kohana Team - * @copyright (c) 2005 Harry Fuecks - * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt - */ -function _transliterate_to_ascii($str, $case = 0) -{ - static $UTF8_LOWER_ACCENTS = NULL; - static $UTF8_UPPER_ACCENTS = NULL; - - if ($case <= 0) - { - if ($UTF8_LOWER_ACCENTS === NULL) - { - $UTF8_LOWER_ACCENTS = array( - 'à' => 'a', 'ô' => 'o', 'ď' => 'd', 'ḟ' => 'f', 'ë' => 'e', 'š' => 's', 'ơ' => 'o', - 'ß' => 'ss', 'ă' => 'a', 'ř' => 'r', 'ț' => 't', 'ň' => 'n', 'ā' => 'a', 'ķ' => 'k', - 'ŝ' => 's', 'ỳ' => 'y', 'ņ' => 'n', 'ĺ' => 'l', 'ħ' => 'h', 'ṗ' => 'p', 'ó' => 'o', - 'ú' => 'u', 'ě' => 'e', 'é' => 'e', 'ç' => 'c', 'ẁ' => 'w', 'ċ' => 'c', 'õ' => 'o', - 'ṡ' => 's', 'ø' => 'o', 'ģ' => 'g', 'ŧ' => 't', 'ș' => 's', 'ė' => 'e', 'ĉ' => 'c', - 'ś' => 's', 'î' => 'i', 'ű' => 'u', 'ć' => 'c', 'ę' => 'e', 'ŵ' => 'w', 'ṫ' => 't', - 'ū' => 'u', 'č' => 'c', 'ö' => 'o', 'è' => 'e', 'ŷ' => 'y', 'ą' => 'a', 'ł' => 'l', - 'ų' => 'u', 'ů' => 'u', 'ş' => 's', 'ğ' => 'g', 'ļ' => 'l', 'ƒ' => 'f', 'ž' => 'z', - 'ẃ' => 'w', 'ḃ' => 'b', 'å' => 'a', 'ì' => 'i', 'ï' => 'i', 'ḋ' => 'd', 'ť' => 't', - 'ŗ' => 'r', 'ä' => 'a', 'í' => 'i', 'ŕ' => 'r', 'ê' => 'e', 'ü' => 'u', 'ò' => 'o', - 'ē' => 'e', 'ñ' => 'n', 'ń' => 'n', 'ĥ' => 'h', 'ĝ' => 'g', 'đ' => 'd', 'ĵ' => 'j', - 'ÿ' => 'y', 'ũ' => 'u', 'ŭ' => 'u', 'ư' => 'u', 'ţ' => 't', 'ý' => 'y', 'ő' => 'o', - 'â' => 'a', 'ľ' => 'l', 'ẅ' => 'w', 'ż' => 'z', 'ī' => 'i', 'ã' => 'a', 'ġ' => 'g', - 'ṁ' => 'm', 'ō' => 'o', 'ĩ' => 'i', 'ù' => 'u', 'į' => 'i', 'ź' => 'z', 'á' => 'a', - 'û' => 'u', 'þ' => 'th', 'ð' => 'dh', 'æ' => 'ae', 'µ' => 'u', 'ĕ' => 'e', - ); - } - - $str = str_replace( - array_keys($UTF8_LOWER_ACCENTS), - array_values($UTF8_LOWER_ACCENTS), - $str - ); - } - - if ($case >= 0) - { - if ($UTF8_UPPER_ACCENTS === NULL) - { - $UTF8_UPPER_ACCENTS = array( - 'À' => 'A', 'Ô' => 'O', 'Ď' => 'D', 'Ḟ' => 'F', 'Ë' => 'E', 'Š' => 'S', 'Ơ' => 'O', - 'Ă' => 'A', 'Ř' => 'R', 'Ț' => 'T', 'Ň' => 'N', 'Ā' => 'A', 'Ķ' => 'K', 'Ĕ' => 'E', - 'Ŝ' => 'S', 'Ỳ' => 'Y', 'Ņ' => 'N', 'Ĺ' => 'L', 'Ħ' => 'H', 'Ṗ' => 'P', 'Ó' => 'O', - 'Ú' => 'U', 'Ě' => 'E', 'É' => 'E', 'Ç' => 'C', 'Ẁ' => 'W', 'Ċ' => 'C', 'Õ' => 'O', - 'Ṡ' => 'S', 'Ø' => 'O', 'Ģ' => 'G', 'Ŧ' => 'T', 'Ș' => 'S', 'Ė' => 'E', 'Ĉ' => 'C', - 'Ś' => 'S', 'Î' => 'I', 'Ű' => 'U', 'Ć' => 'C', 'Ę' => 'E', 'Ŵ' => 'W', 'Ṫ' => 'T', - 'Ū' => 'U', 'Č' => 'C', 'Ö' => 'O', 'È' => 'E', 'Ŷ' => 'Y', 'Ą' => 'A', 'Ł' => 'L', - 'Ų' => 'U', 'Ů' => 'U', 'Ş' => 'S', 'Ğ' => 'G', 'Ļ' => 'L', 'Ƒ' => 'F', 'Ž' => 'Z', - 'Ẃ' => 'W', 'Ḃ' => 'B', 'Å' => 'A', 'Ì' => 'I', 'Ï' => 'I', 'Ḋ' => 'D', 'Ť' => 'T', - 'Ŗ' => 'R', 'Ä' => 'A', 'Í' => 'I', 'Ŕ' => 'R', 'Ê' => 'E', 'Ü' => 'U', 'Ò' => 'O', - 'Ē' => 'E', 'Ñ' => 'N', 'Ń' => 'N', 'Ĥ' => 'H', 'Ĝ' => 'G', 'Đ' => 'D', 'Ĵ' => 'J', - 'Ÿ' => 'Y', 'Ũ' => 'U', 'Ŭ' => 'U', 'Ư' => 'U', 'Ţ' => 'T', 'Ý' => 'Y', 'Ő' => 'O', - 'Â' => 'A', 'Ľ' => 'L', 'Ẅ' => 'W', 'Ż' => 'Z', 'Ī' => 'I', 'Ã' => 'A', 'Ġ' => 'G', - 'Ṁ' => 'M', 'Ō' => 'O', 'Ĩ' => 'I', 'Ù' => 'U', 'Į' => 'I', 'Ź' => 'Z', 'Á' => 'A', - 'Û' => 'U', 'Þ' => 'Th', 'Ð' => 'Dh', 'Æ' => 'Ae', - ); - } - - $str = str_replace( - array_keys($UTF8_UPPER_ACCENTS), - array_values($UTF8_UPPER_ACCENTS), - $str - ); - } - - return $str; -}
\ No newline at end of file diff --git a/system/core/utf8/trim.php b/system/core/utf8/trim.php deleted file mode 100644 index 7434102a..00000000 --- a/system/core/utf8/trim.php +++ /dev/null @@ -1,17 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * utf8::trim - * - * @package Core - * @author Kohana Team - * @copyright (c) 2007 Kohana Team - * @copyright (c) 2005 Harry Fuecks - * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt - */ -function _trim($str, $charlist = NULL) -{ - if ($charlist === NULL) - return trim($str); - - return utf8::ltrim(utf8::rtrim($str, $charlist), $charlist); -}
\ No newline at end of file diff --git a/system/core/utf8/ucfirst.php b/system/core/utf8/ucfirst.php deleted file mode 100644 index 81a4b380..00000000 --- a/system/core/utf8/ucfirst.php +++ /dev/null @@ -1,18 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * utf8::ucfirst - * - * @package Core - * @author Kohana Team - * @copyright (c) 2007 Kohana Team - * @copyright (c) 2005 Harry Fuecks - * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt - */ -function _ucfirst($str) -{ - if (utf8::is_ascii($str)) - return ucfirst($str); - - preg_match('/^(.?)(.*)$/us', $str, $matches); - return utf8::strtoupper($matches[1]).$matches[2]; -}
\ No newline at end of file diff --git a/system/core/utf8/ucwords.php b/system/core/utf8/ucwords.php deleted file mode 100644 index 2d4c94b6..00000000 --- a/system/core/utf8/ucwords.php +++ /dev/null @@ -1,26 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * utf8::ucwords - * - * @package Core - * @author Kohana Team - * @copyright (c) 2007 Kohana Team - * @copyright (c) 2005 Harry Fuecks - * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt - */ -function _ucwords($str) -{ - if (SERVER_UTF8) - return mb_convert_case($str, MB_CASE_TITLE); - - if (utf8::is_ascii($str)) - return ucwords($str); - - // [\x0c\x09\x0b\x0a\x0d\x20] matches form feeds, horizontal tabs, vertical tabs, linefeeds and carriage returns. - // This corresponds to the definition of a 'word' defined at http://php.net/ucwords - return preg_replace( - '/(?<=^|[\x0c\x09\x0b\x0a\x0d\x20])[^\x0c\x09\x0b\x0a\x0d\x20]/ue', - 'utf8::strtoupper(\'$0\')', - $str - ); -}
\ No newline at end of file diff --git a/system/helpers/arr.php b/system/helpers/arr.php index 9570c4b5..a1bde230 100644 --- a/system/helpers/arr.php +++ b/system/helpers/arr.php @@ -2,12 +2,12 @@ /** * Array helper class. * - * $Id: arr.php 4346 2009-05-11 17:08:15Z zombor $ + * $Id: arr.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 arr_Core { @@ -102,20 +102,26 @@ class arr_Core { $found = array(); foreach ($keys as $key) { - if (isset($search[$key])) - { - $found[$key] = $search[$key]; - } - else - { - $found[$key] = NULL; - } + $found[$key] = isset($search[$key]) ? $search[$key] : NULL; } return $found; } /** + * Get the value of array[key]. If it doesn't exist, return default. + * + * @param array array to search + * @param string key name + * @param mixed default value + * @return mixed + */ + public static function get(array $array, $key, $default = NULL) + { + return isset($array[$key]) ? $array[$key] : $default; + } + + /** * Because PHP does not have this function. * * @param array array to unshift @@ -152,44 +158,6 @@ class arr_Core { } /** - * @param mixed $needle the value to search for - * @param array $haystack an array of values to search in - * @param boolean $sort sort the array now - * @return integer|FALSE the index of the match or FALSE when not found - */ - public static function binary_search($needle, $haystack, $sort = FALSE) - { - if ($sort) - { - sort($haystack); - } - - $high = count($haystack) - 1; - $low = 0; - - while ($low <= $high) - { - $mid = ($low + $high) >> 1; - - if ($haystack[$mid] < $needle) - { - $low = $mid + 1; - } - elseif ($haystack[$mid] > $needle) - { - $high = $mid - 1; - } - else - { - return $mid; - } - } - - return FALSE; - } - - - /** * Emulates array_merge_recursive, but appends numeric keys and replaces * associative keys, instead of appending all keys. * @@ -264,27 +232,6 @@ class arr_Core { } /** - * Fill an array with a range of numbers. - * - * @param integer stepping - * @param integer ending number - * @return array - */ - public static function range($step = 10, $max = 100) - { - if ($step < 1) - return array(); - - $array = array(); - for ($i = $step; $i <= $max; $i += $step) - { - $array[$i] = $i; - } - - return $array; - } - - /** * Recursively convert an array to an object. * * @param array array to convert @@ -309,4 +256,20 @@ class arr_Core { return $object; } + /** + * Returns specific key/column from an array of objects. + * + * @param string|integer $key The key or column number to pluck from each object. + * @param array $array The array of objects to pluck from. + * @return array + */ + public static function pluck($key, $array) + { + $result = array(); + foreach ($array as $i => $object) + { + $result[$i] = isset($object[$key]) ? $object[$key] : NULL; + } + return $result; + } } // End arr diff --git a/system/helpers/cookie.php b/system/helpers/cookie.php index 901b6d86..8a2e3659 100644 --- a/system/helpers/cookie.php +++ b/system/helpers/cookie.php @@ -2,12 +2,12 @@ /** * Cookie helper class. * - * $Id: cookie.php 3769 2008-12-15 00:48:56Z zombor $ + * $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 cookie_Core { @@ -42,8 +42,13 @@ class cookie_Core { } } - // Expiration timestamp - $expire = ($expire == 0) ? 0 : time() + (int) $expire; + if ($expire !== 0) + { + // The expiration is expected to be a UNIX timestamp + $expire += time(); + } + + $value = cookie::salt($name, $value).'~'.$value; return setcookie($name, $value, $expire, $path, $domain, $secure, $httponly); } @@ -56,9 +61,51 @@ class cookie_Core { * @param boolean use XSS cleaning on the value * @return string */ - public static function get($name, $default = NULL, $xss_clean = FALSE) + public static function get($name = NULL, $default = NULL, $xss_clean = FALSE) { - return Input::instance()->cookie($name, $default, $xss_clean); + // Return an array of all the cookies if we don't have a name + if ($name === NULL) + { + $cookies = array(); + + foreach($_COOKIE AS $key => $value) + { + $cookies[$key] = cookie::get($key, $default, $xss_clean); + } + return $cookies; + } + + if ( ! isset($_COOKIE[$name])) + { + return $default; + } + + // Get the cookie value + $cookie = $_COOKIE[$name]; + + // Find the position of the split between salt and contents + $split = strlen(cookie::salt($name, NULL)); + + if (isset($cookie[$split]) AND $cookie[$split] === '~') + { + // Separate the salt and the value + list ($hash, $value) = explode('~', $cookie, 2); + + if (cookie::salt($name, $value) === $hash) + { + if ($xss_clean === TRUE AND Kohana::config('core.global_xss_filtering') === FALSE) + { + return Input::instance()->xss_clean($value); + } + // Cookie signature is valid + return $value; + } + + // The cookie signature is invalid, delete it + cookie::delete($name); + } + + return $default; } /** @@ -71,9 +118,6 @@ class cookie_Core { */ public static function delete($name, $path = NULL, $domain = NULL) { - if ( ! isset($_COOKIE[$name])) - return FALSE; - // Delete the cookie from globals unset($_COOKIE[$name]); @@ -81,4 +125,27 @@ class cookie_Core { return cookie::set($name, '', -86400, $path, $domain, FALSE, FALSE); } + /** + * Generates a salt string for a cookie based on the name and value. + * + * @param string $name name of cookie + * @param string $value value of cookie + * @return string sha1 hash + */ + public static function salt($name, $value) + { + // Determine the user agent + $agent = isset($_SERVER['HTTP_USER_AGENT']) ? strtolower($_SERVER['HTTP_USER_AGENT']) : 'unknown'; + + // Cookie salt. + $salt = Kohana::config('cookie.salt'); + + return sha1($agent.$name.$value.$salt); + } + + final private function __construct() + { + // Static class. + } + } // End cookie
\ No newline at end of file diff --git a/system/helpers/date.php b/system/helpers/date.php index 7d5a9ab6..af7e85bd 100644 --- a/system/helpers/date.php +++ b/system/helpers/date.php @@ -2,12 +2,12 @@ /** * Date helper class. * - * $Id: date.php 4316 2009-05-04 01:03:54Z kiall $ + * $Id: date.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 date_Core { @@ -57,36 +57,28 @@ class date_Core { * Returns the offset (in seconds) between two time zones. * @see http://php.net/timezones * - * @param string timezone that to find the offset of + * @param string timezone to find the offset of * @param string|boolean timezone used as the baseline + * @param string time at which to calculate * @return integer */ - public static function offset($remote, $local = TRUE) + public static function offset($remote, $local = TRUE, $when = 'now') { - static $offsets; - - // Default values - $remote = (string) $remote; - $local = ($local === TRUE) ? date_default_timezone_get() : (string) $local; - - // Cache key name - $cache = $remote.$local; - - if (empty($offsets[$cache])) + if ($local === TRUE) { - // Create timezone objects - $remote = new DateTimeZone($remote); - $local = new DateTimeZone($local); + $local = date_default_timezone_get(); + } - // Create date objects from timezones - $time_there = new DateTime('now', $remote); - $time_here = new DateTime('now', $local); + // Create timezone objects + $remote = new DateTimeZone($remote); + $local = new DateTimeZone($local); - // Find the offset - $offsets[$cache] = $remote->getOffset($time_there) - $local->getOffset($time_here); - } + // Create date objects from timezones + $time_there = new DateTime($when, $remote); + $time_here = new DateTime($when, $local); - return $offsets[$cache]; + // Find the offset + return $remote->getOffset($time_there) - $local->getOffset($time_here); } /** diff --git a/system/helpers/db.php b/system/helpers/db.php new file mode 100644 index 00000000..ce7583b7 --- /dev/null +++ b/system/helpers/db.php @@ -0,0 +1,49 @@ +<?php defined('SYSPATH') or die('No direct script access.'); +/** + * Database helper class. + * + * $Id: $ + * + * @package Core + * @author Kohana Team + * @copyright (c) 2007-2009 Kohana Team + * @license http://kohanaphp.com/license + */ + class db_Core { + + public static function query($sql) + { + return new Database_Query($sql); + } + + public static function build($database = 'default') + { + return new Database_Builder($database); + } + + public static function select($columns = NULL) + { + return db::build()->select($columns); + } + + public static function insert($table = NULL, $set = NULL) + { + return db::build()->insert($table, $set); + } + + public static function update($table = NULL, $set = NULL, $where = NULL) + { + return db::build()->update($table, $set, $where); + } + + public static function delete($table = NULL, $where = NULL) + { + return db::build()->delete($table, $where); + } + + public static function expr($expression) + { + return new Database_Expression($expression); + } + +} // End db diff --git a/system/helpers/download.php b/system/helpers/download.php index 49fed42c..58a7ab94 100644 --- a/system/helpers/download.php +++ b/system/helpers/download.php @@ -2,104 +2,136 @@ /** * Download helper class. * - * $Id: download.php 3769 2008-12-15 00:48:56Z zombor $ + * $Id: download.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 download_Core { /** - * Force a download of a file to the user's browser. This function is - * binary-safe and will work with any MIME type that Kohana is aware of. + * Send headers necessary to invoke a "Save As" dialog + * + * @link http://support.microsoft.com/kb/260519 + * @link http://greenbytes.de/tech/tc2231/ + * + * @param string file name + * @return string file name as it was sent + */ + public static function dialog($filename) + { + $filename = basename($filename); + + header('Content-Disposition: attachment; filename="'.$filename.'"'); + + return $filename; + } + + /** + * Send the contents of a file or a data string with the proper MIME type and exit. + * + * @uses exit() + * @uses Kohana::close_buffers() * * @param string a file path or file name - * @param mixed data to be sent if the filename does not exist - * @param string suggested filename to display in the download + * @param string optional data to send * @return void */ - public static function force($filename = NULL, $data = NULL, $nicename = NULL) + public static function send($filename, $data = NULL) { - if (empty($filename)) - return FALSE; - - if (is_file($filename)) + if ($data === NULL) { - // Get the real path - $filepath = str_replace('\\', '/', realpath($filename)); + $filepath = realpath($filename); - // Set filesize + $filename = basename($filepath); $filesize = filesize($filepath); - - // Get filename - $filename = substr(strrchr('/'.$filepath, '/'), 1); - - // Get extension - $extension = strtolower(substr(strrchr($filepath, '.'), 1)); } else { - // Get filesize + $filename = basename($filename); $filesize = strlen($data); + } - // Make sure the filename does not have directory info - $filename = substr(strrchr('/'.$filename, '/'), 1); + // Retrieve MIME type by extension + $mime = Kohana::config('mimes.'.strtolower(substr(strrchr($filename, '.'), 1))); + $mime = empty($mime) ? 'application/octet-stream' : $mime[0]; - // Get extension - $extension = strtolower(substr(strrchr($filename, '.'), 1)); - } + // Close output buffers + Kohana::close_buffers(FALSE); + + // Clear any output + Event::add('system.display', create_function('', 'Kohana::$output = "";')); - // Get the mime type of the file - $mime = Kohana::config('mimes.'.$extension); + // Send headers + header("Content-Type: $mime"); + header('Content-Length: '.sprintf('%d', $filesize)); + header('Content-Transfer-Encoding: binary'); - if (empty($mime)) + // Send data + if ($data === NULL) { - // Set a default mime if none was found - $mime = array('application/octet-stream'); + $handle = fopen($filepath, 'rb'); + + fpassthru($handle); + fclose($handle); + } + else + { + echo $data; } - // Generate the server headers - header('Content-Type: '.$mime[0]); - header('Content-Disposition: attachment; filename="'.(empty($nicename) ? $filename : $nicename).'"'); - header('Content-Transfer-Encoding: binary'); - header('Content-Length: '.sprintf('%d', $filesize)); + exit; + } + + /** + * Force the download of a file by the user's browser by preventing any + * caching. Contains a workaround for Internet Explorer. + * + * @link http://support.microsoft.com/kb/316431 + * @link http://support.microsoft.com/kb/812935 + * + * @uses download::dialog() + * @uses download::send() + * + * @param string a file path or file name + * @param mixed data to be sent if the filename does not exist + * @param string suggested filename to display in the download + * @return void + */ + public static function force($filename = NULL, $data = NULL, $nicename = NULL) + { + download::dialog(empty($nicename) ? $filename : $nicename); - // More caching prevention - header('Expires: 0'); + // Prevent caching + header('Expires: Thu, 01 Jan 1970 00:00:00 GMT'); - if (Kohana::user_agent('browser') === 'Internet Explorer') + if (request::user_agent('browser') === 'Internet Explorer' AND request::user_agent('version') <= '6.0') { - // Send IE headers + // HTTP 1.0 + header('Pragma:'); + + // HTTP 1.1 with IE extensions header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); - header('Pragma: public'); } else { - // Send normal headers + // HTTP 1.0 header('Pragma: no-cache'); - } - // Clear the output buffer - Kohana::close_buffers(FALSE); + // HTTP 1.1 + header('Cache-Control: no-cache, max-age=0'); + } - if (isset($filepath)) + if (is_file($filename)) { - // Open the file - $handle = fopen($filepath, 'rb'); - - // Send the file data - fpassthru($handle); - - // Close the file - fclose($handle); + download::send($filename); } else { - // Send the file data - echo $data; + download::send($filename, $data); } } -} // End download
\ No newline at end of file +} // End download diff --git a/system/helpers/email.php b/system/helpers/email.php deleted file mode 100644 index fb222d0c..00000000 --- a/system/helpers/email.php +++ /dev/null @@ -1,181 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * Email helper class. - * - * $Id: email.php 3769 2008-12-15 00:48:56Z zombor $ - * - * @package Core - * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html - */ -class email_Core { - - // SwiftMailer instance - protected static $mail; - - /** - * Creates a SwiftMailer instance. - * - * @param string DSN connection string - * @return object Swift object - */ - public static function connect($config = NULL) - { - if ( ! class_exists('Swift', FALSE)) - { - // Load SwiftMailer - require Kohana::find_file('vendor', 'swift/Swift'); - - // Register the Swift ClassLoader as an autoload - spl_autoload_register(array('Swift_ClassLoader', 'load')); - } - - // Load default configuration - ($config === NULL) and $config = Kohana::config('email'); - - switch ($config['driver']) - { - case 'smtp': - // Set port - $port = empty($config['options']['port']) ? NULL : (int) $config['options']['port']; - - if (empty($config['options']['encryption'])) - { - // No encryption - $encryption = Swift_Connection_SMTP::ENC_OFF; - } - else - { - // Set encryption - switch (strtolower($config['options']['encryption'])) - { - case 'tls': $encryption = Swift_Connection_SMTP::ENC_TLS; break; - case 'ssl': $encryption = Swift_Connection_SMTP::ENC_SSL; break; - } - } - - // Create a SMTP connection - $connection = new Swift_Connection_SMTP($config['options']['hostname'], $port, $encryption); - - // Do authentication, if part of the DSN - empty($config['options']['username']) or $connection->setUsername($config['options']['username']); - empty($config['options']['password']) or $connection->setPassword($config['options']['password']); - - if ( ! empty($config['options']['auth'])) - { - // Get the class name and params - list ($class, $params) = arr::callback_string($config['options']['auth']); - - if ($class === 'PopB4Smtp') - { - // Load the PopB4Smtp class manually, due to its odd filename - require Kohana::find_file('vendor', 'swift/Swift/Authenticator/$PopB4Smtp$'); - } - - // Prepare the class name for auto-loading - $class = 'Swift_Authenticator_'.$class; - - // Attach the authenticator - $connection->attachAuthenticator(($params === NULL) ? new $class : new $class($params[0])); - } - - // Set the timeout to 5 seconds - $connection->setTimeout(empty($config['options']['timeout']) ? 5 : (int) $config['options']['timeout']); - break; - case 'sendmail': - // Create a sendmail connection - $connection = new Swift_Connection_Sendmail - ( - empty($config['options']) ? Swift_Connection_Sendmail::AUTO_DETECT : $config['options'] - ); - - // Set the timeout to 5 seconds - $connection->setTimeout(5); - break; - default: - // Use the native connection - $connection = new Swift_Connection_NativeMail($config['options']); - break; - } - - // Create the SwiftMailer instance - return email::$mail = new Swift($connection); - } - - /** - * Send an email message. - * - * @param string|array recipient email (and name), or an array of To, Cc, Bcc names - * @param string|array sender email (and name) - * @param string message subject - * @param string message body - * @param boolean send email as HTML - * @return integer number of emails sent - */ - public static function send($to, $from, $subject, $message, $html = FALSE) - { - // Connect to SwiftMailer - (email::$mail === NULL) and email::connect(); - - // Determine the message type - $html = ($html === TRUE) ? 'text/html' : 'text/plain'; - - // Create the message - $message = new Swift_Message($subject, $message, $html, '8bit', 'utf-8'); - - if (is_string($to)) - { - // Single recipient - $recipients = new Swift_Address($to); - } - elseif (is_array($to)) - { - if (isset($to[0]) AND isset($to[1])) - { - // Create To: address set - $to = array('to' => $to); - } - - // Create a list of recipients - $recipients = new Swift_RecipientList; - - foreach ($to as $method => $set) - { - if ( ! in_array($method, array('to', 'cc', 'bcc'))) - { - // Use To: by default - $method = 'to'; - } - - // Create method name - $method = 'add'.ucfirst($method); - - if (is_array($set)) - { - // Add a recipient with name - $recipients->$method($set[0], $set[1]); - } - else - { - // Add a recipient without name - $recipients->$method($set); - } - } - } - - if (is_string($from)) - { - // From without a name - $from = new Swift_Address($from); - } - elseif (is_array($from)) - { - // From with a name - $from = new Swift_Address($from[0], $from[1]); - } - - return email::$mail->send($message, $recipients, $from); - } - -} // End email
\ No newline at end of file diff --git a/system/helpers/expires.php b/system/helpers/expires.php index c43cc0cc..ce0482c8 100644 --- a/system/helpers/expires.php +++ b/system/helpers/expires.php @@ -2,54 +2,49 @@ /** * Controls headers that effect client caching of pages * - * $Id: expires.php 4272 2009-04-25 21:47:26Z zombor $ + * $Id: expires.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 expires_Core { /** - * Sets the amount of time before a page expires + * Sets the amount of time before content expires * - * @param integer Seconds before the page expires - * @return boolean + * @param integer Seconds before the content expires + * @return integer Timestamp when the content expires */ public static function set($seconds = 60) { - if (expires::check_headers()) - { - $now = $expires = time(); + $now = time(); + $expires = $now + $seconds; - // Set the expiration timestamp - $expires += $seconds; + header('Last-Modified: '.gmdate('D, d M Y H:i:s T', $now)); - // Send headers - header('Last-Modified: '.gmdate('D, d M Y H:i:s T', $now)); - header('Expires: '.gmdate('D, d M Y H:i:s T', $expires)); - header('Cache-Control: max-age='.$seconds); + // HTTP 1.0 + header('Expires: '.gmdate('D, d M Y H:i:s T', $expires)); - return $expires; - } + // HTTP 1.1 + header('Cache-Control: max-age='.$seconds); - return FALSE; + return $expires; } /** - * Checks to see if a page should be updated or send Not Modified status + * Parses the If-Modified-Since header * - * @param integer Seconds added to the modified time received to calculate what should be sent - * @return bool FALSE when the request needs to be updated + * @return integer|boolean Timestamp or FALSE when header is lacking or malformed */ - public static function check($seconds = 60) + public static function get() { - if ( ! empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) AND expires::check_headers()) + if ( ! empty($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { + // Some versions of IE6 append "; length=####" if (($strpos = strpos($_SERVER['HTTP_IF_MODIFIED_SINCE'], ';')) !== FALSE) { - // IE6 and perhaps other IE versions send length too, compensate here $mod_time = substr($_SERVER['HTTP_IF_MODIFIED_SINCE'], 0, $strpos); } else @@ -57,55 +52,69 @@ class expires_Core { $mod_time = $_SERVER['HTTP_IF_MODIFIED_SINCE']; } - $mod_time = strtotime($mod_time); - $mod_time_diff = $mod_time + $seconds - time(); + return strtotime($mod_time); + } + + return FALSE; + } - if ($mod_time_diff > 0) + /** + * Checks to see if content should be updated otherwise sends Not Modified status + * and exits. + * + * @uses exit() + * @uses expires::get() + * + * @param integer Maximum age of the content in seconds + * @return integer|boolean Timestamp of the If-Modified-Since header or FALSE when header is lacking or malformed + */ + public static function check($seconds = 60) + { + if ($last_modified = expires::get()) + { + $expires = $last_modified + $seconds; + $max_age = $expires - time(); + + if ($max_age > 0) { - // Re-send headers - header('Last-Modified: '.gmdate('D, d M Y H:i:s T', $mod_time)); - header('Expires: '.gmdate('D, d M Y H:i:s T', time() + $mod_time_diff)); - header('Cache-Control: max-age='.$mod_time_diff); - header('Status: 304 Not Modified', TRUE, 304); + // Content has not expired + header($_SERVER['SERVER_PROTOCOL'].' 304 Not Modified'); + header('Last-Modified: '.gmdate('D, d M Y H:i:s T', $last_modified)); + + // HTTP 1.0 + header('Expires: '.gmdate('D, d M Y H:i:s T', $expires)); - // Prevent any output - Event::add('system.display', array('expires', 'prevent_output')); + // HTTP 1.1 + header('Cache-Control: max-age='.$max_age); + + // Clear any output + Event::add('system.display', create_function('', 'Kohana::$output = "";')); - // Exit to prevent other output exit; } } - return FALSE; + return $last_modified; } /** - * Check headers already created to not step on download or Img_lib's feet + * Check if expiration headers are already set * * @return boolean */ - public static function check_headers() + public static function headers_set() { foreach (headers_list() as $header) { - if ((session_cache_limiter() == '' AND stripos($header, 'Last-Modified:') === 0) - OR stripos($header, 'Expires:') === 0) + if (strncasecmp($header, 'Expires:', 8) === 0 + OR strncasecmp($header, 'Cache-Control:', 14) === 0 + OR strncasecmp($header, 'Last-Modified:', 14) === 0) { - return FALSE; + return TRUE; } } - return TRUE; - } - - /** - * Prevent any output from being displayed. Executed during system.display. - * - * @return void - */ - public static function prevent_output() - { - Kohana::$output = ''; + return FALSE; } -} // End expires
\ No newline at end of file +} // End expires diff --git a/system/helpers/feed.php b/system/helpers/feed.php index 74bb2f6b..4aab1dcd 100644 --- a/system/helpers/feed.php +++ b/system/helpers/feed.php @@ -2,12 +2,12 @@ /** * Feed helper class. * - * $Id: feed.php 4152 2009-04-03 23:26:23Z ixmatus $ + * $Id: feed.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 feed_Core { diff --git a/system/helpers/file.php b/system/helpers/file.php index b1b71740..0d4a7980 100644 --- a/system/helpers/file.php +++ b/system/helpers/file.php @@ -2,12 +2,12 @@ /** * File helper class. * - * $Id: file.php 3769 2008-12-15 00:48:56Z zombor $ + * $Id: file.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 file_Core { diff --git a/system/helpers/form.php b/system/helpers/form.php index 815eef84..901edc91 100644 --- a/system/helpers/form.php +++ b/system/helpers/form.php @@ -2,12 +2,12 @@ /** * Form helper class. * - * $Id: form.php 4291 2009-04-29 22:51:58Z kiall $ + * $Id: form.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 form_Core { @@ -17,9 +17,10 @@ class form_Core { * @param string form action attribute * @param array extra attributes * @param array hidden fields to be created immediately after the form tag + * @param string non-default protocol, eg: https * @return string */ - public static function open($action = NULL, $attr = array(), $hidden = NULL) + public static function open($action = NULL, $attr = array(), $hidden = NULL, $protocol = NULL) { // Make sure that the method is always set empty($attr['method']) and $attr['method'] = 'post'; @@ -33,12 +34,12 @@ class form_Core { if ($action === NULL) { // Use the current URL as the default action - $action = url::site(Router::$complete_uri); + $action = url::site(Router::$complete_uri, $protocol); } elseif (strpos($action, '://') === FALSE) { // Make the action URI into a URL - $action = url::site($action); + $action = url::site($action, $protocol); } // Set action @@ -70,72 +71,23 @@ class form_Core { } /** - * Generates a fieldset opening tag. + * Creates a HTML form hidden input tag. * - * @param array html attributes - * @param string a string to be attached to the end of the attributes - * @return string - */ - public static function open_fieldset($data = NULL, $extra = '') - { - return '<fieldset'.html::attributes((array) $data).' '.$extra.'>'."\n"; - } - - /** - * Generates a fieldset closing tag. - * - * @return string - */ - public static function close_fieldset() - { - return '</fieldset>'."\n"; - } - - /** - * Generates a legend tag for use with a fieldset. - * - * @param string legend text - * @param array HTML attributes - * @param string a string to be attached to the end of the attributes - * @return string - */ - public static function legend($text = '', $data = NULL, $extra = '') - { - return '<legend'.form::attributes((array) $data).' '.$extra.'>'.$text.'</legend>'."\n"; - } - - /** - * Generates hidden form fields. - * You can pass a simple key/value string or an associative array with multiple values. - * - * @param string|array input name (string) or key/value pairs (array) - * @param string input value, if using an input name + * @param string|array input name or an array of HTML attributes + * @param string input value, when using a name + * @param string a string to be attached to the end of the attributes * @return string */ - public static function hidden($data, $value = '') + public static function hidden($data, $value = '', $extra = '') { if ( ! is_array($data)) { - $data = array - ( - $data => $value - ); + $data = array('name' => $data); } - $input = ''; - foreach ($data as $name => $value) - { - $attr = array - ( - 'type' => 'hidden', - 'name' => $name, - 'value' => $value - ); - - $input .= form::input($attr)."\n"; - } + $data['type'] = 'hidden'; - return $input; + return form::input($data, $value, $extra); } /** @@ -219,13 +171,23 @@ class form_Core { $data = array('name' => $data); } + if ( ! isset($data['rows'])) + { + $data['rows'] = ''; + } + + if ( ! isset($data['cols'])) + { + $data['cols'] = ''; + } + // Use the value from $data if possible, or use $value $value = isset($data['value']) ? $data['value'] : $value; // Value is not part of the attributes unset($data['value']); - return '<textarea'.form::attributes($data, 'textarea').' '.$extra.'>'.html::specialchars($value, $double_encode).'</textarea>'; + return '<textarea'.form::attributes($data, 'textarea').' '.$extra.'>'.htmlspecialchars($value, ENT_QUOTES, Kohana::CHARSET, $double_encode).'</textarea>'; } /** @@ -283,21 +245,15 @@ class form_Core { // Inner key should always be a string $inner_key = (string) $inner_key; - $attr = array('value' => $inner_key); - if (in_array($inner_key, $selected)) { - $attr['selected'] = 'selected'; - } - $input .= '<option '.html::attributes($attr).'>'.html::purify($inner_val).'</option>'."\n"; + $sel = in_array($inner_key, $selected) ? ' selected="selected"' : ''; + $input .= '<option value="'.$inner_key.'"'.$sel.'>'.htmlspecialchars($inner_val, ENT_QUOTES, Kohana::CHARSET).'</option>'."\n"; } $input .= '</optgroup>'."\n"; } else { - $attr = array('value' => $key); - if (in_array($key, $selected)) { - $attr['selected'] = 'selected'; - } - $input .= '<option '.html::attributes($attr).'>'.html::purify($val).'</option>'."\n"; + $sel = in_array($key, $selected) ? ' selected="selected"' : ''; + $input .= '<option value="'.$key.'"'.$sel.'>'.htmlspecialchars($val, ENT_QUOTES, Kohana::CHARSET).'</option>'."\n"; } } $input .= '</select>'; @@ -416,20 +372,8 @@ class form_Core { { $value = arr::remove('value', $data); } - // $value must be ::purify - - return '<button'.form::attributes($data, 'button').' '.$extra.'>'.html::purify($value).'</button>'; - } - /** - * Closes an open form tag. - * - * @param string string to be attached after the closing tag - * @return string - */ - public static function close($extra = '') - { - return '</form>'."\n".$extra; + return '<button'.form::attributes($data, 'button').' '.$extra.'>'.$value.'</button>'; } /** @@ -462,7 +406,7 @@ class form_Core { $text = ucwords(inflector::humanize($data['for'])); } - return '<label'.form::attributes($data).' '.$extra.'>'.html::purify($text).'</label>'; + return '<label'.form::attributes($data).' '.$extra.'>'.$text.'</label>'; } /** @@ -496,6 +440,7 @@ class form_Core { case 'image': case 'button': case 'submit': + case 'hidden': // Only specific types of inputs use name to id matching $attr['id'] = $attr['name']; break; @@ -546,4 +491,4 @@ class form_Core { return html::attributes(array_merge($sorted, $attr)); } -} // End form
\ No newline at end of file +} // End form diff --git a/system/helpers/format.php b/system/helpers/format.php index fb8a0294..9351afda 100644 --- a/system/helpers/format.php +++ b/system/helpers/format.php @@ -2,16 +2,33 @@ /** * Format helper class. * - * $Id: format.php 4070 2009-03-11 20:37:38Z Geert $ + * $Id: format.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 format_Core { /** + * Formats a number according to the current locale. + * + * @param float + * @param int|boolean number of fractional digits or TRUE to use the locale default + * @return string + */ + public static function number($number, $decimals = 0) + { + $locale = localeconv(); + + if ($decimals === TRUE) + return number_format($number, $locale['frac_digits'], $locale['decimal_point'], $locale['thousands_sep']); + + return number_format($number, $decimals, $locale['decimal_point'], $locale['thousands_sep']); + } + + /** * Formats a phone number according to the specified format. * * @param string phone number @@ -63,4 +80,35 @@ class format_Core { return $str; } + /** + * Normalizes a hexadecimal HTML color value. All values will be converted + * to lowercase, have a "#" prepended and contain six characters. + * + * @param string hexadecimal HTML color value + * @return string + */ + public static function color($str = '') + { + // Reject invalid values + if ( ! valid::color($str)) + return ''; + + // Convert to lowercase + $str = strtolower($str); + + // Prepend "#" + if ($str[0] !== '#') + { + $str = '#'.$str; + } + + // Expand short notation + if (strlen($str) === 4) + { + $str = '#'.$str[1].$str[1].$str[2].$str[2].$str[3].$str[3]; + } + + return $str; + } + } // End format
\ No newline at end of file diff --git a/system/helpers/html.php b/system/helpers/html.php index 2c609567..2d759ac0 100644 --- a/system/helpers/html.php +++ b/system/helpers/html.php @@ -2,12 +2,12 @@ /** * HTML helper class. * - * $Id: html.php 4376 2009-06-01 11:40:39Z samsoir $ + * $Id: html.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 html_Core { @@ -21,47 +21,13 @@ class html_Core { * @param boolean encode existing entities * @return string */ - public static function specialchars($str, $double_encode = TRUE) + public static function chars($str, $double_encode = TRUE) { - // Force the string to be a string - $str = (string) $str; - - // Do encode existing HTML entities (default) - if ($double_encode === TRUE) - { - $str = htmlspecialchars($str, ENT_QUOTES, 'UTF-8'); - } - else - { - // Do not encode existing HTML entities - // From PHP 5.2.3 this functionality is built-in, otherwise use a regex - if (version_compare(PHP_VERSION, '5.2.3', '>=')) - { - $str = htmlspecialchars($str, ENT_QUOTES, 'UTF-8', FALSE); - } - else - { - $str = preg_replace('/&(?!(?:#\d++|[a-z]++);)/ui', '&', $str); - $str = str_replace(array('<', '>', '\'', '"'), array('<', '>', ''', '"'), $str); - } - } - - return $str; + // Return HTML entities using the Kohana charset + return htmlspecialchars($str, ENT_QUOTES, Kohana::CHARSET, $double_encode); } /** - * Perform a html::specialchars() with additional URL specific encoding. - * - * @param string string to convert - * @param boolean encode existing entities - * @return string - */ - public static function specialurlencode($str, $double_encode = TRUE) - { - return str_replace(' ', '%20', html::specialchars($str, $double_encode)); - } - - /** * Create HTML link anchors. * * @param string URL or URI string @@ -98,11 +64,11 @@ class html_Core { return // Parsed URL - '<a href="'.html::specialurlencode($site_url, FALSE).'"' + '<a href="'.htmlspecialchars($site_url, ENT_QUOTES, Kohana::CHARSET, FALSE).'"' // Attributes empty? Use an empty string .(is_array($attributes) ? html::attributes($attributes) : '').'>' // Title empty? Use the parsed URL - .($escape_title ? html::specialchars((($title === NULL) ? $site_url : $title), FALSE) : (($title === NULL) ? $site_url : $title)).'</a>'; + .($escape_title ? htmlspecialchars((($title === NULL) ? $site_url : $title), ENT_QUOTES, Kohana::CHARSET, FALSE) : (($title === NULL) ? $site_url : $title)).'</a>'; } /** @@ -118,7 +84,7 @@ class html_Core { { return // Base URL + URI = full URL - '<a href="'.html::specialurlencode(url::base(FALSE, $protocol).$file, FALSE).'"' + '<a href="'.htmlspecialchars(url::base(FALSE, $protocol).$file, ENT_QUOTES, Kohana::CHARSET, FALSE).'"' // Attributes empty? Use an empty string .(is_array($attributes) ? html::attributes($attributes) : '').'>' // Title empty? Use the filename part of the URI @@ -126,37 +92,6 @@ class html_Core { } /** - * Similar to anchor, but with the protocol parameter first. - * - * @param string link protocol - * @param string URI or URL to link to - * @param string link text - * @param array HTML anchor attributes - * @return string - */ - public static function panchor($protocol, $uri, $title = NULL, $attributes = FALSE) - { - return html::anchor($uri, $title, $attributes, $protocol); - } - - /** - * Create an array of anchors from an array of link/title pairs. - * - * @param array link/title pairs - * @return array - */ - public static function anchor_array(array $array) - { - $anchors = array(); - foreach ($array as $link => $title) - { - // Create list of anchors - $anchors[] = html::anchor($link, $title); - } - return $anchors; - } - - /** * Generates an obfuscated version of an email address. * * @param string email address @@ -286,7 +221,7 @@ class html_Core { */ public static function stylesheet($style, $media = FALSE, $index = FALSE) { - return html::link($style, 'stylesheet', 'text/css', '.css', $media, $index); + return html::link($style, 'stylesheet', 'text/css', $media, $index); } /** @@ -295,12 +230,11 @@ class html_Core { * @param string|array filename * @param string|array relationship * @param string|array mimetype - * @param string specifies suffix of the file * @param string|array specifies on what device the document will be displayed * @param boolean include the index_page in the link * @return string */ - public static function link($href, $rel, $type, $suffix = FALSE, $media = FALSE, $index = FALSE) + public static function link($href, $rel, $type, $media = FALSE, $index = FALSE) { $compiled = ''; @@ -312,7 +246,7 @@ class html_Core { $_type = is_array($type) ? array_shift($type) : $type; $_media = is_array($media) ? array_shift($media) : $media; - $compiled .= html::link($_href, $_rel, $_type, $suffix, $_media, $index); + $compiled .= html::link($_href, $_rel, $_type, $_media, $index); } } else @@ -323,14 +257,6 @@ class html_Core { $href = url::base($index).$href; } - $length = strlen($suffix); - - if ( $length > 0 AND substr_compare($href, $suffix, -$length, $length, FALSE) !== 0) - { - // Add the defined suffix - $href .= $suffix; - } - $attr = array ( 'rel' => $rel, @@ -376,12 +302,6 @@ class html_Core { $script = url::base((bool) $index).$script; } - if (substr_compare($script, '.js', -3, 3, FALSE) !== 0) - { - // Add the javascript suffix - $script .= '.js'; - } - $compiled = '<script type="text/javascript" src="'.$script.'"></script>'; } @@ -437,7 +357,7 @@ class html_Core { $compiled = ''; foreach ($attrs as $key => $val) { - $compiled .= ' '.$key.'="'.html::specialchars($val).'"'; + $compiled .= ' '.$key.'="'.htmlspecialchars($val, ENT_QUOTES, Kohana::CHARSET).'"'; } return $compiled; diff --git a/system/helpers/inflector.php b/system/helpers/inflector.php index 1e4fee23..9bd281db 100644 --- a/system/helpers/inflector.php +++ b/system/helpers/inflector.php @@ -2,12 +2,12 @@ /** * Inflector helper class. * - * $Id: inflector.php 4072 2009-03-13 17:20:38Z jheathco $ + * $Id: inflector.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 inflector_Core { @@ -45,7 +45,27 @@ class inflector_Core { * @param integer number of things * @return string */ - public static function singular($str, $count = NULL) + public static function singular($str, $count = NULL) { + $parts = explode('_', $str); + + $last = inflector::_singular(array_pop($parts), $count); + + $pre = implode('_', $parts); + if (strlen($pre)) + $pre .= '_'; + + return $pre.$last; + } + + + /** + * Makes a plural word singular. + * + * @param string word to singularize + * @param integer number of things + * @return string + */ + public static function _singular($str, $count = NULL) { // Remove garbage $str = strtolower(trim($str)); @@ -104,6 +124,29 @@ class inflector_Core { */ public static function plural($str, $count = NULL) { + if ( ! $str) + return $str; + + $parts = explode('_', $str); + + $last = inflector::_plural(array_pop($parts), $count); + + $pre = implode('_', $parts); + if (strlen($pre)) + $pre .= '_'; + + return $pre.$last; + } + + + /** + * Makes a singular word plural. + * + * @param string word to pluralize + * @return string + */ + public static function _plural($str, $count = NULL) + { // Remove garbage $str = strtolower(trim($str)); @@ -155,6 +198,24 @@ class inflector_Core { } /** + * Makes a word possessive. + * + * @param string word to to make possessive + * @return string + */ + public static function possessive($string) + { + $length = strlen($string); + + if (substr($string, $length - 1, $length) == 's') + { + return $string.'\''; + } + + return $string.'\'s'; + } + + /** * Makes a phrase camel case. * * @param string phrase to camelize @@ -176,7 +237,7 @@ class inflector_Core { */ public static function underscore($str) { - return preg_replace('/\s+/', '_', trim($str)); + return trim(preg_replace('/[\s_]+/', '_', $str), '_'); } /** @@ -187,7 +248,7 @@ class inflector_Core { */ public static function humanize($str) { - return preg_replace('/[_-]+/', ' ', trim($str)); + return trim(preg_replace('/[_-\s]+/', ' ', $str)); } } // End inflector
\ No newline at end of file diff --git a/system/helpers/num.php b/system/helpers/num.php index 3eb5d5ac..42f13bec 100644 --- a/system/helpers/num.php +++ b/system/helpers/num.php @@ -2,12 +2,12 @@ /** * Number helper class. * - * $Id: num.php 3769 2008-12-15 00:48:56Z zombor $ + * $Id: num.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 num_Core { diff --git a/system/helpers/remote.php b/system/helpers/remote.php index f9e0267f..37995cdb 100644 --- a/system/helpers/remote.php +++ b/system/helpers/remote.php @@ -2,12 +2,12 @@ /** * Remote url/file helper. * - * $Id: remote.php 3769 2008-12-15 00:48:56Z zombor $ + * $Id: remote.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 remote_Core { @@ -35,7 +35,7 @@ class remote_Core { $CRLF = "\r\n"; // Send request - fwrite($remote, 'HEAD '.$url['path'].' HTTP/1.0'.$CRLF); + fwrite($remote, 'HEAD '.$url['path'].(isset($url['query']) ? '?'.$url['query'] : '').' HTTP/1.0'.$CRLF); fwrite($remote, 'Host: '.$url['host'].$CRLF); fwrite($remote, 'Connection: close'.$CRLF); fwrite($remote, 'User-Agent: Kohana Framework (+http://kohanaphp.com/)'.$CRLF); @@ -63,4 +63,4 @@ class remote_Core { return isset($response) ? $response : FALSE; } -} // End remote
\ No newline at end of file +} // End remote diff --git a/system/helpers/request.php b/system/helpers/request.php index 4203d0e5..4770d64b 100644 --- a/system/helpers/request.php +++ b/system/helpers/request.php @@ -2,35 +2,48 @@ /** * Request helper class. * - * $Id: request.php 4355 2009-05-15 17:18:28Z kiall $ + * $Id: request.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 request_Core { // Possible HTTP methods protected static $http_methods = array('get', 'head', 'options', 'post', 'put', 'delete'); - // Content types from client's HTTP Accept request header (array) + // Character sets from client's HTTP Accept-Charset request header + protected static $accept_charsets; + + // Content codings from client's HTTP Accept-Encoding request header + protected static $accept_encodings; + + // Language tags from client's HTTP Accept-Language request header + protected static $accept_languages; + + // Content types from client's HTTP Accept request header protected static $accept_types; + // The current user agent and its parsed attributes + protected static $user_agent; + /** * Returns the HTTP referrer, or the default if the referrer is not set. * * @param mixed default to return + * @param bool Remove base URL * @return string */ - public static function referrer($default = FALSE) + public static function referrer($default = FALSE, $remove_base = FALSE) { if ( ! empty($_SERVER['HTTP_REFERER'])) { // Set referrer $ref = $_SERVER['HTTP_REFERER']; - if (strpos($ref, url::base(FALSE)) === 0) + if ($remove_base === TRUE AND (strpos($ref, url::base(FALSE)) === 0)) { // Remove the base URL from the referrer $ref = substr($ref, strlen(url::base(FALSE))); @@ -84,10 +97,59 @@ class request_Core { $method = strtolower($_SERVER['REQUEST_METHOD']); if ( ! in_array($method, request::$http_methods)) - throw new Kohana_Exception('request.unknown_method', $method); + throw new Kohana_Exception('Invalid request method :method:', array(':method:' => $method)); return $method; - } + } + + /** + * Retrieves current user agent information + * keys: browser, version, platform, mobile, robot + * + * @param string key + * @return mixed NULL or the parsed value + */ + public static function user_agent($key = 'agent') + { + // Retrieve raw user agent without parsing + if ($key === 'agent') + { + if (request::$user_agent === NULL) + return request::$user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? trim($_SERVER['HTTP_USER_AGENT']) : ''; + + if (is_array(request::$user_agent)) + return request::$user_agent['agent']; + + return request::$user_agent; + } + + if ( ! is_array(request::$user_agent)) + { + request::$user_agent['agent'] = isset($_SERVER['HTTP_USER_AGENT']) ? trim($_SERVER['HTTP_USER_AGENT']) : ''; + + // Parse the user agent and extract basic information + foreach (Kohana::config('user_agents') as $type => $data) + { + foreach ($data as $fragment => $name) + { + if (stripos(request::$user_agent['agent'], $fragment) !== FALSE) + { + if ($type === 'browser' AND preg_match('|'.preg_quote($fragment).'[^0-9.]*+([0-9.][0-9.a-z]*)|i', request::$user_agent['agent'], $match)) + { + // Set the browser version + request::$user_agent['version'] = $match[1]; + } + + // Set the agent name + request::$user_agent[$type] = $name; + break; + } + } + } + } + + return isset(request::$user_agent[$key]) ? request::$user_agent[$key] : NULL; + } /** * Returns boolean of whether client accepts content type. @@ -98,7 +160,7 @@ class request_Core { */ public static function accepts($type = NULL, $explicit_check = FALSE) { - request::parse_accept_header(); + request::parse_accept_content_header(); if ($type === NULL) return request::$accept_types; @@ -107,6 +169,56 @@ class request_Core { } /** + * Returns boolean indicating if the client accepts a charset + * + * @param string + * @return boolean + */ + public static function accepts_charset($charset = NULL) + { + request::parse_accept_charset_header(); + + if ($charset === NULL) + return request::$accept_charsets; + + return (request::accepts_charset_at_quality($charset) > 0); + } + + /** + * Returns boolean indicating if the client accepts an encoding + * + * @param string + * @param boolean set to TRUE to disable wildcard checking + * @return boolean + */ + public static function accepts_encoding($encoding = NULL, $explicit_check = FALSE) + { + request::parse_accept_encoding_header(); + + if ($encoding === NULL) + return request::$accept_encodings; + + return (request::accepts_encoding_at_quality($encoding, $explicit_check) > 0); + } + + /** + * Returns boolean indicating if the client accepts a language tag + * + * @param string language tag + * @param boolean set to TRUE to disable prefix and wildcard checking + * @return boolean + */ + public static function accepts_language($tag = NULL, $explicit_check = FALSE) + { + request::parse_accept_language_header(); + + if ($tag === NULL) + return request::$accept_languages; + + return (request::accepts_language_at_quality($tag, $explicit_check) > 0); + } + + /** * Compare the q values for given array of content types and return the one with the highest value. * If items are found to have the same q value, the first one encountered in the given array wins. * If all items in the given array have a q value of 0, FALSE is returned. @@ -117,24 +229,103 @@ class request_Core { */ public static function preferred_accept($types, $explicit_check = FALSE) { - // Initialize - $mime_types = array(); $max_q = 0; $preferred = FALSE; - // Load q values for all given content types - foreach (array_unique($types) as $type) + foreach ($types as $type) + { + $q = request::accepts_at_quality($type, $explicit_check); + + if ($q > $max_q) + { + $max_q = $q; + $preferred = $type; + } + } + + return $preferred; + } + + /** + * Compare the q values for a given array of character sets and return the + * one with the highest value. If items are found to have the same q value, + * the first one encountered takes precedence. If all items in the given + * array have a q value of 0, FALSE is returned. + * + * @param array character sets + * @return mixed + */ + public static function preferred_charset($charsets) + { + $max_q = 0; + $preferred = FALSE; + + foreach ($charsets as $charset) + { + $q = request::accepts_charset_at_quality($charset); + + if ($q > $max_q) + { + $max_q = $q; + $preferred = $charset; + } + } + + return $preferred; + } + + /** + * Compare the q values for a given array of encodings and return the one with + * the highest value. If items are found to have the same q value, the first + * one encountered takes precedence. If all items in the given array have + * a q value of 0, FALSE is returned. + * + * @param array encodings + * @param boolean set to TRUE to disable wildcard checking + * @return mixed + */ + public static function preferred_encoding($encodings, $explicit_check = FALSE) + { + $max_q = 0; + $preferred = FALSE; + + foreach ($encodings as $encoding) { - $mime_types[$type] = request::accepts_at_quality($type, $explicit_check); + $q = request::accepts_encoding_at_quality($encoding, $explicit_check); + + if ($q > $max_q) + { + $max_q = $q; + $preferred = $encoding; + } } - // Look for the highest q value - foreach ($mime_types as $type => $q) + return $preferred; + } + + /** + * Compare the q values for a given array of language tags and return the + * one with the highest value. If items are found to have the same q value, + * the first one encountered takes precedence. If all items in the given + * array have a q value of 0, FALSE is returned. + * + * @param array language tags + * @param boolean set to TRUE to disable prefix and wildcard checking + * @return mixed + */ + public static function preferred_language($tags, $explicit_check = FALSE) + { + $max_q = 0; + $preferred = FALSE; + + foreach ($tags as $tag) { + $q = request::accepts_language_at_quality($tag, $explicit_check); + if ($q > $max_q) { $max_q = $q; - $preferred = $type; + $preferred = $tag; } } @@ -142,18 +333,18 @@ class request_Core { } /** - * Returns quality factor at which the client accepts content type. + * Returns quality factor at which the client accepts content type * * @param string content type (e.g. "image/jpg", "jpg") * @param boolean set to TRUE to disable wildcard checking * @return integer|float */ - public static function accepts_at_quality($type = NULL, $explicit_check = FALSE) + public static function accepts_at_quality($type, $explicit_check = FALSE) { - request::parse_accept_header(); + request::parse_accept_content_header(); // Normalize type - $type = strtolower((string) $type); + $type = strtolower($type); // General content type (e.g. "jpg") if (strpos($type, '/') === FALSE) @@ -178,62 +369,251 @@ class request_Core { if (isset(request::$accept_types[$type[0]][$type[1]])) return request::$accept_types[$type[0]][$type[1]]; - // Wildcard match (if not checking explicitly) - if ($explicit_check === FALSE AND isset(request::$accept_types[$type[0]]['*'])) - return request::$accept_types[$type[0]]['*']; + if ($explicit_check === FALSE) + { + // Wildcard match + if (isset(request::$accept_types[$type[0]]['*'])) + return request::$accept_types[$type[0]]['*']; - // Catch-all wildcard match (if not checking explicitly) - if ($explicit_check === FALSE AND isset(request::$accept_types['*']['*'])) - return request::$accept_types['*']['*']; + // Catch-all wildcard match + if (isset(request::$accept_types['*']['*'])) + return request::$accept_types['*']['*']; + } // Content type not accepted return 0; } /** - * Parses client's HTTP Accept request header, and builds array structure representing it. + * Returns quality factor at which the client accepts a charset + * + * @param string charset (e.g., "ISO-8859-1", "utf-8") + * @return integer|float + */ + public static function accepts_charset_at_quality($charset) + { + request::parse_accept_charset_header(); + + // Normalize charset + $charset = strtolower($charset); + + // Exact match + if (isset(request::$accept_charsets[$charset])) + return request::$accept_charsets[$charset]; + + if (isset(request::$accept_charsets['*'])) + return request::$accept_charsets['*']; + + if ($charset === 'iso-8859-1') + return 1; + + return 0; + } + + /** + * Returns quality factor at which the client accepts an encoding + * + * @param string encoding (e.g., "gzip", "deflate") + * @param boolean set to TRUE to disable wildcard checking + * @return integer|float + */ + public static function accepts_encoding_at_quality($encoding, $explicit_check = FALSE) + { + request::parse_accept_encoding_header(); + + // Normalize encoding + $encoding = strtolower($encoding); + + // Exact match + if (isset(request::$accept_encodings[$encoding])) + return request::$accept_encodings[$encoding]; + + if ($explicit_check === FALSE) + { + if (isset(request::$accept_encodings['*'])) + return request::$accept_encodings['*']; + + if ($encoding === 'identity') + return 1; + } + + return 0; + } + + /** + * Returns quality factor at which the client accepts a language + * + * @param string tag (e.g., "en", "en-us", "fr-ca") + * @param boolean set to TRUE to disable prefix and wildcard checking + * @return integer|float + */ + public static function accepts_language_at_quality($tag, $explicit_check = FALSE) + { + request::parse_accept_language_header(); + + $tag = explode('-', strtolower($tag), 2); + + if (isset(request::$accept_languages[$tag[0]])) + { + if (isset($tag[1])) + { + // Exact match + if (isset(request::$accept_languages[$tag[0]][$tag[1]])) + return request::$accept_languages[$tag[0]][$tag[1]]; + + // A prefix matches + if ($explicit_check === FALSE AND isset(request::$accept_languages[$tag[0]]['*'])) + return request::$accept_languages[$tag[0]]['*']; + } + else + { + // No subtags + if (isset(request::$accept_languages[$tag[0]]['*'])) + return request::$accept_languages[$tag[0]]['*']; + } + } + + if ($explicit_check === FALSE AND isset(request::$accept_languages['*'])) + return request::$accept_languages['*']; + + return 0; + } + + /** + * Parses a HTTP Accept or Accept-* header for q values * - * @return void + * @param string header data + * @return array + */ + protected static function parse_accept_header($header) + { + $result = array(); + + // Remove linebreaks and parse the HTTP Accept header + foreach (explode(',', str_replace(array("\r", "\n"), '', strtolower($header))) as $entry) + { + // Explode each entry in content type and possible quality factor + $entry = explode(';', trim($entry), 2); + + $q = (isset($entry[1]) AND preg_match('~\bq\s*+=\s*+([.0-9]+)~', $entry[1], $match)) ? (float) $match[1] : 1; + + // Overwrite entries with a smaller q value + if ( ! isset($result[$entry[0]]) OR $q > $result[$entry[0]]) + { + $result[$entry[0]] = $q; + } + } + + return $result; + } + + /** + * Parses a client's HTTP Accept-Charset header */ - protected static function parse_accept_header() + protected static function parse_accept_charset_header() { // Run this function just once - if (request::$accept_types !== NULL) + if (request::$accept_charsets !== NULL) return; - // Initialize accept_types array - request::$accept_types = array(); + // No HTTP Accept-Charset header found + if (empty($_SERVER['HTTP_ACCEPT_CHARSET'])) + { + // Accept everything + request::$accept_charsets['*'] = 1; + } + else + { + request::$accept_charsets = request::parse_accept_header($_SERVER['HTTP_ACCEPT_CHARSET']); + } + } + + /** + * Parses a client's HTTP Accept header + */ + protected static function parse_accept_content_header() + { + // Run this function just once + if (request::$accept_types !== NULL) + return; // No HTTP Accept header found if (empty($_SERVER['HTTP_ACCEPT'])) { // Accept everything request::$accept_types['*']['*'] = 1; - return; } - - // Remove linebreaks and parse the HTTP Accept header - foreach (explode(',', str_replace(array("\r", "\n"), '', $_SERVER['HTTP_ACCEPT'])) as $accept_entry) + else { - // Explode each entry in content type and possible quality factor - $accept_entry = explode(';', trim($accept_entry), 2); + request::$accept_types = array(); + + foreach (request::parse_accept_header($_SERVER['HTTP_ACCEPT']) as $type => $q) + { + // Explode each content type (e.g. "text/html") + $type = explode('/', $type, 2); + + // Skip invalid content types + if ( ! isset($type[1])) + continue; + + request::$accept_types[$type[0]][$type[1]] = $q; + } + } + } + + /** + * Parses a client's HTTP Accept-Encoding header + */ + protected static function parse_accept_encoding_header() + { + // Run this function just once + if (request::$accept_encodings !== NULL) + return; - // Explode each content type (e.g. "text/html") - $type = explode('/', $accept_entry[0], 2); + // No HTTP Accept-Encoding header found + if ( ! isset($_SERVER['HTTP_ACCEPT_ENCODING'])) + { + // Accept everything + request::$accept_encodings['*'] = 1; + } + elseif ($_SERVER['HTTP_ACCEPT_ENCODING'] === '') + { + // Accept only identity + request::$accept_encodings['identity'] = 1; + } + else + { + request::$accept_encodings = request::parse_accept_header($_SERVER['HTTP_ACCEPT_ENCODING']); + } + } - // Skip invalid content types - if ( ! isset($type[1])) - continue; + /** + * Parses a client's HTTP Accept-Language header + */ + protected static function parse_accept_language_header() + { + // Run this function just once + if (request::$accept_languages !== NULL) + return; - // Assume a default quality factor of 1 if no custom q value found - $q = (isset($accept_entry[1]) AND preg_match('~\bq\s*+=\s*+([.0-9]+)~', $accept_entry[1], $match)) ? (float) $match[1] : 1; + // No HTTP Accept-Language header found + if (empty($_SERVER['HTTP_ACCEPT_LANGUAGE'])) + { + // Accept everything + request::$accept_languages['*'] = 1; + } + else + { + request::$accept_languages = array(); - // Populate accept_types array - if ( ! isset(request::$accept_types[$type[0]][$type[1]]) OR $q > request::$accept_types[$type[0]][$type[1]]) + foreach (request::parse_accept_header($_SERVER['HTTP_ACCEPT_LANGUAGE']) as $tag => $q) { - request::$accept_types[$type[0]][$type[1]] = $q; + // Explode each language (e.g. "en-us") + $tag = explode('-', $tag, 2); + + request::$accept_languages[$tag[0]][isset($tag[1]) ? $tag[1] : '*'] = $q; } } } -} // End request
\ No newline at end of file +} // End request diff --git a/system/helpers/security.php b/system/helpers/security.php index cd48d2e0..9eb82a58 100644 --- a/system/helpers/security.php +++ b/system/helpers/security.php @@ -2,12 +2,12 @@ /** * Security helper class. * - * $Id: security.php 3769 2008-12-15 00:48:56Z zombor $ + * $Id: security.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 security_Core { @@ -15,11 +15,12 @@ class security_Core { * Sanitize a string with the xss_clean method. * * @param string string to sanitize + * @param string xss_clean method to use ('htmlpurifier' or defaults to built-in method) * @return string */ - public static function xss_clean($str) + public static function xss_clean($str, $tool = NULL) { - return Input::instance()->xss_clean($str); + return Input::instance()->xss_clean($str, $tool); } /** diff --git a/system/helpers/text.php b/system/helpers/text.php index d0e573ec..66bcd243 100644 --- a/system/helpers/text.php +++ b/system/helpers/text.php @@ -2,12 +2,12 @@ /** * Text helper class. * - * $Id: text.php 3769 2008-12-15 00:48:56Z zombor $ + * $Id: text.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 text_Core { @@ -52,7 +52,7 @@ class text_Core { $limit = (int) $limit; - if (trim($str) === '' OR utf8::strlen($str) <= $limit) + if (trim($str) === '' OR mb_strlen($str) <= $limit) return $str; if ($limit <= 0) @@ -60,7 +60,7 @@ class text_Core { if ($preserve_words == FALSE) { - return rtrim(utf8::substr($str, 0, $limit)).$end_char; + return rtrim(mb_substr($str, 0, $limit)).$end_char; } preg_match('/^.{'.($limit - 1).'}\S*/us', $str, $matches); @@ -128,7 +128,7 @@ class text_Core { break; default: $pool = (string) $type; - $utf8 = ! utf8::is_ascii($pool); + $utf8 = ! text::is_ascii($pool); break; } @@ -183,7 +183,7 @@ class text_Core { * @param boolean replace words across word boundries (space, period, etc) * @return string */ - public static function censor($str, $badwords, $replacement = '#', $replace_partial_words = FALSE) + public static function censor($str, $badwords, $replacement = '#', $replace_partial_words = TRUE) { foreach ((array) $badwords as $key => $badword) { @@ -192,7 +192,7 @@ class text_Core { $regex = '('.implode('|', $badwords).')'; - if ($replace_partial_words == TRUE) + if ($replace_partial_words === FALSE) { // Just using \b isn't sufficient when we need to replace a badword that already contains word boundaries itself $regex = '(?<=\b|\s|^)'.$regex.'(?=\b|\s|$)'; @@ -200,10 +200,10 @@ class text_Core { $regex = '!'.$regex.'!ui'; - if (utf8::strlen($replacement) == 1) + if (mb_strlen($replacement) == 1) { $regex .= 'e'; - return preg_replace($regex, 'str_repeat($replacement, utf8::strlen(\'$1\'))', $str); + return preg_replace($regex, 'str_repeat($replacement, mb_strlen(\'$1\'))', $str); } return preg_replace($regex, $replacement, $str); @@ -235,15 +235,59 @@ class text_Core { } /** - * Converts text email addresses and anchors into links. + * An alternative to the php levenshtein() function that work out the + * distance between 2 words using the Damerau–Levenshtein algorithm. + * Credit: http://forums.devnetwork.net/viewtopic.php?f=50&t=89094 * - * @param string text to auto link - * @return string + * @see http://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance + * @param string first word + * @param string second word + * @return int distance between words */ - public static function auto_link($text) + public static function distance($string1, $string2) { - // Auto link emails first to prevent problems with "www.domain.com@example.com" - return text::auto_link_urls(text::auto_link_emails($text)); + $string1_length = strlen($string1); + $string2_length = strlen($string2); + + // Here we start building the table of values + $matrix = array(); + + // String1 length + 1 = rows. + for ($i = 0; $i <= $string1_length; ++$i) + { + $matrix[$i][0] = $i; + } + + // String2 length + 1 columns. + for ($j = 0; $j <= $string2_length; ++$j) + { + $matrix[0][$j] = $j; + } + + for ($i = 1; $i <= $string1_length; ++$i) + { + for ($j = 1; $j <= $string2_length; ++$j) + { + $cost = substr($string1, $i - 1, 1) == substr($string2, $j - 1, 1) ? 0 : 1; + + $matrix[$i][$j] = min( + $matrix[$i - 1][$j] + 1, // deletion + $matrix[$i][$j - 1] + 1, // insertion + $matrix[$i - 1][$j - 1] + $cost // substitution + ); + + if ($i > 1 && $j > 1 && (substr($string1, $i - 1, 1) == substr($string2, $j - 2, 1)) + && (substr($string1, $i - 2, 1) == substr($string2, $j - 1, 1))) + { + $matrix[$i][$j] = min( + $matrix[$i][$j], + $matrix[$i - 2][$j - 2] + $cost // transposition + ); + } + } + } + + return $matrix[$string1_length][$string2_length]; } /** @@ -304,9 +348,10 @@ class text_Core { * Automatically applies <p> and <br /> markup to text. Basically nl2br() on steroids. * * @param string subject + * @param boolean convert single linebreaks to <br /> * @return string */ - public static function auto_p($str) + public static function auto_p($str, $br = TRUE) { // Trim whitespace if (($str = trim($str)) === '') @@ -343,7 +388,10 @@ class text_Core { } // Convert single linebreaks to <br /> - $str = preg_replace('~(?<!\n)\n(?!\n)~', "<br />\n", $str); + if ($br === TRUE) + { + $str = preg_replace('~(?<!\n)\n(?!\n)~', "<br />\n", $str); + } return $str; } @@ -368,13 +416,13 @@ class text_Core { // IEC prefixes (binary) if ($si == FALSE OR strpos($force_unit, 'i') !== FALSE) { - $units = array('B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB'); + $units = array(__('B'), __('KiB'), __('MiB'), __('GiB'), __('TiB'), __('PiB')); $mod = 1024; } // SI prefixes (decimal) else { - $units = array('B', 'kB', 'MB', 'GB', 'TB', 'PB'); + $units = array(__('B'), __('kB'), __('MB'), __('GB'), __('TB'), __('PB')); $mod = 1000; } @@ -407,4 +455,134 @@ class text_Core { return $str; } + /** + * Tests whether a string contains only 7bit ASCII bytes. This is used to + * determine when to use native functions or UTF-8 functions. + * + * @see http://sourceforge.net/projects/phputf8/ + * @copyright (c) 2007-2009 Kohana Team + * @copyright (c) 2005 Harry Fuecks + * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt + * + * @param string string to check + * @return bool + */ + public static function is_ascii($str) + { + return is_string($str) AND ! preg_match('/[^\x00-\x7F]/S', $str); + } + + /** + * Strips out device control codes in the ASCII range. + * + * @see http://sourceforge.net/projects/phputf8/ + * @copyright (c) 2007-2009 Kohana Team + * @copyright (c) 2005 Harry Fuecks + * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt + * + * @param string string to clean + * @return string + */ + public static function strip_ascii_ctrl($str) + { + return preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S', '', $str); + } + + /** + * Strips out all non-7bit ASCII bytes. + * + * @see http://sourceforge.net/projects/phputf8/ + * @copyright (c) 2007-2009 Kohana Team + * @copyright (c) 2005 Harry Fuecks + * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt + * + * @param string string to clean + * @return string + */ + public static function strip_non_ascii($str) + { + return preg_replace('/[^\x00-\x7F]+/S', '', $str); + } + + /** + * Replaces special/accented UTF-8 characters by ASCII-7 'equivalents'. + * + * @author Andreas Gohr <andi@splitbrain.org> + * @see http://sourceforge.net/projects/phputf8/ + * @copyright (c) 2007-2009 Kohana Team + * @copyright (c) 2005 Harry Fuecks + * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt + * + * @param string string to transliterate + * @param integer -1 lowercase only, +1 uppercase only, 0 both cases + * @return string + */ + public static function transliterate_to_ascii($str, $case = 0) + { + static $UTF8_LOWER_ACCENTS = NULL; + static $UTF8_UPPER_ACCENTS = NULL; + + if ($case <= 0) + { + if ($UTF8_LOWER_ACCENTS === NULL) + { + $UTF8_LOWER_ACCENTS = array( + 'à' => 'a', 'ô' => 'o', 'ď' => 'd', 'ḟ' => 'f', 'ë' => 'e', 'š' => 's', 'ơ' => 'o', + 'ß' => 'ss', 'ă' => 'a', 'ř' => 'r', 'ț' => 't', 'ň' => 'n', 'ā' => 'a', 'ķ' => 'k', + 'ŝ' => 's', 'ỳ' => 'y', 'ņ' => 'n', 'ĺ' => 'l', 'ħ' => 'h', 'ṗ' => 'p', 'ó' => 'o', + 'ú' => 'u', 'ě' => 'e', 'é' => 'e', 'ç' => 'c', 'ẁ' => 'w', 'ċ' => 'c', 'õ' => 'o', + 'ṡ' => 's', 'ø' => 'o', 'ģ' => 'g', 'ŧ' => 't', 'ș' => 's', 'ė' => 'e', 'ĉ' => 'c', + 'ś' => 's', 'î' => 'i', 'ű' => 'u', 'ć' => 'c', 'ę' => 'e', 'ŵ' => 'w', 'ṫ' => 't', + 'ū' => 'u', 'č' => 'c', 'ö' => 'o', 'è' => 'e', 'ŷ' => 'y', 'ą' => 'a', 'ł' => 'l', + 'ų' => 'u', 'ů' => 'u', 'ş' => 's', 'ğ' => 'g', 'ļ' => 'l', 'ƒ' => 'f', 'ž' => 'z', + 'ẃ' => 'w', 'ḃ' => 'b', 'å' => 'a', 'ì' => 'i', 'ï' => 'i', 'ḋ' => 'd', 'ť' => 't', + 'ŗ' => 'r', 'ä' => 'a', 'í' => 'i', 'ŕ' => 'r', 'ê' => 'e', 'ü' => 'u', 'ò' => 'o', + 'ē' => 'e', 'ñ' => 'n', 'ń' => 'n', 'ĥ' => 'h', 'ĝ' => 'g', 'đ' => 'd', 'ĵ' => 'j', + 'ÿ' => 'y', 'ũ' => 'u', 'ŭ' => 'u', 'ư' => 'u', 'ţ' => 't', 'ý' => 'y', 'ő' => 'o', + 'â' => 'a', 'ľ' => 'l', 'ẅ' => 'w', 'ż' => 'z', 'ī' => 'i', 'ã' => 'a', 'ġ' => 'g', + 'ṁ' => 'm', 'ō' => 'o', 'ĩ' => 'i', 'ù' => 'u', 'į' => 'i', 'ź' => 'z', 'á' => 'a', + 'û' => 'u', 'þ' => 'th', 'ð' => 'dh', 'æ' => 'ae', 'µ' => 'u', 'ĕ' => 'e', 'ı' => 'i', + ); + } + + $str = str_replace( + array_keys($UTF8_LOWER_ACCENTS), + array_values($UTF8_LOWER_ACCENTS), + $str + ); + } + + if ($case >= 0) + { + if ($UTF8_UPPER_ACCENTS === NULL) + { + $UTF8_UPPER_ACCENTS = array( + 'À' => 'A', 'Ô' => 'O', 'Ď' => 'D', 'Ḟ' => 'F', 'Ë' => 'E', 'Š' => 'S', 'Ơ' => 'O', + 'Ă' => 'A', 'Ř' => 'R', 'Ț' => 'T', 'Ň' => 'N', 'Ā' => 'A', 'Ķ' => 'K', 'Ĕ' => 'E', + 'Ŝ' => 'S', 'Ỳ' => 'Y', 'Ņ' => 'N', 'Ĺ' => 'L', 'Ħ' => 'H', 'Ṗ' => 'P', 'Ó' => 'O', + 'Ú' => 'U', 'Ě' => 'E', 'É' => 'E', 'Ç' => 'C', 'Ẁ' => 'W', 'Ċ' => 'C', 'Õ' => 'O', + 'Ṡ' => 'S', 'Ø' => 'O', 'Ģ' => 'G', 'Ŧ' => 'T', 'Ș' => 'S', 'Ė' => 'E', 'Ĉ' => 'C', + 'Ś' => 'S', 'Î' => 'I', 'Ű' => 'U', 'Ć' => 'C', 'Ę' => 'E', 'Ŵ' => 'W', 'Ṫ' => 'T', + 'Ū' => 'U', 'Č' => 'C', 'Ö' => 'O', 'È' => 'E', 'Ŷ' => 'Y', 'Ą' => 'A', 'Ł' => 'L', + 'Ų' => 'U', 'Ů' => 'U', 'Ş' => 'S', 'Ğ' => 'G', 'Ļ' => 'L', 'Ƒ' => 'F', 'Ž' => 'Z', + 'Ẃ' => 'W', 'Ḃ' => 'B', 'Å' => 'A', 'Ì' => 'I', 'Ï' => 'I', 'Ḋ' => 'D', 'Ť' => 'T', + 'Ŗ' => 'R', 'Ä' => 'A', 'Í' => 'I', 'Ŕ' => 'R', 'Ê' => 'E', 'Ü' => 'U', 'Ò' => 'O', + 'Ē' => 'E', 'Ñ' => 'N', 'Ń' => 'N', 'Ĥ' => 'H', 'Ĝ' => 'G', 'Đ' => 'D', 'Ĵ' => 'J', + 'Ÿ' => 'Y', 'Ũ' => 'U', 'Ŭ' => 'U', 'Ư' => 'U', 'Ţ' => 'T', 'Ý' => 'Y', 'Ő' => 'O', + 'Â' => 'A', 'Ľ' => 'L', 'Ẅ' => 'W', 'Ż' => 'Z', 'Ī' => 'I', 'Ã' => 'A', 'Ġ' => 'G', + 'Ṁ' => 'M', 'Ō' => 'O', 'Ĩ' => 'I', 'Ù' => 'U', 'Į' => 'I', 'Ź' => 'Z', 'Á' => 'A', + 'Û' => 'U', 'Þ' => 'Th', 'Ð' => 'Dh', 'Æ' => 'Ae', 'İ' => 'I', + ); + } + + $str = str_replace( + array_keys($UTF8_UPPER_ACCENTS), + array_values($UTF8_UPPER_ACCENTS), + $str + ); + } + + return $str; + } + } // End text
\ No newline at end of file diff --git a/system/helpers/upload.php b/system/helpers/upload.php index 422e9e8d..3aec2ac4 100644 --- a/system/helpers/upload.php +++ b/system/helpers/upload.php @@ -3,12 +3,12 @@ * Upload helper class for working with the global $_FILES * array and Validation library. * - * $Id: upload.php 3769 2008-12-15 00:48:56Z zombor $ + * $Id: upload.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 upload_Core { @@ -54,7 +54,7 @@ class upload_Core { } if ( ! is_writable($directory)) - throw new Kohana_Exception('upload.not_writable', $directory); + throw new Kohana_Exception('The upload destination folder, :dir:, does not appear to be writable.', array(':dir:' => $directory)); if (is_uploaded_file($file['tmp_name']) AND move_uploaded_file($file['tmp_name'], $filename = $directory.$filename)) { @@ -118,11 +118,8 @@ class upload_Core { // Get the default extension of the file $extension = strtolower(substr(strrchr($file['name'], '.'), 1)); - // Get the mime types for the extension - $mime_types = Kohana::config('mimes.'.$extension); - - // Make sure there is an extension, that the extension is allowed, and that mime types exist - return ( ! empty($extension) AND in_array($extension, $allowed_types) AND is_array($mime_types)); + // Make sure there is an extension and that the extension is allowed + return ( ! empty($extension) AND in_array($extension, $allowed_types)); } /** diff --git a/system/helpers/url.php b/system/helpers/url.php index 56f6db4b..6c2a6c66 100644 --- a/system/helpers/url.php +++ b/system/helpers/url.php @@ -2,12 +2,12 @@ /** * URL helper class. * - * $Id: url.php 4479 2009-07-23 04:51:22Z ixmatus $ + * $Id: url.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 url_Core { @@ -15,7 +15,7 @@ class url_Core { * Fetches the current URI. * * @param boolean include the query string - * @param boolean include the suffix + * @param boolean include the suffix * @return string */ public static function current($qs = FALSE, $suffix = FALSE) @@ -167,7 +167,7 @@ class url_Core { $separator = ($separator === '-') ? '-' : '_'; // Replace accented characters by their unaccented equivalents - $title = utf8::transliterate_to_ascii($title); + $title = text::transliterate_to_ascii($title); // Remove all characters that are not the separator, a-z, 0-9, or whitespace $title = preg_replace('/[^'.$separator.'a-z0-9\s]+/', '', strtolower($title)); diff --git a/system/helpers/utf8.php b/system/helpers/utf8.php new file mode 100644 index 00000000..20c6878c --- /dev/null +++ b/system/helpers/utf8.php @@ -0,0 +1,746 @@ +<?php defined('SYSPATH') OR die('No direct access allowed.'); +/** + * A port of phputf8 to a unified file/class. + * + * This file is licensed differently from the rest of Kohana. As a port of + * phputf8, which is LGPL software, this file is released under the LGPL. + * + * PCRE needs to be compiled with UTF-8 support (--enable-utf8). + * Support for Unicode properties is highly recommended (--enable-unicode-properties). + * @see http://php.net/manual/reference.pcre.pattern.modifiers.php + * + * string functions. + * @see http://php.net/mbstring + * + * $Id$ + * + * @package Core + * @author Kohana Team + * @copyright (c) 2007-2009 Kohana Team + * @copyright (c) 2005 Harry Fuecks + * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt + */ + +class utf8 { + + /** + * Replaces text within a portion of a UTF-8 string. + * @see http://php.net/substr_replace + * + * @author Harry Fuecks <hfuecks@gmail.com> + * + * @param string input string + * @param string replacement string + * @param integer offset + * @return string + */ + public static function substr_replace($str, $replacement, $offset, $length = NULL) + { + if (text::is_ascii($str)) + return ($length === NULL) ? substr_replace($str, $replacement, $offset) : substr_replace($str, $replacement, $offset, $length); + + $length = ($length === NULL) ? mb_strlen($str) : (int) $length; + preg_match_all('/./us', $str, $str_array); + preg_match_all('/./us', $replacement, $replacement_array); + + array_splice($str_array[0], $offset, $length, $replacement_array[0]); + return implode('', $str_array[0]); + } + + /** + * Makes a UTF-8 string's first character uppercase. + * @see http://php.net/ucfirst + * + * @author Harry Fuecks <hfuecks@gmail.com> + * + * @param string mixed case string + * @return string + */ + public static function ucfirst($str) + { + if (text::is_ascii($str)) + return ucfirst($str); + + preg_match('/^(.?)(.*)$/us', $str, $matches); + return mb_strtoupper($matches[1]).$matches[2]; + } + + /** + * Case-insensitive UTF-8 string comparison. + * @see http://php.net/strcasecmp + * + * @author Harry Fuecks <hfuecks@gmail.com> + * + * @param string string to compare + * @param string string to compare + * @return integer less than 0 if str1 is less than str2 + * @return integer greater than 0 if str1 is greater than str2 + * @return integer 0 if they are equal + */ + public static function strcasecmp($str1, $str2) + { + if (text::is_ascii($str1) AND text::is_ascii($str2)) + return strcasecmp($str1, $str2); + + $str1 = mb_strtolower($str1); + $str2 = mb_strtolower($str2); + return strcmp($str1, $str2); + } + + /** + * Returns a string or an array with all occurrences of search in subject (ignoring case). + * replaced with the given replace value. + * @see http://php.net/str_ireplace + * + * @note It's not fast and gets slower if $search and/or $replace are arrays. + * @author Harry Fuecks <hfuecks@gmail.com + * + * @param string|array text to replace + * @param string|array replacement text + * @param string|array subject text + * @param integer number of matched and replaced needles will be returned via this parameter which is passed by reference + * @return string if the input was a string + * @return array if the input was an array + */ + public static function str_ireplace($search, $replace, $str, & $count = NULL) + { + if (text::is_ascii($search) AND text::is_ascii($replace) AND text::is_ascii($str)) + return str_ireplace($search, $replace, $str, $count); + + if (is_array($str)) + { + foreach ($str as $key => $val) + { + $str[$key] = utf8::str_ireplace($search, $replace, $val, $count); + } + return $str; + } + + if (is_array($search)) + { + $keys = array_keys($search); + + foreach ($keys as $k) + { + if (is_array($replace)) + { + if (array_key_exists($k, $replace)) + { + $str = utf8::str_ireplace($search[$k], $replace[$k], $str, $count); + } + else + { + $str = utf8::str_ireplace($search[$k], '', $str, $count); + } + } + else + { + $str = utf8::str_ireplace($search[$k], $replace, $str, $count); + } + } + return $str; + } + + $search = mb_strtolower($search); + $str_lower = mb_strtolower($str); + + $total_matched_strlen = 0; + $i = 0; + + while (preg_match('/(.*?)'.preg_quote($search, '/').'/s', $str_lower, $matches)) + { + $matched_strlen = strlen($matches[0]); + $str_lower = substr($str_lower, $matched_strlen); + + $offset = $total_matched_strlen + strlen($matches[1]) + ($i * (strlen($replace) - 1)); + $str = substr_replace($str, $replace, $offset, strlen($search)); + + $total_matched_strlen += $matched_strlen; + $i++; + } + + $count += $i; + return $str; + } + + /** + * Case-insenstive UTF-8 version of strstr. Returns all of input string + * from the first occurrence of needle to the end. + * @see http://php.net/stristr + * + * @author Harry Fuecks <hfuecks@gmail.com> + * + * @param string input string + * @param string needle + * @return string matched substring if found + * @return boolean FALSE if the substring was not found + */ + public static function stristr($str, $search) + { + if (text::is_ascii($str) AND text::is_ascii($search)) + return stristr($str, $search); + + if ($search == '') + return $str; + + $str_lower = mb_strtolower($str); + $search_lower = mb_strtolower($search); + + preg_match('/^(.*?)'.preg_quote($search, '/').'/s', $str_lower, $matches); + + if (isset($matches[1])) + return substr($str, strlen($matches[1])); + + return FALSE; + } + + /** + * Finds the length of the initial segment matching mask. + * @see http://php.net/strspn + * + * @author Harry Fuecks <hfuecks@gmail.com> + * + * @param string input string + * @param string mask for search + * @param integer start position of the string to examine + * @param integer length of the string to examine + * @return integer length of the initial segment that contains characters in the mask + */ + public static function strspn($str, $mask, $offset = NULL, $length = NULL) + { + if ($str == '' OR $mask == '') + return 0; + + if (text::is_ascii($str) AND text::is_ascii($mask)) + return ($offset === NULL) ? strspn($str, $mask) : (($length === NULL) ? strspn($str, $mask, $offset) : strspn($str, $mask, $offset, $length)); + + if ($offset !== NULL OR $length !== NULL) + { + $str = mb_substr($str, $offset, $length); + } + + // Escape these characters: - [ ] . : \ ^ / + // The . and : are escaped to prevent possible warnings about POSIX regex elements + $mask = preg_replace('#[-[\].:\\\\^/]#', '\\\\$0', $mask); + preg_match('/^[^'.$mask.']+/u', $str, $matches); + + return isset($matches[0]) ? mb_strlen($matches[0]) : 0; + } + + /** + * Finds the length of the initial segment not matching mask. + * @see http://php.net/strcspn + * + * @author Harry Fuecks <hfuecks@gmail.com> + * + * @param string input string + * @param string mask for search + * @param integer start position of the string to examine + * @param integer length of the string to examine + * @return integer length of the initial segment that contains characters not in the mask + */ + public static function strcspn($str, $mask, $offset = NULL, $length = NULL) + { + if ($str == '' OR $mask == '') + return 0; + + if (text::is_ascii($str) AND text::is_ascii($mask)) + return ($offset === NULL) ? strcspn($str, $mask) : (($length === NULL) ? strcspn($str, $mask, $offset) : strcspn($str, $mask, $offset, $length)); + + if ($str !== NULL OR $length !== NULL) + { + $str = mb_substr($str, $offset, $length); + } + + // Escape these characters: - [ ] . : \ ^ / + // The . and : are escaped to prevent possible warnings about POSIX regex elements + $mask = preg_replace('#[-[\].:\\\\^/]#', '\\\\$0', $mask); + preg_match('/^[^'.$mask.']+/u', $str, $matches); + + return isset($matches[0]) ? mb_strlen($matches[0]) : 0; + } + + /** + * Pads a UTF-8 string to a certain length with another string. + * @see http://php.net/str_pad + * + * @author Harry Fuecks <hfuecks@gmail.com> + * + * @param string input string + * @param integer desired string length after padding + * @param string string to use as padding + * @param string padding type: STR_PAD_RIGHT, STR_PAD_LEFT, or STR_PAD_BOTH + * @return string + */ + public static function str_pad($str, $final_str_length, $pad_str = ' ', $pad_type = STR_PAD_RIGHT) + { + if (text::is_ascii($str) AND text::is_ascii($pad_str)) + { + return str_pad($str, $final_str_length, $pad_str, $pad_type); + } + + $str_length = mb_strlen($str); + + if ($final_str_length <= 0 OR $final_str_length <= $str_length) + { + return $str; + } + + $pad_str_length = mb_strlen($pad_str); + $pad_length = $final_str_length - $str_length; + + if ($pad_type == STR_PAD_RIGHT) + { + $repeat = ceil($pad_length / $pad_str_length); + return mb_substr($str.str_repeat($pad_str, $repeat), 0, $final_str_length); + } + + if ($pad_type == STR_PAD_LEFT) + { + $repeat = ceil($pad_length / $pad_str_length); + return mb_substr(str_repeat($pad_str, $repeat), 0, floor($pad_length)).$str; + } + + if ($pad_type == STR_PAD_BOTH) + { + $pad_length /= 2; + $pad_length_left = floor($pad_length); + $pad_length_right = ceil($pad_length); + $repeat_left = ceil($pad_length_left / $pad_str_length); + $repeat_right = ceil($pad_length_right / $pad_str_length); + + $pad_left = mb_substr(str_repeat($pad_str, $repeat_left), 0, $pad_length_left); + $pad_right = mb_substr(str_repeat($pad_str, $repeat_right), 0, $pad_length_left); + return $pad_left.$str.$pad_right; + } + + trigger_error('utf8::str_pad: Unknown padding type (' . $pad_type . ')', E_USER_ERROR); + } + + /** + * Converts a UTF-8 string to an array. + * @see http://php.net/str_split + * + * @author Harry Fuecks <hfuecks@gmail.com> + * + * @param string input string + * @param integer maximum length of each chunk + * @return array + */ + public static function str_split($str, $split_length = 1) + { + $split_length = (int) $split_length; + + if (text::is_ascii($str)) + { + return str_split($str, $split_length); + } + + if ($split_length < 1) + { + return FALSE; + } + + if (mb_strlen($str) <= $split_length) + { + return array($str); + } + + preg_match_all('/.{'.$split_length.'}|[^\x00]{1,'.$split_length.'}$/us', $str, $matches); + + return $matches[0]; + } + + /** + * Reverses a UTF-8 string. + * @see http://php.net/strrev + * + * @author Harry Fuecks <hfuecks@gmail.com> + * + * @param string string to be reversed + * @return string + */ + public static function strrev($str) + { + if (text::is_ascii($str)) + return strrev($str); + + preg_match_all('/./us', $str, $matches); + return implode('', array_reverse($matches[0])); + } + + /** + * Strips whitespace (or other UTF-8 characters) from the beginning and + * end of a string. + * @see http://php.net/trim + * + * @author Andreas Gohr <andi@splitbrain.org> + * + * @param string input string + * @param string string of characters to remove + * @return string + */ + public static function trim($str, $charlist = NULL) + { + if ($charlist === NULL) + return trim($str); + + return utf8::ltrim(utf8::rtrim($str, $charlist), $charlist); + } + + /** + * Strips whitespace (or other UTF-8 characters) from the beginning of a string. + * @see http://php.net/ltrim + * + * @author Andreas Gohr <andi@splitbrain.org> + * + * @param string input string + * @param string string of characters to remove + * @return string + */ + public static function ltrim($str, $charlist = NULL) + { + if ($charlist === NULL) + return ltrim($str); + + if (text::is_ascii($charlist)) + return ltrim($str, $charlist); + + $charlist = preg_replace('#[-\[\]:\\\\^/]#', '\\\\$0', $charlist); + + return preg_replace('/^['.$charlist.']+/u', '', $str); + } + + /** + * Strips whitespace (or other UTF-8 characters) from the end of a string. + * @see http://php.net/rtrim + * + * @author Andreas Gohr <andi@splitbrain.org> + * + * @param string input string + * @param string string of characters to remove + * @return string + */ + public static function rtrim($str, $charlist = NULL) + { + if ($charlist === NULL) + return rtrim($str); + + if (text::is_ascii($charlist)) + return rtrim($str, $charlist); + + $charlist = preg_replace('#[-\[\]:\\\\^/]#', '\\\\$0', $charlist); + + return preg_replace('/['.$charlist.']++$/uD', '', $str); + } + + /** + * Returns the unicode ordinal for a character. + * @see http://php.net/ord + * + * @author Harry Fuecks <hfuecks@gmail.com> + * + * @param string UTF-8 encoded character + * @return integer + */ + public static function ord($chr) + { + $ord0 = ord($chr); + + if ($ord0 >= 0 AND $ord0 <= 127) + { + return $ord0; + } + + if ( ! isset($chr[1])) + { + trigger_error('Short sequence - at least 2 bytes expected, only 1 seen', E_USER_WARNING); + return FALSE; + } + + $ord1 = ord($chr[1]); + + if ($ord0 >= 192 AND $ord0 <= 223) + { + return ($ord0 - 192) * 64 + ($ord1 - 128); + } + + if ( ! isset($chr[2])) + { + trigger_error('Short sequence - at least 3 bytes expected, only 2 seen', E_USER_WARNING); + return FALSE; + } + + $ord2 = ord($chr[2]); + + if ($ord0 >= 224 AND $ord0 <= 239) + { + return ($ord0 - 224) * 4096 + ($ord1 - 128) * 64 + ($ord2 - 128); + } + + if ( ! isset($chr[3])) + { + trigger_error('Short sequence - at least 4 bytes expected, only 3 seen', E_USER_WARNING); + return FALSE; + } + + $ord3 = ord($chr[3]); + + if ($ord0 >= 240 AND $ord0 <= 247) + { + return ($ord0 - 240) * 262144 + ($ord1 - 128) * 4096 + ($ord2-128) * 64 + ($ord3 - 128); + } + + if ( ! isset($chr[4])) + { + trigger_error('Short sequence - at least 5 bytes expected, only 4 seen', E_USER_WARNING); + return FALSE; + } + + $ord4 = ord($chr[4]); + + if ($ord0 >= 248 AND $ord0 <= 251) + { + return ($ord0 - 248) * 16777216 + ($ord1-128) * 262144 + ($ord2 - 128) * 4096 + ($ord3 - 128) * 64 + ($ord4 - 128); + } + + if ( ! isset($chr[5])) + { + trigger_error('Short sequence - at least 6 bytes expected, only 5 seen', E_USER_WARNING); + return FALSE; + } + + if ($ord0 >= 252 AND $ord0 <= 253) + { + return ($ord0 - 252) * 1073741824 + ($ord1 - 128) * 16777216 + ($ord2 - 128) * 262144 + ($ord3 - 128) * 4096 + ($ord4 - 128) * 64 + (ord($chr[5]) - 128); + } + + if ($ord0 >= 254 AND $ord0 <= 255) + { + trigger_error('Invalid UTF-8 with surrogate ordinal '.$ord0, E_USER_WARNING); + return FALSE; + } + } + + /** + * Takes an UTF-8 string and returns an array of ints representing the Unicode characters. + * Astral planes are supported i.e. the ints in the output can be > 0xFFFF. + * Occurrances of the BOM are ignored. Surrogates are not allowed. + * + * The Original Code is Mozilla Communicator client code. + * The Initial Developer of the Original Code is Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 the Initial Developer. + * Ported to PHP by Henri Sivonen <hsivonen@iki.fi>, see http://hsivonen.iki.fi/php-utf8/. + * Slight modifications to fit with phputf8 library by Harry Fuecks <hfuecks@gmail.com>. + * + * @param string UTF-8 encoded string + * @return array unicode code points + * @return boolean FALSE if the string is invalid + */ + public static function to_unicode($str) + { + $mState = 0; // cached expected number of octets after the current octet until the beginning of the next UTF8 character sequence + $mUcs4 = 0; // cached Unicode character + $mBytes = 1; // cached expected number of octets in the current sequence + + $out = array(); + + $len = strlen($str); + + for ($i = 0; $i < $len; $i++) + { + $in = ord($str[$i]); + + if ($mState == 0) + { + // When mState is zero we expect either a US-ASCII character or a + // multi-octet sequence. + if (0 == (0x80 & $in)) + { + // US-ASCII, pass straight through. + $out[] = $in; + $mBytes = 1; + } + elseif (0xC0 == (0xE0 & $in)) + { + // First octet of 2 octet sequence + $mUcs4 = $in; + $mUcs4 = ($mUcs4 & 0x1F) << 6; + $mState = 1; + $mBytes = 2; + } + elseif (0xE0 == (0xF0 & $in)) + { + // First octet of 3 octet sequence + $mUcs4 = $in; + $mUcs4 = ($mUcs4 & 0x0F) << 12; + $mState = 2; + $mBytes = 3; + } + elseif (0xF0 == (0xF8 & $in)) + { + // First octet of 4 octet sequence + $mUcs4 = $in; + $mUcs4 = ($mUcs4 & 0x07) << 18; + $mState = 3; + $mBytes = 4; + } + elseif (0xF8 == (0xFC & $in)) + { + // First octet of 5 octet sequence. + // + // This is illegal because the encoded codepoint must be either + // (a) not the shortest form or + // (b) outside the Unicode range of 0-0x10FFFF. + // Rather than trying to resynchronize, we will carry on until the end + // of the sequence and let the later error handling code catch it. + $mUcs4 = $in; + $mUcs4 = ($mUcs4 & 0x03) << 24; + $mState = 4; + $mBytes = 5; + } + elseif (0xFC == (0xFE & $in)) + { + // First octet of 6 octet sequence, see comments for 5 octet sequence. + $mUcs4 = $in; + $mUcs4 = ($mUcs4 & 1) << 30; + $mState = 5; + $mBytes = 6; + } + else + { + // Current octet is neither in the US-ASCII range nor a legal first octet of a multi-octet sequence. + trigger_error('utf8::to_unicode: Illegal sequence identifier in UTF-8 at byte '.$i, E_USER_WARNING); + return FALSE; + } + } + else + { + // When mState is non-zero, we expect a continuation of the multi-octet sequence + if (0x80 == (0xC0 & $in)) + { + // Legal continuation + $shift = ($mState - 1) * 6; + $tmp = $in; + $tmp = ($tmp & 0x0000003F) << $shift; + $mUcs4 |= $tmp; + + // End of the multi-octet sequence. mUcs4 now contains the final Unicode codepoint to be output + if (0 == --$mState) + { + // Check for illegal sequences and codepoints + + // From Unicode 3.1, non-shortest form is illegal + if (((2 == $mBytes) AND ($mUcs4 < 0x0080)) OR + ((3 == $mBytes) AND ($mUcs4 < 0x0800)) OR + ((4 == $mBytes) AND ($mUcs4 < 0x10000)) OR + (4 < $mBytes) OR + // From Unicode 3.2, surrogate characters are illegal + (($mUcs4 & 0xFFFFF800) == 0xD800) OR + // Codepoints outside the Unicode range are illegal + ($mUcs4 > 0x10FFFF)) + { + trigger_error('utf8::to_unicode: Illegal sequence or codepoint in UTF-8 at byte '.$i, E_USER_WARNING); + return FALSE; + } + + if (0xFEFF != $mUcs4) + { + // BOM is legal but we don't want to output it + $out[] = $mUcs4; + } + + // Initialize UTF-8 cache + $mState = 0; + $mUcs4 = 0; + $mBytes = 1; + } + } + else + { + // ((0xC0 & (*in) != 0x80) AND (mState != 0)) + // Incomplete multi-octet sequence + trigger_error('utf8::to_unicode: Incomplete multi-octet sequence in UTF-8 at byte '.$i, E_USER_WARNING); + return FALSE; + } + } + } + + return $out; + } + + /** + * Takes an array of ints representing the Unicode characters and returns a UTF-8 string. + * Astral planes are supported i.e. the ints in the input can be > 0xFFFF. + * Occurrances of the BOM are ignored. Surrogates are not allowed. + * + * The Original Code is Mozilla Communicator client code. + * The Initial Developer of the Original Code is Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 the Initial Developer. + * Ported to PHP by Henri Sivonen <hsivonen@iki.fi>, see http://hsivonen.iki.fi/php-utf8/. + * Slight modifications to fit with phputf8 library by Harry Fuecks <hfuecks@gmail.com>. + * + * @param array unicode code points representing a string + * @return string utf8 string of characters + * @return boolean FALSE if a code point cannot be found + */ + public static function from_unicode($arr) + { + ob_start(); + + $keys = array_keys($arr); + + foreach ($keys as $k) + { + // ASCII range (including control chars) + if (($arr[$k] >= 0) AND ($arr[$k] <= 0x007f)) + { + echo chr($arr[$k]); + } + // 2 byte sequence + elseif ($arr[$k] <= 0x07ff) + { + echo chr(0xc0 | ($arr[$k] >> 6)); + echo chr(0x80 | ($arr[$k] & 0x003f)); + } + // Byte order mark (skip) + elseif ($arr[$k] == 0xFEFF) + { + // nop -- zap the BOM + } + // Test for illegal surrogates + elseif ($arr[$k] >= 0xD800 AND $arr[$k] <= 0xDFFF) + { + // Found a surrogate + trigger_error('utf8::from_unicode: Illegal surrogate at index: '.$k.', value: '.$arr[$k], E_USER_WARNING); + return FALSE; + } + // 3 byte sequence + elseif ($arr[$k] <= 0xffff) + { + echo chr(0xe0 | ($arr[$k] >> 12)); + echo chr(0x80 | (($arr[$k] >> 6) & 0x003f)); + echo chr(0x80 | ($arr[$k] & 0x003f)); + } + // 4 byte sequence + elseif ($arr[$k] <= 0x10ffff) + { + echo chr(0xf0 | ($arr[$k] >> 18)); + echo chr(0x80 | (($arr[$k] >> 12) & 0x3f)); + echo chr(0x80 | (($arr[$k] >> 6) & 0x3f)); + echo chr(0x80 | ($arr[$k] & 0x3f)); + } + // Out of range + else + { + trigger_error('utf8::from_unicode: Codepoint out of Unicode range at index: '.$k.', value: '.$arr[$k], E_USER_WARNING); + return FALSE; + } + } + + $result = ob_get_contents(); + ob_end_clean(); + return $result; + } + +} // End utf8
\ No newline at end of file diff --git a/system/helpers/valid.php b/system/helpers/valid.php index 8a3583b2..cffcd7c0 100644 --- a/system/helpers/valid.php +++ b/system/helpers/valid.php @@ -2,12 +2,12 @@ /** * Validation helper class. * - * $Id: valid.php 4367 2009-05-27 21:23:57Z samsoir $ + * $Id: valid.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 valid_Core { @@ -161,13 +161,13 @@ class valid_Core { for ($i = $length - 1; $i >= 0; $i -= 2) { // Add up every 2nd digit, starting from the right - $checksum += $number[$i]; + $checksum += substr($number, $i, 1); } for ($i = $length - 2; $i >= 0; $i -= 2) { // Add up every 2nd digit doubled, starting from the right - $double = $number[$i] * 2; + $double = substr($number, $i, 1) * 2; // Subtract 9 from the double where value is greater than 10 $checksum += ($double >= 10) ? $double - 9 : $double; @@ -199,7 +199,7 @@ class valid_Core { /** * Tests if a string is a valid date string. - * + * * @param string date to check * @return boolean */ @@ -269,7 +269,7 @@ class valid_Core { * * @see Uses locale conversion to allow decimal point to be locale specific. * @see http://www.php.net/manual/en/function.localeconv.php - * + * * @param string input string * @return boolean */ @@ -281,21 +281,35 @@ class valid_Core { } /** - * Checks whether a string is a valid text. Letters, numbers, whitespace, - * dashes, periods, and underscores are allowed. + * Tests if an integer is within a range. * - * @param string text to check + * @param integer number to check + * @param array valid range of input * @return boolean */ - public static function standard_text($str) + public static function range($number, array $range) { - // pL matches letters - // pN matches numbers - // pZ matches whitespace - // pPc matches underscores - // pPd matches dashes - // pPo matches normal puncuation - return (bool) preg_match('/^[\pL\pN\pZ\p{Pc}\p{Pd}\p{Po}]++$/uD', (string) $str); + // Invalid by default + $status = FALSE; + + if (is_int($number) OR ctype_digit($number)) + { + if (count($range) > 1) + { + if ($number >= $range[0] AND $number <= $range[1]) + { + // Number is within the required range + $status = TRUE; + } + } + elseif ($number >= $range[0]) + { + // Number is greater than the minimum + $status = TRUE; + } + } + + return $status; } /** @@ -335,4 +349,18 @@ class valid_Core { return (bool) preg_match($pattern, (string) $str); } + /** + * Checks if a string is a proper hexadecimal HTML color value. The validation + * is quite flexible as it does not require an initial "#" and also allows for + * the short notation using only three instead of six hexadecimal characters. + * You may want to normalize these values with format::color(). + * + * @param string input string + * @return boolean + */ + public static function color($str) + { + return (bool) preg_match('/^#?+[0-9a-f]{3}(?:[0-9a-f]{3})?$/iD', $str); + } + } // End valid
\ No newline at end of file diff --git a/system/i18n/en_US/cache.php b/system/i18n/en_US/cache.php deleted file mode 100644 index bef02793..00000000 --- a/system/i18n/en_US/cache.php +++ /dev/null @@ -1,10 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); - -$lang = array -( - 'undefined_group' => 'The %s group is not defined in your configuration.', - 'extension_not_loaded' => 'The %s PHP extension must be loaded to use this driver.', - 'unwritable' => 'The configured storage location, %s, is not writable.', - 'resources' => 'Caching of resources is impossible, because resources cannot be serialized.', - 'driver_error' => '%s', -);
\ No newline at end of file diff --git a/system/i18n/en_US/calendar.php b/system/i18n/en_US/calendar.php deleted file mode 100644 index 21dad220..00000000 --- a/system/i18n/en_US/calendar.php +++ /dev/null @@ -1,59 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); - -$lang = array -( - // Two letter days - 'su' => 'Su', - 'mo' => 'Mo', - 'tu' => 'Tu', - 'we' => 'We', - 'th' => 'Th', - 'fr' => 'Fr', - 'sa' => 'Sa', - - // Short day names - 'sun' => 'Sun', - 'mon' => 'Mon', - 'tue' => 'Tue', - 'wed' => 'Wed', - 'thu' => 'Thu', - 'fri' => 'Fri', - 'sat' => 'Sat', - - // Long day names - 'sunday' => 'Sunday', - 'monday' => 'Monday', - 'tuesday' => 'Tuesday', - 'wednesday' => 'Wednesday', - 'thursday' => 'Thursday', - 'friday' => 'Friday', - 'saturday' => 'Saturday', - - // Short month names - 'jan' => 'Jan', - 'feb' => 'Feb', - 'mar' => 'Mar', - 'apr' => 'Apr', - 'may' => 'May', - 'jun' => 'Jun', - 'jul' => 'Jul', - 'aug' => 'Aug', - 'sep' => 'Sep', - 'oct' => 'Oct', - 'nov' => 'Nov', - 'dec' => 'Dec', - - // Long month names - 'january' => 'January', - 'february' => 'February', - 'march' => 'March', - 'april' => 'April', - 'mayl' => 'May', - 'june' => 'June', - 'july' => 'July', - 'august' => 'August', - 'september' => 'September', - 'october' => 'October', - 'november' => 'November', - 'december' => 'December' -);
\ No newline at end of file diff --git a/system/i18n/en_US/core.php b/system/i18n/en_US/core.php deleted file mode 100644 index 9711b7cd..00000000 --- a/system/i18n/en_US/core.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); - -$lang = array -( - 'there_can_be_only_one' => 'There can be only one instance of Kohana per page request', - 'uncaught_exception' => 'Uncaught %s: %s in file %s on line %s', - 'invalid_method' => 'Invalid method %s called in %s', - 'invalid_property' => 'The %s property does not exist in the %s class.', - 'log_dir_unwritable' => 'The log directory is not writable: %s', - 'resource_not_found' => 'The requested %s, %s, could not be found', - 'invalid_filetype' => 'The requested filetype, .%s, is not allowed in your view configuration file', - 'view_set_filename' => 'You must set the the view filename before calling render', - 'no_default_route' => 'Please set a default route in config/routes.php', - 'no_controller' => 'Kohana was not able to determine a controller to process this request: %s', - 'page_not_found' => 'The page you requested, %s, could not be found.', - 'stats_footer' => 'Loaded in {execution_time} seconds, using {memory_usage} of memory. Generated by Kohana v{kohana_version}.', - 'error_file_line' => '<tt>%s <strong>[%s]:</strong></tt>', - 'stack_trace' => 'Stack Trace', - 'generic_error' => 'Unable to Complete Request', - 'errors_disabled' => 'You can go to the <a href="%s">home page</a> or <a href="%s">try again</a>.', - - // Drivers - 'driver_implements' => 'The %s driver for the %s library must implement the %s interface', - 'driver_not_found' => 'The %s driver for the %s library could not be found', - - // Resource names - 'config' => 'config file', - 'controller' => 'controller', - 'helper' => 'helper', - 'library' => 'library', - 'driver' => 'driver', - 'model' => 'model', - 'view' => 'view', -); diff --git a/system/i18n/en_US/database.php b/system/i18n/en_US/database.php deleted file mode 100644 index 172e5c90..00000000 --- a/system/i18n/en_US/database.php +++ /dev/null @@ -1,15 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); - -$lang = array -( - 'undefined_group' => 'The %s group is not defined in your configuration.', - 'error' => 'There was an SQL error: %s', - 'connection' => 'There was an error connecting to the database: %s', - 'invalid_dsn' => 'The DSN you supplied is not valid: %s', - 'must_use_set' => 'You must set a SET clause for your query.', - 'must_use_where' => 'You must set a WHERE clause for your query.', - 'must_use_table' => 'You must set a database table for your query.', - 'table_not_found' => 'Table %s does not exist in your database.', - 'not_implemented' => 'The method you called, %s, is not supported by this driver.', - 'result_read_only' => 'Query results are read only.' -);
\ No newline at end of file diff --git a/system/i18n/en_US/encrypt.php b/system/i18n/en_US/encrypt.php deleted file mode 100644 index 9afd6208..00000000 --- a/system/i18n/en_US/encrypt.php +++ /dev/null @@ -1,8 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); - -$lang = array -( - 'undefined_group' => 'The %s group is not defined in your configuration.', - 'requires_mcrypt' => 'To use the Encrypt library, mcrypt must be enabled in your PHP installation', - 'no_encryption_key' => 'To use the Encrypt library, you must set an encryption key in your config file' -); diff --git a/system/i18n/en_US/errors.php b/system/i18n/en_US/errors.php deleted file mode 100644 index 83341a2c..00000000 --- a/system/i18n/en_US/errors.php +++ /dev/null @@ -1,16 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); - -$lang = array -( - E_KOHANA => array( 1, 'Framework Error', 'Please check the Kohana documentation for information about the following error.'), - E_PAGE_NOT_FOUND => array( 1, 'Page Not Found', 'The requested page was not found. It may have moved, been deleted, or archived.'), - E_DATABASE_ERROR => array( 1, 'Database Error', 'A database error occurred while performing the requested procedure. Please review the database error below for more information.'), - E_RECOVERABLE_ERROR => array( 1, 'Recoverable Error', 'An error was detected which prevented the loading of this page. If this problem persists, please contact the website administrator.'), - E_ERROR => array( 1, 'Fatal Error', ''), - E_USER_ERROR => array( 1, 'Fatal Error', ''), - E_PARSE => array( 1, 'Syntax Error', ''), - E_WARNING => array( 1, 'Warning Message', ''), - E_USER_WARNING => array( 1, 'Warning Message', ''), - E_STRICT => array( 2, 'Strict Mode Error', ''), - E_NOTICE => array( 2, 'Runtime Message', ''), -);
\ No newline at end of file diff --git a/system/i18n/en_US/event.php b/system/i18n/en_US/event.php deleted file mode 100644 index 282a0a25..00000000 --- a/system/i18n/en_US/event.php +++ /dev/null @@ -1,7 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); - -$lang = array -( - 'invalid_subject' => 'Attempt to attach invalid subject %s to %s failed: Subjects must extend the Event_Subject class', - 'invalid_observer' => 'Attempt to attach invalid observer %s to %s failed: Observers must extend the Event_Observer class', -); diff --git a/system/i18n/en_US/image.php b/system/i18n/en_US/image.php deleted file mode 100644 index 9f184930..00000000 --- a/system/i18n/en_US/image.php +++ /dev/null @@ -1,33 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); - -$lang = array -( - 'getimagesize_missing' => 'The Image library requires the getimagesize() PHP function, which is not available in your installation.', - 'unsupported_method' => 'Your configured driver does not support the %s image transformation.', - 'file_not_found' => 'The specified image, %s, was not found. Please verify that images exist by using file_exists() before manipulating them.', - 'type_not_allowed' => 'The specified image, %s, is not an allowed image type.', - 'invalid_width' => 'The width you specified, %s, is not valid.', - 'invalid_height' => 'The height you specified, %s, is not valid.', - 'invalid_dimensions' => 'The dimensions specified for %s are not valid.', - 'invalid_master' => 'The master dimension specified is not valid.', - 'invalid_flip' => 'The flip direction specified is not valid.', - 'directory_unwritable' => 'The specified directory, %s, is not writable.', - - // ImageMagick specific messages - 'imagemagick' => array - ( - 'not_found' => 'The ImageMagick directory specified does not contain a required program, %s.', - ), - - // GraphicsMagick specific messages - 'graphicsmagick' => array - ( - 'not_found' => 'The GraphicsMagick directory specified does not contain a required program, %s.', - ), - - // GD specific messages - 'gd' => array - ( - 'requires_v2' => 'The Image library requires GD2. Please see http://php.net/gd_info for more information.', - ), -); diff --git a/system/i18n/en_US/orm.php b/system/i18n/en_US/orm.php deleted file mode 100644 index 3c5720bc..00000000 --- a/system/i18n/en_US/orm.php +++ /dev/null @@ -1,3 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); - -$lang['query_methods_not_allowed'] = 'Query methods cannot be used through ORM';
\ No newline at end of file diff --git a/system/i18n/en_US/profiler.php b/system/i18n/en_US/profiler.php deleted file mode 100644 index a39c2f51..00000000 --- a/system/i18n/en_US/profiler.php +++ /dev/null @@ -1,15 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); - -$lang = array -( - 'benchmarks' => 'Benchmarks', - 'post_data' => 'Post Data', - 'no_post' => 'No post data', - 'session_data' => 'Session Data', - 'no_session' => 'No session data', - 'queries' => 'Database Queries', - 'no_queries' => 'No queries', - 'no_database' => 'Database not loaded', - 'cookie_data' => 'Cookie Data', - 'no_cookie' => 'No cookie data', -); diff --git a/system/i18n/en_US/session.php b/system/i18n/en_US/session.php deleted file mode 100644 index ee781c65..00000000 --- a/system/i18n/en_US/session.php +++ /dev/null @@ -1,6 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); - -$lang = array -( - 'invalid_session_name' => 'The session_name, %s, is invalid. It must contain only alphanumeric characters and underscores. Also at least one letter must be present.', -);
\ No newline at end of file diff --git a/system/i18n/en_US/upload.php b/system/i18n/en_US/upload.php deleted file mode 100644 index 7f6e216d..00000000 --- a/system/i18n/en_US/upload.php +++ /dev/null @@ -1,6 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); - -$lang = array -( - 'not_writable' => 'The upload destination folder, %s, does not appear to be writable.', -);
\ No newline at end of file diff --git a/system/i18n/en_US/validation.php b/system/i18n/en_US/validation.php deleted file mode 100644 index d98e548f..00000000 --- a/system/i18n/en_US/validation.php +++ /dev/null @@ -1,41 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); - -$lang = array -( - // Class errors - 'invalid_rule' => 'Invalid validation rule used: %s', - 'i18n_array' => 'The %s i18n key must be an array to be used with the in_lang rule', - 'not_callable' => 'Callback %s used for Validation is not callable', - - // General errors - 'unknown_error' => 'Unknown validation error while validating the %s field.', - 'required' => 'The %s field is required.', - 'min_length' => 'The %s field must be at least %d characters long.', - 'max_length' => 'The %s field must be %d characters or fewer.', - 'exact_length' => 'The %s field must be exactly %d characters.', - 'in_array' => 'The %s field must be selected from the options listed.', - 'matches' => 'The %s field must match the %s field.', - 'valid_url' => 'The %s field must contain a valid URL.', - 'valid_email' => 'The %s field must contain a valid email address.', - 'valid_ip' => 'The %s field must contain a valid IP address.', - 'valid_type' => 'The %s field must only contain %s characters.', - 'range' => 'The %s field must be between specified ranges.', - 'regex' => 'The %s field does not match accepted input.', - 'depends_on' => 'The %s field depends on the %s field.', - - // Upload errors - 'user_aborted' => 'The %s file was aborted during upload.', - 'invalid_type' => 'The %s file is not an allowed file type.', - 'max_size' => 'The %s file you uploaded was too large. The maximum size allowed is %s.', - 'max_width' => 'The %s file you uploaded was too big. The maximum allowed width is %spx.', - 'max_height' => 'The %s file you uploaded was too big. The maximum allowed height is %spx.', - 'min_width' => 'The %s file you uploaded was too small. The minimum allowed width is %spx.', - 'min_height' => 'The %s file you uploaded was too small. The minimum allowed height is %spx.', - - // Field types - 'alpha' => 'alphabetical', - 'alpha_numeric' => 'alphabetical and numeric', - 'alpha_dash' => 'alphabetical, dash, and underscore', - 'digit' => 'digit', - 'numeric' => 'numeric', -); 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 @@ +<?php defined('SYSPATH') OR die('No direct access allowed.'); +/** + * $Id: Kohana_User_Exception.php 4543 2009-09-04 16:58:56Z nodren $ + * + * @package Core + * @author Kohana Team + * @copyright (c) 2007-2009 Kohana Team + * @license http://kohanaphp.com/license + */ + +class Cache_Exception_Core extends Kohana_Exception {} +// End Kohana User Exception diff --git a/system/libraries/Controller.php b/system/libraries/Controller.php index 2f64c211..da84bfc1 100644 --- a/system/libraries/Controller.php +++ b/system/libraries/Controller.php @@ -3,12 +3,12 @@ * Kohana Controller class. The controller class must be extended to work * properly, so this class is defined as abstract. * - * $Id: Controller.php 4365 2009-05-27 21:09:27Z samsoir $ + * $Id: Controller.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 */ abstract class Controller_Core { @@ -48,39 +48,4 @@ abstract class Controller_Core { Event::run('system.404'); } - /** - * Includes a View within the controller scope. - * - * @param string view filename - * @param array array of view variables - * @return string - */ - public function _kohana_load_view($kohana_view_filename, $kohana_input_data) - { - if ($kohana_view_filename == '') - return; - - // Buffering on - ob_start(); - - // Import the view variables to local namespace - extract($kohana_input_data, EXTR_SKIP); - - // Views are straight HTML pages with embedded PHP, so importing them - // this way insures that $this can be accessed as if the user was in - // the controller, which gives the easiest access to libraries in views - 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 Controller Class
\ No newline at end of file diff --git a/system/libraries/Database.php b/system/libraries/Database.php index 4cd29c58..d3716c59 100644 --- a/system/libraries/Database.php +++ b/system/libraries/Database.php @@ -1,1457 +1,642 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); +<?php defined('SYSPATH') or die('No direct script access.'); /** - * Provides database access in a platform agnostic way, using simple query building blocks. - * - * $Id: Database.php 4438 2009-07-06 04:11:16Z kiall $ - * - * @package Core + * Database wrapper. + * + * $Id: Database.php 4679 2009-11-10 01:45:52Z isaiah $ + * + * @package Kohana * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html + * @copyright (c) 2008-2009 Kohana Team + * @license http://kohanaphp.com/license */ -class Database_Core { +abstract class Database_Core { - // Database instances - public static $instances = array(); + const SELECT = 1; + const INSERT = 2; + const UPDATE = 3; + const DELETE = 4; - // Global benchmark + protected static $instances = array(); + + // Global benchmarks public static $benchmarks = array(); - // Configuration - protected $config = array - ( - 'benchmark' => TRUE, - 'persistent' => FALSE, - 'connection' => '', - 'character_set' => 'utf8', - 'table_prefix' => '', - 'object' => TRUE, - 'cache' => FALSE, - 'escape' => TRUE, - ); - - // Database driver object - protected $driver; - protected $link; - - // Un-compiled parts of the SQL query - protected $select = array(); - protected $set = array(); - protected $from = array(); - protected $join = array(); - protected $where = array(); - protected $orderby = array(); - protected $order = array(); - protected $groupby = array(); - protected $having = array(); - protected $distinct = FALSE; - protected $limit = FALSE; - protected $offset = FALSE; - protected $last_query = ''; - - // Stack of queries for push/pop - protected $query_history = array(); + // 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. - * - * @param Database instance of Database - * @return string - */ - public static function instance_name(Database $db) - { - return array_search($db, Database::$instances, TRUE); - } - - /** - * Sets up the database configuration, loads the Database_Driver. + * Constructs a new Database object * - * @throws Kohana_Database_Exception + * @param array Database config array + * @return Database_Core */ - public function __construct($config = array()) + protected function __construct(array $config) { - if (empty($config)) - { - // Load the default group - $config = Kohana::config('database.default'); - } - elseif (is_array($config) AND count($config) > 0) - { - if ( ! array_key_exists('connection', $config)) - { - $config = array('connection' => $config); - } - } - elseif (is_string($config)) - { - // The config is a DSN string - if (strpos($config, '://') !== FALSE) - { - $config = array('connection' => $config); - } - // The config is a group name - else - { - $name = $config; - - // Test the config group name - if (($config = Kohana::config('database.'.$config)) === NULL) - throw new Kohana_Database_Exception('database.undefined_group', $name); - } - } + // Store the config locally + $this->config = $config; - // Merge the default config with the passed config - $this->config = array_merge($this->config, $config); - - if (is_string($this->config['connection'])) + if ($this->config['cache'] !== FALSE) { - // 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 (is_string($this->config['cache'])) { - // Get the username and password - list ($db['pass'], $connection) = explode('@', $connection, 2); - // Check if a password is supplied - $logindata = explode(':', $db['pass'], 2); - $db['pass'] = (count($logindata) > 1) ? $logindata[1] : ''; - $db['user'] = $logindata[0]; - - // Prepare for finding the database - $connection = explode('/', $connection); - - // Find the database name - $db['database'] = array_pop($connection); - - // Reset connection string - $connection = implode('/', $connection); - - // Find the socket - if (preg_match('/^unix\([^)]++\)/', $connection)) - { - // This one is a little hairy: we explode based on the end of - // the socket, removing the 'unix(' from the connection string - list ($db['socket'], $connection) = explode(')', substr($connection, 5), 2); - } - elseif (strpos($connection, ':') !== FALSE) - { - // Fetch the host and port name - list ($db['host'], $db['port']) = explode(':', $connection, 2); - } - else - { - $db['host'] = $connection; - } + // Use Cache library + $this->cache = new Cache($this->config['cache']); } - else + elseif ($this->config['cache'] === TRUE) { - // File connection - $connection = explode('/', $connection); - - // Find database file name - $db['database'] = array_pop($connection); - - // Find database directory name - $db['socket'] = implode('/', $connection).'/'; + // Use array + $this->cache = array(); } - - // 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'); + public function __destruct() + { + $this->disconnect(); } /** - * Simple connect method to get the database queries up and running. + * Connects to the database * - * @return void + * @return void */ - public function connect() - { - // A link can be a resource or an object - if ( ! is_resource($this->link) AND ! is_object($this->link)) - { - $this->link = $this->driver->connect(); - if ( ! is_resource($this->link) AND ! is_object($this->link)) - throw new Kohana_Database_Exception('database.connection', $this->driver->show_error()); - - // Clear password after successful connect - $this->config['connection']['pass'] = NULL; - } - } + abstract public function connect(); /** - * Runs a query into the driver and returns the result. + * Disconnects from the database * - * @param string SQL query to execute - * @return Database_Result + * @return void */ - public function query($sql = '') - { - if ($sql == '') return FALSE; - - // No link? Connect! - $this->link or $this->connect(); - - // Start the benchmark - $start = microtime(TRUE); - - if (func_num_args() > 1) //if we have more than one argument ($sql) - { - $argv = func_get_args(); - $binds = (is_array(next($argv))) ? current($argv) : array_slice($argv, 1); - } - - // Compile binds if needed - if (isset($binds)) - { - $sql = $this->compile_binds($sql, $binds); - } - - // Fetch the result - $result = $this->driver->query($this->last_query = $sql); - - // Stop the benchmark - $stop = microtime(TRUE); - - if ($this->config['benchmark'] == TRUE) - { - // Benchmark the query - Database::$benchmarks[] = array('query' => $sql, 'time' => $stop - $start, 'rows' => count($result)); - } - - return $result; - } + abstract public function disconnect(); /** - * Selects the column names for a database query. + * Sets the character set * - * @param string string or array of column names to select - * @return Database_Core This Database object. + * @return void */ - public function select($sql = '*') - { - if (func_num_args() > 1) - { - $sql = func_get_args(); - } - elseif (is_string($sql)) - { - $sql = explode(',', $sql); - } - else - { - $sql = (array) $sql; - } - - foreach ($sql as $val) - { - if (($val = trim($val)) === '') continue; - - if (strpos($val, '(') === FALSE AND $val !== '*') - { - if (preg_match('/^DISTINCT\s++(.+)$/i', $val, $matches)) - { - // Only prepend with table prefix if table name is specified - $val = (strpos($matches[1], '.') !== FALSE) ? $this->config['table_prefix'].$matches[1] : $matches[1]; - - $this->distinct = TRUE; - } - else - { - $val = (strpos($val, '.') !== FALSE) ? $this->config['table_prefix'].$val : $val; - } - - $val = $this->driver->escape_column($val); - } - - $this->select[] = $val; - } - - return $this; - } + abstract public function set_charset($charset); /** - * Selects the from table(s) for a database query. + * Executes the query * - * @param string string or array of tables to select - * @return Database_Core This Database object. + * @param string SQL + * @return Database_Result */ - public function from($sql) - { - if (func_num_args() > 1) - { - $sql = func_get_args(); - } - elseif (is_string($sql)) - { - $sql = explode(',', $sql); - } - else - { - $sql = array($sql); - } + abstract public function query_execute($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); + /** + * Escapes the given value + * + * @param mixed Value + * @return mixed Escaped value + */ + abstract public function escape($value); - // 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; - } - } + /** + * List constraints for the given table + * + * @param string Table name + * @return array + */ + abstract public function list_constraints($table); - $this->from[] = $val; - } + /** + * List fields for the given table + * + * @param string Table name + * @return array + */ + abstract public function list_fields($table); - return $this; - } + /** + * List tables for the given connection (checks for prefix) + * + * @return array + */ + abstract public function list_tables(); /** - * Generates the JOIN portion of the query. + * Converts the given DSN string to an array of database connection components * - * @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 string DSN string + * @return array */ - public function join($table, $key, $value = NULL, $type = '') + public static function parse_dsn($dsn) { - $join = array(); + $db = array + ( + 'type' => FALSE, + 'user' => FALSE, + 'pass' => FALSE, + 'host' => FALSE, + 'port' => FALSE, + 'socket' => FALSE, + 'database' => FALSE + ); - if ( ! empty($type)) + // Get the protocol and arguments + list ($db['type'], $connection) = explode('://', $dsn, 2); + + if ($connection[0] === '/') + { + // Strip leading slash + $db['database'] = substr($connection, 1); + } + else { - $type = strtoupper(trim($type)); + $connection = parse_url('http://'.$connection); - if ( ! in_array($type, array('LEFT', 'RIGHT', 'OUTER', 'INNER', 'LEFT OUTER', 'RIGHT OUTER'), TRUE)) + if (isset($connection['user'])) { - $type = ''; + $db['user'] = $connection['user']; } - else + + if (isset($connection['pass'])) { - $type .= ' '; + $db['pass'] = $connection['pass']; } - } - $cond = array(); - $keys = is_array($key) ? $key : array($key => $value); - foreach ($keys as $key => $value) - { - $key = (strpos($key, '.') !== FALSE) ? $this->config['table_prefix'].$key : $key; - - if (is_string($value)) + if (isset($connection['port'])) { - // Only escape if it's a string - $value = $this->driver->escape_column($this->config['table_prefix'].$value); + $db['port'] = $connection['port']; } - $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)) + if (isset($connection['host'])) { - // TODO: Temporary solution, this should be moved to database driver (AS is checked for twice) - if (stripos($t, ' AS ') !== FALSE) + if ($connection['host'] === 'unix(') { - $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; + list($db['socket'], $connection['path']) = explode(')', $connection['path'], 2); } else { - $t = $this->config['table_prefix'].$t; + $db['host'] = $connection['host']; } } - $join['tables'][] = $this->driver->escape_column($t); + if (isset($connection['path']) AND $connection['path']) + { + // Strip leading slash + $db['database'] = substr($connection['path'], 1); + } } - $join['conditions'] = '('.trim(implode(' ', $cond)).')'; - $join['type'] = $type; - - $this->join[] = $join; - - return $this; + return $db; } - /** - * Selects the where(s) for a database query. + * Returns the last executed query for this database * - * @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. + * @return string */ - public function where($key, $value = NULL, $quote = TRUE) + public function last_query() { - $quote = (func_num_args() < 2 AND ! is_array($key)) ? -1 : $quote; - if (is_object($key)) - { - $keys = array((string) $key => ''); - } - elseif ( ! is_array($key)) - { - $keys = array($key => $value); - } - else - { - $keys = $key; - } - - foreach ($keys as $key => $value) - { - $key = (strpos($key, '.') !== FALSE) ? $this->config['table_prefix'].$key : $key; - $this->where[] = $this->driver->where($key, $value, 'AND ', count($this->where), $quote); - } - - return $this; + return $this->last_query; } /** - * Selects the or where(s) for a database query. + * Executes the given query, returning the cached version if enabled * - * @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 SQL query + * @return Database_Result */ - public function orwhere($key, $value = NULL, $quote = TRUE) + public function query($sql) { - $quote = (func_num_args() < 2 AND ! is_array($key)) ? -1 : $quote; - if (is_object($key)) - { - $keys = array((string) $key => ''); - } - elseif ( ! is_array($key)) - { - $keys = array($key => $value); - } - else - { - $keys = $key; - } + // Start the benchmark + $start = microtime(TRUE); - foreach ($keys as $key => $value) + if (is_array($this->cache)) { - $key = (strpos($key, '.') !== FALSE) ? $this->config['table_prefix'].$key : $key; - $this->where[] = $this->driver->where($key, $value, 'OR ', count($this->where), $quote); - } - - return $this; - } + $hash = $this->query_hash($sql); - /** - * 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); + if (isset($this->cache[$hash])) + { + // Use cached result + $result = $this->cache[$hash]; - foreach ($fields as $field => $match) - { - $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field; - $this->where[] = $this->driver->like($field, $match, $auto, 'AND ', count($this->where)); + // It's from cache + $sql .= ' [CACHE]'; + } + else + { + // No cache, execute query and store in cache + $result = $this->cache[$hash] = $this->query_execute($sql); + } } - - return $this; - } - - /** - * Selects the or like(s) for a database query. - * - * @param string|array field name or array of field => match pairs - * @param string like value to match with field - * @param boolean automatically add starting and ending wildcards - * @return Database_Core This Database object. - */ - public function orlike($field, $match = '', $auto = TRUE) - { - $fields = is_array($field) ? $field : array($field => $match); - - foreach ($fields as $field => $match) + else { - $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field; - $this->where[] = $this->driver->like($field, $match, $auto, 'OR ', count($this->where)); + // Execute the query, cache is off + $result = $this->query_execute($sql); } - return $this; - } - - /** - * Selects the not like(s) for a database query. - * - * @param string|array field name or array of field => match pairs - * @param string like value to match with field - * @param boolean automatically add starting and ending wildcards - * @return Database_Core This Database object. - */ - public function notlike($field, $match = '', $auto = TRUE) - { - $fields = is_array($field) ? $field : array($field => $match); + // Stop the benchmark + $stop = microtime(TRUE); - foreach ($fields as $field => $match) + if ($this->config['benchmark'] === TRUE) { - $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field; - $this->where[] = $this->driver->notlike($field, $match, $auto, 'AND ', count($this->where)); + // Benchmark the query + Database::$benchmarks[] = array('query' => $sql, 'time' => $stop - $start, 'rows' => count($result)); } - return $this; + return $result; } /** - * Selects the or not like(s) for a database query. + * Performs the query on the cache (and caches it if it's not found) * - * @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 query + * @param int time-to-live (NULL for Cache default) + * @return Database_Cache_Result */ - public function ornotlike($field, $match = '', $auto = TRUE) + public function query_cache($sql, $ttl) { - $fields = is_array($field) ? $field : array($field => $match); - - foreach ($fields as $field => $match) + if ( ! $this->cache instanceof Cache) { - $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field; - $this->where[] = $this->driver->notlike($field, $match, $auto, 'OR ', count($this->where)); + throw new Database_Exception('Database :name has not been configured to use the Cache library.'); } - return $this; - } + // Start the benchmark + $start = microtime(TRUE); - /** - * Selects the like(s) for a database query. - * - * @param string|array field name or array of field => match pairs - * @param string like value to match with field - * @return Database_Core This Database object. - */ - public function regex($field, $match = '') - { - $fields = is_array($field) ? $field : array($field => $match); + $hash = $this->query_hash($sql); - foreach ($fields as $field => $match) + if (($data = $this->cache->get($hash)) !== NULL) { - $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field; - $this->where[] = $this->driver->regex($field, $match, 'AND ', count($this->where)); - } - - return $this; - } + // Found in cache, create result + $result = new Database_Cache_Result($data, $sql, $this->config['object']); - /** - * Selects the or like(s) for a database query. - * - * @param string|array field name or array of field => match pairs - * @param string like value to match with field - * @return Database_Core This Database object. - */ - public function orregex($field, $match = '') - { - $fields = is_array($field) ? $field : array($field => $match); - - foreach ($fields as $field => $match) - { - $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field; - $this->where[] = $this->driver->regex($field, $match, 'OR ', count($this->where)); + // It's from the cache + $sql .= ' [CACHE]'; } - - return $this; - } - - /** - * Selects the not regex(s) for a database query. - * - * @param string|array field name or array of field => match pairs - * @param string regex value to match with field - * @return Database_Core This Database object. - */ - public function notregex($field, $match = '') - { - $fields = is_array($field) ? $field : array($field => $match); - - foreach ($fields as $field => $match) + else { - $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field; - $this->where[] = $this->driver->notregex($field, $match, 'AND ', count($this->where)); - } - - return $this; - } + // Run the query and return the full array of rows + $data = $this->query_execute($sql)->as_array(TRUE); - /** - * 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); + // Set the Cache + $this->cache->set($hash, $data, NULL, $ttl); - foreach ($fields as $field => $match) - { - $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field; - $this->where[] = $this->driver->notregex($field, $match, 'OR ', count($this->where)); + // Create result + $result = new Database_Cache_Result($data, $sql, $this->config['object']); } - return $this; - } - - /** - * Chooses the column to group by in a select query. - * - * @param string column name to group by - * @return Database_Core This Database object. - */ - public function groupby($by) - { - if ( ! is_array($by)) - { - $by = explode(',', (string) $by); - } + // Stop the benchmark + $stop = microtime(TRUE); - foreach ($by as $val) + if ($this->config['benchmark'] === TRUE) { - $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); - } + // Benchmark the query + Database::$benchmarks[] = array('query' => $sql, 'time' => $stop - $start, 'rows' => count($result)); } - 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; + return $result; } /** - * Selects the or having(s) for a database query. + * Generates a hash for the given 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. + * @param string SQL query string + * @return string */ - public function orhaving($key, $value = '', $quote = TRUE) + protected function query_hash($sql) { - $this->having[] = $this->driver->where($key, $value, 'OR', count($this->having), TRUE); - return $this; + return sha1(str_replace("\n", ' ', trim($sql))); } /** - * Chooses which column(s) to order the select query by. + * Clears the internal query cache. * - * @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. + * @param mixed clear cache by SQL statement, NULL for all, or TRUE for last query + * @return Database */ - public function orderby($orderby, $direction = NULL) + public function clear_cache($sql = NULL) { - if ( ! is_array($orderby)) + if ($this->cache instanceof Cache) { - $orderby = array($orderby => $direction); + // Using cross-request Cache library + if ($sql === TRUE) + { + $this->cache->delete($this->query_hash($this->last_query)); + } + elseif (is_string($sql)) + { + $this->cache->delete($this->query_hash($sql)); + } + else + { + $this->cache->delete_all(); + } } - - foreach ($orderby as $column => $direction) + elseif (is_array($this->cache)) { - $direction = strtoupper(trim($direction)); - - // Add a direction if the provided one isn't valid - if ( ! in_array($direction, array('ASC', 'DESC', 'RAND()', 'RANDOM()', 'NULL'))) + // Using per-request memory cache + if ($sql === TRUE) { - $direction = 'ASC'; + unset($this->cache[$this->query_hash($this->last_query)]); } - - // Add the table prefix if a table.column was passed - if (strpos($column, '.')) + elseif (is_string($sql)) { - $column = $this->config['table_prefix'].$column; + unset($this->cache[$this->query_hash($sql)]); + } + else + { + $this->cache = array(); } - - $this->orderby[] = $this->driver->escape_column($column).' '.$direction; } - - return $this; } /** - * Selects the limit section of a query. + * Quotes the given value * - * @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. + * @param mixed value + * @return mixed */ - public function limit($limit, $offset = NULL) + public function quote($value) { - $this->limit = (int) $limit; + if ( ! $this->config['escape']) + return $value; - if ($offset !== NULL OR ! is_int($this->offset)) + if ($value === NULL) { - $this->offset($offset); + return 'NULL'; } - - 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)) + elseif ($value === TRUE) { - $key = array($key => $value); + return 'TRUE'; } - - foreach ($key as $k => $v) + elseif ($value === FALSE) { - // 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 'FALSE'; } - - 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 != '') + elseif (is_int($value)) { - $this->from($table); + return (int) $value; } - - if ( ! is_null($limit)) + elseif ($value instanceof Database_Expression) { - $this->limit($limit, $offset); + return (string) $value; } - $sql = $this->driver->compile_select(get_object_vars($this)); - - $this->reset_select(); - - $result = $this->query($sql); - - $this->last_query = $sql; - - return $result; + return '\''.$this->escape($value).'\''; } /** - * Compiles the select statement based on the other functions called and runs the query. + * Quotes a table, adding the table prefix + * Reserved characters not allowed in table names for the builder are [ .*] (space, dot, asterisk) * - * @param string table name - * @param array where clause - * @param string limit clause - * @param string offset 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 getwhere($table = '', $where = NULL, $limit = NULL, $offset = NULL) + public function quote_table($table, $alias = NULL) { - if ($table != '') + if (is_array($table)) { - $this->from($table); + // Using array('u' => 'user') + list($alias, $table) = each($table); } - - if ( ! is_null($where)) + elseif (strpos(' ', $table) !== FALSE) { - $this->where($where); + // Using format 'user u' + list($table, $alias) = explode(' ', $table); } - if ( ! is_null($limit)) + if ($table instanceof Database_Expression) { - $this->limit($limit, $offset); - } - - $sql = $this->driver->compile_select(get_object_vars($this)); - - $this->reset_select(); - - $result = $this->query($sql); - - return $result; - } + if ($alias) + { + if ($this->config['escape']) + { + $alias = $this->quote.$alias.$this->quote; + } - /** - * 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); - } + return $table.' AS '.$alias; + } - if ( ! is_null($limit)) - { - $this->limit($limit, $offset); + return (string) $table; } - $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)) + if ($this->config['table_prefix']) { - $this->set($set); + $table = $this->config['table_prefix'].$table; } - if ($this->set == NULL) - throw new Kohana_Database_Exception('database.must_use_set'); - - if ($table == '') + if ($alias) { - if ( ! isset($this->from[0])) - throw new Kohana_Database_Exception('database.must_use_table'); + if ($this->config['escape']) + { + $table = $this->quote.$table.$this->quote; + $alias = $this->quote.$alias.$this->quote; + } - $table = $this->from[0]; + return $table.' AS '.$alias; } - // 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)) + if ($this->config['escape']) { - $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); + $table = $this->quote.$table.$this->quote; } - $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; + return $table; } /** - * Adds a "NOT IN" condition to the where clause + * 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 Name of the column being examined - * @param mixed An array or string to match against - * @return Database_Core This Database object. + * @param string|array column name or array('u' => 'COUNT("*")') + * @param string column alias + * @return string */ - public function notin($field, $values) + public function quote_column($column, $alias = NULL) { - return $this->in($field, $values, TRUE); - } + if ($column === '*') + return $column; - /** - * 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)) + if (is_array($column)) { - $this->set($set); + list($alias, $column) = each($column); } - 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 + if ($column instanceof Database_Expression) { - $table = $this->config['table_prefix'].$table; - } - - $sql = $this->driver->merge($table, array_keys($this->set), array_values($this->set)); + if ($alias) + { + if ($this->config['escape']) + { + $alias = $this->quote.$alias.$this->quote; + } - $this->reset_write(); - return $this->query($sql); - } + return $column.' AS '.$alias; + } - /** - * 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); + return (string) $column; } - if ( ! is_null($where)) + if ($this->config['table_prefix'] AND strpos($column, '.') !== FALSE) { - $this->where($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; + } } - if ($this->set == FALSE) - throw new Kohana_Database_Exception('database.must_use_set'); - - if ($table == '') + if ($this->config['escape']) { - 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); + 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); + } - $this->reset_write(); - return $this->query($sql); - } + // Replace . with "." + $column = str_replace('.', $this->quote.'.'.$this->quote, $column); - /** - * 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'); + // Unescape any asterisks + $column = str_replace($this->quote.'*'.$this->quote, '*', $column); - $table = $this->from[0]; - } - else - { - $table = $this->config['table_prefix'].$table; - } + if ($alias) + { + // Quote the alias + return $column.' AS '.$this->quote.$alias.$this->quote; + } - if (! is_null($where)) - { - $this->where($where); + return $column; } - if (count($this->where) < 1) - throw new Kohana_Database_Exception('database.must_use_where'); - - $sql = $this->driver->delete($table, $this->where); + // Strip double quotes + $column = str_replace('"', '', $column); - $this->reset_write(); - return $this->query($sql); - } + if ($alias) + return $column.' AS '.$alias; - /** - * Returns the last query run. - * - * @return string SQL - */ - public function last_query() - { - return $this->last_query; + return $column; } /** - * Count query records. + * Get the table prefix * - * @param string table name - * @param array where clause - * @return integer + * @param string Optional new table prefix to set + * @return string */ - public function count_records($table = FALSE, $where = NULL) + public function table_prefix($new_prefix = NULL) { - if (count($this->from) < 1) - { - if ($table == FALSE) - throw new Kohana_Database_Exception('database.must_use_table'); - - $this->from($table); - } + $prefix = $this->config['table_prefix']; - if ($where !== NULL) + if ($new_prefix !== NULL) { - $this->where($where); + // Set a new prefix + $this->config['table_prefix'] = $new_prefix; } - $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; + return $prefix; } /** - * 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. + * Fetches SQL type information about a field, in a generic format. * + * @param string field datatype * @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) + protected function sql_type($str) { - if ($prefix) - return in_array($this->config['table_prefix'].$table_name, $this->list_tables()); - else - return in_array($table_name, $this->list_tables()); - } + static $sql_types; - /** - * 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 ($sql_types === NULL) { - // 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); + // Load SQL data types + $sql_types = Kohana::config('sql_types'); } - // Restore placeholders. - return str_replace('{%B%}', '?', $sql); - } + $str = trim($str); - /** - * 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); - } + if (($open = strpos($str, '(')) !== FALSE) + { + // Closing bracket + $close = strpos($str, ')', $open); - /** - * Returns table prefix of current configuration. - * - * @return string - */ - public function table_prefix() - { - return $this->config['table_prefix']; - } + // Length without brackets + $length = substr($str, $open + 1, $close - 1 - $open); - /** - * 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); + // Type without the length + $type = substr($str, 0, $open).substr($str, $close + 1); } else { - $this->driver->clear_cache(); + // No length + $type = $str; } - return $this; - } + if (empty($sql_types[$type])) + throw new Database_Exception('Undefined field type :type', array(':type' => $str)); - /** - * 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; - } + // Fetch the field definition + $field = $sql_types[$type]; - /** - * 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; - } + $field['sql_type'] = $type; - /** - * 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 (isset($length)) { - 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; + // Add the length to the field info + $field['length'] = $length; } - return FALSE; + return $field; } -} // 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 @@ +<?php defined('SYSPATH') or die('No direct script access.'); +/** + * Database builder + * + * @package Kohana + * @author Kohana Team + * @copyright (c) 2008-2009 Kohana Team + * @license http://kohanaphp.com/license + */ +class Database_Builder_Core { + + // Valid ORDER BY directions + protected $order_directions = array('ASC', 'DESC', 'RAND()'); + + // Database object + protected $db; + + // Builder members + protected $select = array(); + protected $from = array(); + protected $join = array(); + protected $where = array(); + protected $group_by = array(); + protected $having = array(); + protected $order_by = array(); + protected $limit = NULL; + protected $offset = NULL; + protected $set = array(); + protected $columns = array(); + protected $values = array(); + protected $type; + + // TTL for caching (using Cache library) + protected $ttl = FALSE; + + public function __construct($db = 'default') + { + $this->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 @@ +<?php defined('SYSPATH') or die('No direct script access.'); +/** + * Cached database result. + * + * $Id: Database_Cache_Result.php 4679 2009-11-10 01:45:52Z isaiah $ + * + * @package Kohana + * @author Kohana Team + * @copyright (c) 2008-2009 Kohana Team + * @license http://kohanaphp.com/license + */ +class Database_Cache_Result_Core extends Database_Result { + + /** + * Result data (array of rows) + * @var array + */ + protected $data; + + public function __construct($data, $sql, $return_objects) + { + $this->data = $data; + $this->sql = $sql; + $this->total_rows = count($data); + $this->return_objects = $return_objects; + } + + public function __destruct() + { + // Not used + } + + public function as_array($return = FALSE) + { + // Return arrays rather than objects + $this->return_objects = FALSE; + + if ( ! $return ) + { + // Return this result object + return $this; + } + + // Return the entire array of rows + return $this->data; + } + + public function as_object($class = NULL, $return = FALSE) + { + if ($class !== NULL) + throw new Database_Exception('Database cache results do not support object casting'); + + // Return objects of type $class (or stdClass if none given) + $this->return_objects = TRUE; + + return $this; + } + + public function seek($offset) + { + if ( ! $this->offsetExists($offset)) + return FALSE; + + $this->current_row = $offset; + + return TRUE; + } + + public function current() + { + if ($this->return_objects) + { + // Return a new object with the current row of data + return (object) $this->data[$this->current_row]; + } + else + { + // Return an array of the row + return $this->data[$this->current_row]; + } + } + +} // End Database_Cache_Result
\ No newline at end of file diff --git a/system/libraries/Database_Exception.php b/system/libraries/Database_Exception.php new file mode 100644 index 00000000..4a9cd83e --- /dev/null +++ b/system/libraries/Database_Exception.php @@ -0,0 +1,17 @@ +<?php defined('SYSPATH') or die('No direct script access.'); +/** + * Database exceptions. + * + * $Id: Database_Exception.php 4679 2009-11-10 01:45:52Z isaiah $ + * + * @package Kohana + * @author Kohana Team + * @copyright (c) 2008-2009 Kohana Team + * @license http://kohanaphp.com/license + */ +class Database_Exception_Core extends Kohana_Exception { + + // Database error code + protected $code = E_DATABASE_ERROR; + +} // End Database_Exception
\ No newline at end of file diff --git a/system/libraries/Database_Expression.php b/system/libraries/Database_Expression.php index 940a6363..3e21ea31 100644 --- a/system/libraries/Database_Expression.php +++ b/system/libraries/Database_Expression.php @@ -1,13 +1,13 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); +<?php defined('SYSPATH') or die('No direct script access.'); /** - * Database expression class to allow for explicit joins and where expressions. - * - * $Id: Database_Expression.php 4037 2009-03-04 23:35:53Z jheathco $ - * - * @package Core + * Database expression. + * + * $Id: Database_Expression.php 4679 2009-11-10 01:45:52Z isaiah $ + * + * @package Kohana * @author Kohana Team - * @copyright (c) 2007-2009 Kohana Team - * @license http://kohanaphp.com/license.html + * @copyright (c) 2008-2009 Kohana Team + * @license http://kohanaphp.com/license */ class Database_Expression_Core { @@ -20,7 +20,6 @@ class Database_Expression_Core { public function __toString() { - return (string) $this->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 @@ +<?php defined('SYSPATH') or die('No direct script access.'); +/** + * MySQL database connection. + * + * $Id: Database_Mysql.php 4684 2009-11-18 14:26:48Z isaiah $ + * + * @package Kohana + * @author Kohana Team + * @copyright (c) 2008-2009 Kohana Team + * @license http://kohanaphp.com/license + */ +class Database_Mysql_Core extends Database { + + // Quote character to use for identifiers (tables/columns/aliases) + protected $quote = '`'; + + // Use SET NAMES to set the character set + protected static $set_names; + + public function connect() + { + if ($this->connection) + return; + + if (Database_Mysql::$set_names === NULL) + { + // Determine if we can use mysql_set_charset(), which is only + // available on PHP 5.2.3+ when compiled against MySQL 5.0+ + Database_Mysql::$set_names = ! function_exists('mysql_set_charset'); + } + + extract($this->config['connection']); + + // 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 @@ +<?php defined('SYSPATH') or die('No direct script access.'); +/** + * MySQL database result. + * + * $Id: Database_Mysql_Result.php 4679 2009-11-10 01:45:52Z isaiah $ + * + * @package Kohana + * @author Kohana Team + * @copyright (c) 2008-2009 Kohana Team + * @license http://kohanaphp.com/license + */ +class Database_Mysql_Result_Core extends Database_Result { + + protected $internal_row = 0; + + public function __construct($result, $sql, $link, $return_objects) + { + if (is_resource($result)) + { + // True to return objects, false for arrays + $this->return_objects = $return_objects; + + $this->total_rows = mysql_num_rows($result); + } + elseif (is_bool($result)) + { + if ($result == FALSE) + { + throw new Database_Exception('#:errno: :error [ :query ]', + array(':error' => mysql_error($link), + ':query' => $sql, + ':errno' => mysql_errno($link))); + + } + else + { + // It's a DELETE, INSERT, REPLACE, or UPDATE query + $this->insert_id = mysql_insert_id($link); + $this->total_rows = mysql_affected_rows($link); + } + } + + // Store the result locally + $this->result = $result; + + $this->sql = $sql; + } + + public function __destruct() + { + if (is_resource($this->result)) + { + mysql_free_result($this->result); + } + } + + public function as_array($return = FALSE) + { + // Return arrays rather than objects + $this->return_objects = FALSE; + + if ( ! $return ) + { + // Return this result object + return $this; + } + + // Return a nested array of all results + $array = array(); + + if ($this->total_rows > 0) + { + // Seek to the beginning of the result + mysql_data_seek($this->result, 0); + + while ($row = mysql_fetch_assoc($this->result)) + { + // Add each row to the array + $array[] = $row; + } + + $this->internal_row = $this->total_rows; + } + + return $array; + } + + public function as_object($class = NULL, $return = FALSE) + { + // Return objects of type $class (or stdClass if none given) + $this->return_objects = ($class !== NULL) ? $class : TRUE; + + if ( ! $return ) + { + // Return this result object + return $this; + } + + // Return a nested array of all results + $array = array(); + + if ($this->total_rows > 0) + { + // Seek to the beginning of the result + mysql_data_seek($this->result, 0); + + if (is_string($this->return_objects)) + { + while ($row = mysql_fetch_object($this->result, $this->return_objects)) + { + // Add each row to the array + $array[] = $row; + } + } + else + { + while ($row = mysql_fetch_object($this->result)) + { + // Add each row to the array + $array[] = $row; + } + } + + $this->internal_row = $this->total_rows; + } + + return $array; + } + + /** + * SeekableIterator: seek + */ + public function seek($offset) + { + if ($this->offsetExists($offset) AND mysql_data_seek($this->result, $offset)) + { + // Set the current row to the offset + $this->current_row = $this->internal_row = $offset; + + return TRUE; + } + else + { + return FALSE; + } + } + + /** + * Iterator: current + */ + public function current() + { + if ($this->current_row !== $this->internal_row AND ! $this->seek($this->current_row)) + return NULL; + + ++$this->internal_row; + + if ($this->return_objects) + { + if (is_string($this->return_objects)) + { + return mysql_fetch_object($this->result, $this->return_objects); + } + else + { + return mysql_fetch_object($this->result); + } + } + else + { + // Return an array of the row + return mysql_fetch_assoc($this->result); + } + } + +} // End Database_MySQL_Result
\ No newline at end of file diff --git a/system/libraries/Database_Mysqli.php b/system/libraries/Database_Mysqli.php new file mode 100644 index 00000000..523fcb19 --- /dev/null +++ b/system/libraries/Database_Mysqli.php @@ -0,0 +1,92 @@ +<?php defined('SYSPATH') or die('No direct script access.'); +/** + * MySQL database connection. + * + * $Id: Database_Mysqli.php 4679 2009-11-10 01:45:52Z isaiah $ + * + * @package Kohana + * @author Kohana Team + * @copyright (c) 2008-2009 Kohana Team + * @license http://kohanaphp.com/license + */ + +define('RUNS_MYSQLND', function_exists('mysqli_fetch_all')); + +class Database_Mysqli_Core extends Database_Mysql { + + public function connect() + { + if (is_object($this->connection)) + return; + + extract($this->config['connection']); + + // Persistent connections are supported as of PHP 5.3 + if (RUNS_MYSQLND AND $this->config['persistent'] === TRUE) + { + $host = 'p:'.$host; + } + + $host = isset($host) ? $host : $socket; + + 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 @@ +<?php defined('SYSPATH') or die('No direct script access.'); +/** + * MySQL database result. + * + * $Id: Database_Mysqli_Result.php 4679 2009-11-10 01:45:52Z isaiah $ + * + * @package Kohana + * @author Kohana Team + * @copyright (c) 2008-2009 Kohana Team + * @license http://kohanaphp.com/license + */ +class Database_Mysqli_Result_Core extends Database_Result { + + protected $internal_row = 0; + + public function __construct($result, $sql, $link, $return_objects) + { + if (is_object($result)) + { + // True to return objects, false for arrays + $this->return_objects = $return_objects; + + $this->total_rows = $result->num_rows; + } + elseif (is_bool($result)) + { + if ($result == FALSE) + { + throw new Database_Exception('#:errno: :error [ :query ]', + array(':error' => $link->error, + ':query' => $sql, + ':errno' => $link->errno)); + } + else + { + // It's a DELETE, INSERT, REPLACE, or UPDATE query + $this->insert_id = $link->insert_id; + $this->total_rows = $link->affected_rows; + } + } + + // Store the result locally + $this->result = $result; + + $this->sql = $sql; + } + + public function __destruct() + { + if (is_object($this->result)) + { + $this->result->free(); + } + } + + public function as_array($return = FALSE) + { + // Return arrays rather than objects + $this->return_objects = FALSE; + + if ( ! $return ) + { + // Return this result object + return $this; + } + + // Return a nested array of all results + if (RUNS_MYSQLND) + return $this->result->fetch_all(MYSQLI_ASSOC); + + $array = array(); + + if ($this->total_rows > 0) + { + // Seek to the beginning of the result + $this->result->data_seek(0); + + while ($row = $this->result->fetch_assoc()) + { + // Add each row to the array + $array[] = $row; + } + $this->internal_row = $this->total_rows; + } + + return $array; + } + + public function as_object($class = NULL, $return = FALSE) + { + // Return objects of type $class (or stdClass if none given) + $this->return_objects = ($class !== NULL) ? $class : TRUE; + + if ( ! $return ) + { + // Return this result object + return $this; + } + + // Return a nested array of all results + $array = array(); + + if ($this->total_rows > 0) + { + // Seek to the beginning of the result + $this->result->data_seek(0); + + if (is_string($this->return_objects)) + { + while ($row = $this->result->fetch_object($this->return_objects)) + { + // Add each row to the array + $array[] = $row; + } + } + else + { + while ($row = $this->result->fetch_object()) + { + // Add each row to the array + $array[] = $row; + } + } + + $this->internal_row = $this->total_rows; + } + + return $array; + } + + /** + * SeekableIterator: seek + */ + public function seek($offset) + { + if ($this->offsetExists($offset) AND $this->result->data_seek($offset)) + { + // Set the current row to the offset + $this->current_row = $offset; + + return TRUE; + } + else + { + return FALSE; + } + } + + /** + * Iterator: current + */ + public function current() + { + if ($this->current_row !== $this->internal_row AND ! $this->seek($this->current_row)) + return NULL; + + ++$this->internal_row; + + if ($this->return_objects) + { + if (is_string($this->return_objects)) + { + return $this->result->fetch_object($this->return_objects); + } + else + { + return $this->result->fetch_object(); + } + } + else + { + // Return an array of the row + return $this->result->fetch_assoc(); + } + } + +} // End Database_MySQLi_Result
\ No newline at end of file diff --git a/system/libraries/Database_Query.php b/system/libraries/Database_Query.php new file mode 100644 index 00000000..d9399d66 --- /dev/null +++ b/system/libraries/Database_Query.php @@ -0,0 +1,95 @@ +<?php defined('SYSPATH') or die('No direct script access.'); +/** + * Database query wrapper. + * + * $Id: Database_Query.php 4679 2009-11-10 01:45:52Z isaiah $ + * + * @package Kohana + * @author Kohana Team + * @copyright (c) 2008-2009 Kohana Team + * @license http://kohanaphp.com/license + */ +class Database_Query_Core { + + protected $sql; + protected $params; + protected $ttl = FALSE; + + public function __construct($sql = NULL) + { + $this->sql = $sql; + } + + public function __toString() + { + // Return the SQL of this query + return $this->sql; + } + + public function sql($sql) + { + $this->sql = $sql; + + return $this; + } + + public function value($key, $value) + { + $this->params[$key] = $value; + + return $this; + } + + public function bind($key, & $value) + { + $this->params[$key] =& $value; + + return $this; + } + + public function execute($db = 'default') + { + if ( ! is_object($db)) + { + // Get the database instance + $db = Database::instance($db); + } + + // Import the SQL locally + $sql = $this->sql; + + if ( ! empty($this->params)) + { + // Quote all of the values + $params = array_map(array($db, 'quote'), $this->params); + + // Replace the values in the SQL + $sql = strtr($sql, $params); + } + + if ($this->ttl !== FALSE) + { + // Load the result from the cache + return $db->query_cache($sql, $this->ttl); + } + else + { + // Load the result (no caching) + return $db->query($sql); + } + } + + /** + * Set caching for the query + * + * @param mixed Time-to-live (FALSE to disable, NULL for Cache default, seconds otherwise) + * @return Database_Query + */ + public function cache($ttl = NULL) + { + $this->ttl = $ttl; + + return $this; + } + +} // End Database_Query
\ No newline at end of file diff --git a/system/libraries/Database_Result.php b/system/libraries/Database_Result.php new file mode 100644 index 00000000..cf2056f3 --- /dev/null +++ b/system/libraries/Database_Result.php @@ -0,0 +1,170 @@ +<?php defined('SYSPATH') or die('No direct script access.'); +/** + * Database result wrapper. + * + * $Id: Database_Result.php 4679 2009-11-10 01:45:52Z isaiah $ + * + * @package Kohana + * @author Kohana Team + * @copyright (c) 2008-2009 Kohana Team + * @license http://kohanaphp.com/license + */ +abstract class Database_Result_Core implements Countable, Iterator, SeekableIterator, ArrayAccess { + + protected $result; + + protected $total_rows = 0; + protected $current_row = 0; + protected $insert_id; + + // Return objects or arrays for each row + protected $return_objects; + + /** + * Sets the total number of rows and stores the result locally. + * + * @param mixed $result query result + * @param boolean $return_objects True for results as objects, false for arrays + * @return void + */ + abstract public function __construct($result, $sql, $link, $return_objects); + + /** + * Result destruction cleans up all open result sets. + */ + abstract public function __destruct(); + + /** + * Return arrays for reach result, or the entire set of results + * + * @param boolean $return True to return entire result array + * @return Database_Result|array + */ + abstract public function as_array($return = FALSE); + + /** + * Returns objects for each result + * + * @param string $class Class name to return objects as or NULL for stdClass + * @return Database_Result + */ + abstract public function as_object($class = NULL, $return = FALSE); + + /** + * Returns the insert id + * + * @return int + */ + public function insert_id() + { + return $this->insert_id; + } + + /** + * Return the named column from the current row. + * + * @param string Column name + * @return mixed + */ + public function get($name) + { + // Get the current row + $row = $this->current(); + + if ( ! $this->return_objects) + return $row[$name]; + + return $row->$name; + } + + /** + * Countable: count + */ + public function count() + { + return $this->total_rows; + } + + /** + * ArrayAccess: offsetExists + */ + public function offsetExists($offset) + { + return ($offset >= 0 AND $offset < $this->total_rows); + } + + /** + * ArrayAccess: offsetGet + */ + public function offsetGet($offset) + { + if ( ! $this->seek($offset)) + return NULL; + + return $this->current(); + } + + /** + * ArrayAccess: offsetSet + * + * @throws Kohana_Database_Exception + */ + final public function offsetSet($offset, $value) + { + throw new Kohana_Exception('Database results are read-only'); + } + + /** + * ArrayAccess: offsetUnset + * + * @throws Kohana_Database_Exception + */ + final public function offsetUnset($offset) + { + throw new Kohana_Exception('Database results are read-only'); + } + + /** + * Iterator: key + */ + public function key() + { + return $this->current_row; + } + + /** + * Iterator: next + */ + public function next() + { + ++$this->current_row; + return $this; + } + + /** + * Iterator: prev + */ + public function prev() + { + --$this->current_row; + return $this; + } + + /** + * Iterator: rewind + */ + public function rewind() + { + $this->current_row = 0; + return $this; + } + + /** + * Iterator: valid + */ + public function valid() + { + return $this->offsetExists($this->current_row); + } + +} // End Database_Result
\ No newline at end of file diff --git a/system/libraries/Encrypt.php b/system/libraries/Encrypt.php 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 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * Kohana event observer. Uses the SPL observer pattern. - * - * $Id: Event_Observer.php 3769 2008-12-15 00:48:56Z zombor $ - * - * @package Core - * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html - */ -abstract class Event_Observer implements SplObserver { - - // Calling object - protected $caller; - - /** - * Initializes a new observer and attaches the subject as the caller. - * - * @param object Event_Subject - * @return void - */ - public function __construct(SplSubject $caller) - { - // Update the caller - $this->update($caller); - } - - /** - * Updates the observer subject with a new caller. - * - * @chainable - * @param object Event_Subject - * @return object - */ - public function update(SplSubject $caller) - { - if ( ! ($caller instanceof Event_Subject)) - throw new Kohana_Exception('event.invalid_subject', get_class($caller), get_class($this)); - - // Update the caller - $this->caller = $caller; - - return $this; - } - - /** - * Detaches this observer from the subject. - * - * @chainable - * @return object - */ - public function remove() - { - // Detach this observer from the caller - $this->caller->detach($this); - - return $this; - } - - /** - * Notify the observer of a new message. This function must be defined in - * all observers and must take exactly one parameter of any type. - * - * @param mixed message string, object, or array - * @return void - */ - abstract public function notify($message); - -} // End Event Observer
\ No newline at end of file diff --git a/system/libraries/Event_Subject.php b/system/libraries/Event_Subject.php deleted file mode 100644 index d1ccc544..00000000 --- a/system/libraries/Event_Subject.php +++ /dev/null @@ -1,67 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * Kohana event subject. Uses the SPL observer pattern. - * - * $Id: Event_Subject.php 3769 2008-12-15 00:48:56Z zombor $ - * - * @package Core - * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html - */ -abstract class Event_Subject implements SplSubject { - - // Attached subject listeners - protected $listeners = array(); - - /** - * Attach an observer to the object. - * - * @chainable - * @param object Event_Observer - * @return object - */ - public function attach(SplObserver $obj) - { - if ( ! ($obj instanceof Event_Observer)) - throw new Kohana_Exception('eventable.invalid_observer', get_class($obj), get_class($this)); - - // Add a new listener - $this->listeners[spl_object_hash($obj)] = $obj; - - return $this; - } - - /** - * Detach an observer from the object. - * - * @chainable - * @param object Event_Observer - * @return object - */ - public function detach(SplObserver $obj) - { - // Remove the listener - unset($this->listeners[spl_object_hash($obj)]); - - return $this; - } - - /** - * Notify all attached observers of a new message. - * - * @chainable - * @param mixed message string, object, or array - * @return object - */ - public function notify($message) - { - foreach ($this->listeners as $obj) - { - $obj->notify($message); - } - - return $this; - } - -} // End Event Subject
\ No newline at end of file diff --git a/system/libraries/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 @@ +<?php defined('SYSPATH') OR die('No direct access allowed.'); +/** + * Kohana I18N System + * + * $Id: I18n.php 4679 2009-11-10 01:45:52Z isaiah $ + * + * @package Cache + * @author Kohana Team + * @copyright (c) 2007-2009 Kohana Team + * @license http://kohanaphp.com/license + */ + +/** + * Loads the configured driver and validates it. + * + * @param string Text to output + * @param array Key/Value pairs of arguments to replace in the string + * @return string Translated text + */ +function __($string, $args = NULL) +{ + // KOHANA_LOCALE is the default locale, in which all of Kohana's __() calls are written in + if (I18n::get_locale() != Kohana::LOCALE) + { + $string = I18n::get_text($string); + } + + if ($args === NULL) + return $string; + + return strtr($string, $args); +} + +class I18n_Core +{ + protected static $locale; + // All the translations will be cached in here, after the first call of get_text() + protected static $translations = array(); + + public static function set_locale($locale) + { + // Reset the translations array + I18n::$translations = array(); + + I18n::$locale = $locale; + } + + + /** + * + * Returns the locale. + * If $ext is true, the UTF8 extension gets returned as well, otherwise, just the language code. + * Defaults to true. + * + * @return The locale + * @param boolean $ext[optional] Get the Extension? + */ + public static function get_locale($ext = true) + { + if($ext) + return I18n::$locale; + else + return arr::get(explode('.', I18n::$locale), 0); + } + + + /** + * + * Translates $string into language I18n::$locale and caches all found translations on the first call + * + * @return The translated String + * @param string $string The String to translate + */ + public static function get_text($string) + { + if ( ! I18n::$translations) + { + $locale = explode('_', I18n::get_locale(FALSE)); + + // Get the translation files + $translation_files = Kohana::find_file('i18n', $locale[0]); + + if($local_translation_files = Kohana::find_file('i18n', $locale[0].'/'.$locale[1])) + $translation_files = array_merge($translation_files, $local_translation_files); + + if ($translation_files) + { + // Merge the translations + foreach ($translation_files as $file) + { + include $file; + I18n::$translations = array_merge(I18n::$translations, $translations); + } + } + } + + if (isset(I18n::$translations[$string])) + return I18n::$translations[$string]; + else + return $string; + } + +} diff --git a/system/libraries/Image.php b/system/libraries/Image.php index 08c2957c..dd1e28ab 100644 --- a/system/libraries/Image.php +++ b/system/libraries/Image.php @@ -3,12 +3,12 @@ * Manipulate images using standard methods such as resize, crop, rotate, etc. * This class must be re-initialized for every image you wish to manipulate. * - * $Id: Image.php 4072 2009-03-13 17:20:38Z jheathco $ + * $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 */ class Image_Core { @@ -17,10 +17,16 @@ class Image_Core { const AUTO = 2; const HEIGHT = 3; const WIDTH = 4; + // Flip Directions const HORIZONTAL = 5; const VERTICAL = 6; + // Orientations + const PORTRAIT = 7; + const LANDSCAPE = 8; + const SQUARE = 9; + // Allowed image types public static $allowed_types = array ( @@ -68,11 +74,11 @@ class Image_Core { ($check === NULL) and $check = function_exists('getimagesize'); if ($check === FALSE) - throw new Kohana_Exception('image.getimagesize_missing'); + throw new Kohana_Exception('The Image library requires the getimagesize() PHP function, which is not available in your installation.'); // Check to make sure the image exists if ( ! is_file($image)) - throw new Kohana_Exception('image.file_not_found', $image); + throw new Kohana_Exception('The specified image, :image:, was not found. Please verify that images exist by using file_exists() before manipulating them.', array(':image:' => $image)); // Disable error reporting, to prevent PHP warnings $ER = error_reporting(0); @@ -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 <chregu@bitflux.ch> | + // +----------------------------------------------------------------------+ + // + // Kohana Modifications: + // * Changed double quotes to single quotes, changed indenting and spacing + // * Removed magic_quotes stuff + // * Increased regex readability: + // * Used delimeters that aren't found in the pattern + // * Removed all unneeded escapes + // * Deleted U modifiers and swapped greediness where needed + // * Increased regex speed: + // * Made capturing parentheses non-capturing where possible + // * Removed parentheses where possible + // * Split up alternation alternatives + // * Made some quantifiers possessive + + // Fix &entity\n; + $data = str_replace(array('&','<','>'), 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: <span style="width: expression(alert('Ping!'));"></span> + $data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?expression[\x00-\x20]*\([^>]*+>#i', '$1>', $data); + $data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?behaviour[\x00-\x20]*\([^>]*+>#i', '$1>', $data); + $data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:*[^>]*+>#iu', '$1>', $data); + + // Remove namespaced elements (we do not need them) + $data = preg_replace('#</*\w+:\w[^>]*+>#i', '', $data); + + do { - 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('#</*(?:applet|b(?:ase|gsound|link)|embed|frame(?:set)?|i(?:frame|layer)|l(?:ayer|ink)|meta|object|s(?:cript|tyle)|title|xml)[^>]*+>#i', '', $data); + } + while ($old_data !== $data); - // Set configuration - $config = HTMLPurifier_Config::createDefault(); - $config->set('HTML', 'TidyLevel', 'none'); // Only XSS cleaning now - - // Run HTMLPurifier - $data = HTMLPurifier($data, $config); - break; - default: - // http://svn.bitflux.ch/repos/public/popoon/trunk/classes/externalinput.php - // +----------------------------------------------------------------------+ - // | Copyright (c) 2001-2006 Bitflux GmbH | - // +----------------------------------------------------------------------+ - // | Licensed under the Apache License, Version 2.0 (the "License"); | - // | you may not use this file except in compliance with the License. | - // | You may obtain a copy of the License at | - // | http://www.apache.org/licenses/LICENSE-2.0 | - // | Unless required by applicable law or agreed to in writing, software | - // | distributed under the License is distributed on an "AS IS" BASIS, | - // | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | - // | implied. See the License for the specific language governing | - // | permissions and limitations under the License. | - // +----------------------------------------------------------------------+ - // | Author: Christian Stocker <chregu@bitflux.ch> | - // +----------------------------------------------------------------------+ - // - // Kohana Modifications: - // * Changed double quotes to single quotes, changed indenting and spacing - // * Removed magic_quotes stuff - // * Increased regex readability: - // * Used delimeters that aren't found in the pattern - // * Removed all unneeded escapes - // * Deleted U modifiers and swapped greediness where needed - // * Increased regex speed: - // * Made capturing parentheses non-capturing where possible - // * Removed parentheses where possible - // * Split up alternation alternatives - // * Made some quantifiers possessive - - // Fix &entity\n; - $data = str_replace(array('&','<','>'), 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: <span style="width: expression(alert('Ping!'));"></span> - $data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?expression[\x00-\x20]*\([^>]*+>#i', '$1>', $data); - $data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?behaviour[\x00-\x20]*\([^>]*+>#i', '$1>', $data); - $data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:*[^>]*+>#iu', '$1>', $data); - - // Remove namespaced elements (we do not need them) - $data = preg_replace('#</*\w+:\w[^>]*+>#i', '', $data); - - do - { - // Remove really unwanted tags - $old_data = $data; - $data = preg_replace('#</*(?:applet|b(?:ase|gsound|link)|embed|frame(?:set)?|i(?:frame|layer)|l(?:ayer|ink)|meta|object|s(?:cript|tyle)|title|xml)[^>]*+>#i', '', $data); - } - while ($old_data !== $data); - break; + return $data; + } + + /** + * 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 @@ +<?php defined('SYSPATH') OR die('No direct access allowed.'); +/** + * Creates a "Page Not Found" exception. + * + * $Id: Kohana_404_Exception.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 + */ + +class Kohana_404_Exception_Core extends Kohana_Exception { + + protected $code = E_PAGE_NOT_FOUND; + + /** + * Set internal properties. + * + * @param string URI of page + * @param string custom error template + */ + public function __construct($page = NULL) + { + if ($page === NULL) + { + // Use the complete URI + $page = Router::$complete_uri; + } + + parent::__construct('The page you requested, %page%, could not be found.', array('%page%' => $page)); + } + + /** + * Throws a new 404 exception. + * + * @throws Kohana_404_Exception + * @return void + */ + public static function trigger($page = NULL) + { + throw new Kohana_404_Exception($page); + } + + /** + * Sends 404 headers, to emulate server behavior. + * + * @return void + */ + public function sendHeaders() + { + // Send the 404 header + header('HTTP/1.1 404 File Not Found'); + } + +} // End Kohana 404 Exception
\ No newline at end of file diff --git a/system/libraries/Kohana_Log.php b/system/libraries/Kohana_Log.php new file mode 100644 index 00000000..44ef8af8 --- /dev/null +++ b/system/libraries/Kohana_Log.php @@ -0,0 +1,90 @@ +<?php defined('SYSPATH') OR die('No direct access allowed.'); +/** + * Logging class. + * + * $Id: Kohana_Log.php 4679 2009-11-10 01:45:52Z isaiah $ + * + * @package Core + * @author Kohana Team + * @copyright (c) 2007-2009 Kohana Team + * @license http://kohanaphp.com/license + */ +class Kohana_Log_Core { + + // Configuration + protected static $config; + + // Drivers + protected static $drivers; + + // Logged messages + protected static $messages; + + /** + * Add a new message to the log. + * + * @param string type of message + * @param string message text + * @return void + */ + public static function add($type, $message) + { + // Make sure the drivers and config are loaded + if ( ! is_array(Kohana_Log::$config)) + { + Kohana_Log::$config = Kohana::config('log'); + } + + if ( ! is_array(Kohana_Log::$drivers)) + { + foreach ( (array) Kohana::config('log.drivers') as $driver_name) + { + // Set driver name + $driver = 'Log_'.ucfirst($driver_name).'_Driver'; + + // Load the driver + if ( ! Kohana::auto_load($driver)) + throw new Kohana_Exception('Log Driver Not Found: %driver%', array('%driver%' => $driver)); + + // Initialize the driver + $driver = new $driver(array_merge(Kohana::config('log'), Kohana::config('log_'.$driver_name))); + + // Validate the driver + if ( ! ($driver instanceof Log_Driver)) + throw new Kohana_Exception('%driver% does not implement the Log_Driver interface', array('%driver%' => $driver)); + + Kohana_Log::$drivers[] = $driver; + } + + // Always save logs on shutdown + Event::add('system.shutdown', array('Kohana_Log', 'save')); + } + + Kohana_Log::$messages[] = array('date' => time(), 'type' => $type, 'message' => $message); + } + + /** + * Save all currently logged messages. + * + * @return void + */ + public static function save() + { + if (empty(Kohana_Log::$messages)) + return; + + foreach (Kohana_Log::$drivers as $driver) + { + // We can't throw exceptions here or else we will get a + // Exception thrown without a stack frame error + try + { + $driver->save(Kohana_Log::$messages); + } + catch(Exception $e){} + } + + // Reset the messages + Kohana_Log::$messages = array(); + } +}
\ No newline at end of file diff --git a/system/libraries/Kohana_PHP_Exception.php b/system/libraries/Kohana_PHP_Exception.php new file mode 100644 index 00000000..fca5b30b --- /dev/null +++ b/system/libraries/Kohana_PHP_Exception.php @@ -0,0 +1,99 @@ +<?php defined('SYSPATH') OR die('No direct access allowed.'); +/** + * Kohana PHP Error Exceptions + * + * $Id: Kohana_PHP_Exception.php 4679 2009-11-10 01:45:52Z isaiah $ + * + * @package Core + * @author Kohana Team + * @copyright (c) 2007-2009 Kohana Team + * @license http://kohanaphp.com/license + */ + +class Kohana_PHP_Exception_Core extends Kohana_Exception { + + public static $enabled = FALSE; + + /** + * Enable Kohana PHP error handling. + * + * @return void + */ + public static function enable() + { + if ( ! Kohana_PHP_Exception::$enabled) + { + // Handle runtime errors + set_error_handler(array('Kohana_PHP_Exception', 'error_handler')); + + // Handle errors which halt execution + Event::add('system.shutdown', array('Kohana_PHP_Exception', 'shutdown_handler')); + + Kohana_PHP_Exception::$enabled = TRUE; + } + } + + /** + * Disable Kohana PHP error handling. + * + * @return void + */ + public static function disable() + { + if (Kohana_PHP_Exception::$enabled) + { + restore_error_handler(); + + Event::clear('system.shutdown', array('Kohana_PHP_Exception', 'shutdown_handler')); + + Kohana_PHP_Exception::$enabled = FALSE; + } + } + + /** + * Create a new PHP error exception. + * + * @return void + */ + public function __construct($code, $error, $file, $line, $context = NULL) + { + parent::__construct($error); + + // Set the error code, file, line, and context manually + $this->code = $code; + $this->file = $file; + $this->line = $line; + } + + /** + * PHP error handler. + * + * @throws Kohana_PHP_Exception + * @return void + */ + public static function error_handler($code, $error, $file, $line, $context = NULL) + { + // Respect error_reporting settings + if (error_reporting() & $code) + { + // Throw an exception + throw new Kohana_PHP_Exception($code, $error, $file, $line, $context); + } + } + + /** + * Catches errors that are not caught by the error handler, such as E_PARSE. + * + * @uses Kohana_Exception::handle() + * @return void + */ + public static function shutdown_handler() + { + if (Kohana_PHP_Exception::$enabled AND $error = error_get_last()) + { + // 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 @@ +<?php defined('SYSPATH') OR die('No direct access allowed.'); +/** + * Creates a custom exception message. + * + * $Id: Kohana_User_Exception.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 + */ + +class Kohana_User_Exception_Core extends Kohana_Exception { + + /** + * Set exception title and message. + * + * @param string exception title string + * @param string exception message string + * @param string custom error template + */ + public function __construct($title, $message, array $variables = NULL) + { + parent::__construct($message, $variables); + + // Code is the error title + $this->code = $title; + } + +} // End Kohana User Exception diff --git a/system/libraries/Model.php b/system/libraries/Model.php 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 @@ -1204,13 +1301,62 @@ class ORM_Core { */ 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 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * Object Relational Mapping (ORM) "tree" extension. Allows ORM objects to act - * as trees, with parents and children. - * - * $Id: ORM_Tree.php 3923 2009-01-22 15:37:04Z samsoir $ - * - * @package ORM - * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html - */ -class ORM_Tree_Core extends ORM { - - // Name of the child - protected $ORM_Tree_children; - - // Parent keyword name - protected $ORM_Tree_parent_key = 'parent_id'; - - /** - * Overload ORM::__get to support "parent" and "children" properties. - * - * @param string column name - * @return mixed - */ - public function __get($column) - { - if ($column === 'parent') - { - if (empty($this->related[$column])) - { - // Load child model - $model = ORM::factory(inflector::singular($this->ORM_Tree_children)); - - if (array_key_exists($this->ORM_Tree_parent_key, $this->object)) - { - // Find children of this parent - $model->where($model->primary_key, $this->object[$this->ORM_Tree_parent_key])->find(); - } - - $this->related[$column] = $model; - } - - return $this->related[$column]; - } - elseif ($column === 'children') - { - if (empty($this->related[$column])) - { - $model = ORM::factory(inflector::singular($this->ORM_Tree_children)); - - if ($this->ORM_Tree_children === $this->table_name) - { - // Load children within this table - $this->related[$column] = $model - ->where($this->ORM_Tree_parent_key, $this->object[$this->primary_key]) - ->find_all(); - } - else - { - // Find first selection of children - $this->related[$column] = $model - ->where($this->foreign_key(), $this->object[$this->primary_key]) - ->where($this->ORM_Tree_parent_key, NULL) - ->find_all(); - } - } - - return $this->related[$column]; - } - - return parent::__get($column); - } - -} // End ORM Tree
\ No newline at end of file diff --git a/system/libraries/ORM_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 @@ +<?php defined('SYSPATH') or die('No direct script access.'); +/** + * ORM Validation exceptions. + * + * $Id: ORM_Validation_Exception.php 4679 2009-11-10 01:45:52Z isaiah $ + * + * @package Kohana + * @author Kohana Team + * @copyright (c) 2008-2009 Kohana Team + * @license http://kohanaphp.com/license + */ +class ORM_Validation_Exception_Core extends Database_Exception { + + /** + * Handles Database Validation Exceptions + * + * @param Validation $array + * @return + */ + public static function handle_validation($table, Validation $array) + { + $exception = new ORM_Validation_Exception('ORM Validation has failed for :table model',array(':table'=>$table)); + $exception->validation = $array; + throw $exception; + } +} // End ORM_Validation_Exception
\ No newline at end of file diff --git a/system/libraries/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 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * Object Relational Mapping (ORM) "versioned" extension. Allows ORM objects to - * be revisioned instead of updated. - * - * $Id$ - * - * @package ORM - * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html - */ -class ORM_Versioned_Core extends ORM { - - protected $last_version = NULL; - - /** - * Overload ORM::save() to support versioned data - * - * @chainable - * @return ORM - */ - public function save() - { - $this->last_version = 1 + ($this->last_version === NULL ? $this->object['version'] : $this->last_version); - $this->__set('version', $this->last_version); - - parent::save(); - - if ($this->saved) - { - $data = array(); - foreach ($this->object as $key => $value) - { - if ($key === 'id') - continue; - - $data[$key] = $value; - } - $data[$this->foreign_key()] = $this->id; - - $this->db->insert($this->table_name.'_versions', $data); - } - - return $this; - } - - /** - * Loads previous version from current object - * - * @chainable - * @return ORM - */ - public function previous() - { - if ( ! $this->loaded) - return $this; - - $this->last_version = ($this->last_version === NULL) ? $this->object['version'] : $this->last_version; - $version = $this->last_version - 1; - - $query = $this->db - ->where($this->foreign_key(), $this->object[$this->primary_key]) - ->where('version', $version) - ->limit(1) - ->get($this->table_name.'_versions'); - - if ($query->count()) - { - $this->load_values($query->result(FALSE)->current()); - } - - return $this; - } - - /** - * Restores the object with data from stored version - * - * @param integer version number you want to restore - * @return ORM - */ - public function restore($version) - { - if ( ! $this->loaded) - return $this; - - $query = $this->db - ->where($this->foreign_key(), $this->object[$this->primary_key]) - ->where('version', $version) - ->limit(1) - ->get($this->table_name.'_versions'); - - if ($query->count()) - { - $row = $query->result(FALSE)->current(); - - foreach ($row as $key => $value) - { - if ($key === $this->primary_key OR $key === $this->foreign_key()) - { - // Do not overwrite the primary key - continue; - } - - if ($key === 'version') - { - // Always use the current version - $value = $this->version; - } - - $this->__set($key, $value); - } - - $this->save(); - } - - return $this; - } - - /** - * Overloads ORM::delete() to delete all versioned entries of current object - * and the object itself - * - * @param integer id of the object you want to delete - * @return ORM - */ - public function delete($id = NULL) - { - if ($id === NULL) - { - // Use the current object id - $id = $this->object[$this->primary_key]; - } - - if ($status = parent::delete($id)) - { - $this->db->where($this->foreign_key(), $id)->delete($this->table_name.'_versions'); - } - - return $status; - } - -}
\ No newline at end of file diff --git a/system/libraries/Pagination.php b/system/libraries/Pagination.php deleted file mode 100644 index a8f7bb19..00000000 --- a/system/libraries/Pagination.php +++ /dev/null @@ -1,236 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * Pagination library. - * - * $Id: Pagination.php 3769 2008-12-15 00:48:56Z zombor $ - * - * @package Core - * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html - */ -class Pagination_Core { - - // Config values - protected $base_url = ''; - protected $directory = 'pagination'; - protected $style = 'classic'; - protected $uri_segment = 3; - protected $query_string = ''; - protected $items_per_page = 20; - protected $total_items = 0; - protected $auto_hide = FALSE; - - // Autogenerated values - protected $url; - protected $current_page; - protected $total_pages; - protected $current_first_item; - protected $current_last_item; - protected $first_page; - protected $last_page; - protected $previous_page; - protected $next_page; - protected $sql_offset; - protected $sql_limit; - - /** - * Constructs and returns a new Pagination object. - * - * @param array configuration settings - * @return object - */ - public function factory($config = array()) - { - return new Pagination($config); - } - - /** - * Constructs a new Pagination object. - * - * @param array configuration settings - * @return void - */ - public function __construct($config = array()) - { - // No custom group name given - if ( ! isset($config['group'])) - { - $config['group'] = 'default'; - } - - // Pagination setup - $this->initialize($config); - - Kohana::log('debug', 'Pagination Library initialized'); - } - - /** - * Sets config values. - * - * @throws Kohana_Exception - * @param array configuration settings - * @return void - */ - public function initialize($config = array()) - { - // Load config group - if (isset($config['group'])) - { - // Load and validate config group - if ( ! is_array($group_config = Kohana::config('pagination.'.$config['group']))) - throw new Kohana_Exception('pagination.undefined_group', $config['group']); - - // All pagination config groups inherit default config group - if ($config['group'] !== 'default') - { - // Load and validate default config group - if ( ! is_array($default_config = Kohana::config('pagination.default'))) - throw new Kohana_Exception('pagination.undefined_group', 'default'); - - // Merge config group with default config group - $group_config += $default_config; - } - - // Merge custom config items with config group - $config += $group_config; - } - - // Assign config values to the object - foreach ($config as $key => $value) - { - if (property_exists($this, $key)) - { - $this->$key = $value; - } - } - - // Clean view directory - $this->directory = trim($this->directory, '/').'/'; - - // Build generic URL with page in query string - if ($this->query_string !== '') - { - // Extract current page - $this->current_page = isset($_GET[$this->query_string]) ? (int) $_GET[$this->query_string] : 1; - - // Insert {page} placeholder - $_GET[$this->query_string] = '{page}'; - - // Create full URL - $base_url = ($this->base_url === '') ? Router::$current_uri : $this->base_url; - $this->url = url::site($base_url).'?'.str_replace('%7Bpage%7D', '{page}', http_build_query($_GET)); - - // Reset page number - $_GET[$this->query_string] = $this->current_page; - } - - // Build generic URL with page as URI segment - else - { - // Use current URI if no base_url set - $this->url = ($this->base_url === '') ? Router::$segments : explode('/', trim($this->base_url, '/')); - - // Convert uri 'label' to corresponding integer if needed - if (is_string($this->uri_segment)) - { - if (($key = array_search($this->uri_segment, $this->url)) === FALSE) - { - // If uri 'label' is not found, auto add it to base_url - $this->url[] = $this->uri_segment; - $this->uri_segment = count($this->url) + 1; - } - else - { - $this->uri_segment = $key + 2; - } - } - - // Insert {page} placeholder - $this->url[$this->uri_segment - 1] = '{page}'; - - // Create full URL - $this->url = url::site(implode('/', $this->url)).Router::$query_string; - - // Extract current page - $this->current_page = URI::instance()->segment($this->uri_segment); - } - - // Core pagination values - $this->total_items = (int) max(0, $this->total_items); - $this->items_per_page = (int) max(1, $this->items_per_page); - $this->total_pages = (int) ceil($this->total_items / $this->items_per_page); - $this->current_page = (int) min(max(1, $this->current_page), max(1, $this->total_pages)); - $this->current_first_item = (int) min((($this->current_page - 1) * $this->items_per_page) + 1, $this->total_items); - $this->current_last_item = (int) min($this->current_first_item + $this->items_per_page - 1, $this->total_items); - - // If there is no first/last/previous/next page, relative to the - // current page, value is set to FALSE. Valid page number otherwise. - $this->first_page = ($this->current_page === 1) ? FALSE : 1; - $this->last_page = ($this->current_page >= $this->total_pages) ? FALSE : $this->total_pages; - $this->previous_page = ($this->current_page > 1) ? $this->current_page - 1 : FALSE; - $this->next_page = ($this->current_page < $this->total_pages) ? $this->current_page + 1 : FALSE; - - // SQL values - $this->sql_offset = (int) ($this->current_page - 1) * $this->items_per_page; - $this->sql_limit = sprintf(' LIMIT %d OFFSET %d ', $this->items_per_page, $this->sql_offset); - } - - /** - * Generates the HTML for the chosen pagination style. - * - * @param string pagination style - * @return string pagination html - */ - public function render($style = NULL) - { - // Hide single page pagination - if ($this->auto_hide === TRUE AND $this->total_pages <= 1) - return ''; - - if ($style === NULL) - { - // Use default style - $style = $this->style; - } - - // Return rendered pagination view - return View::factory($this->directory.$style, get_object_vars($this))->render(); - } - - /** - * Magically converts Pagination object to string. - * - * @return string pagination html - */ - public function __toString() - { - return $this->render(); - } - - /** - * Magically gets a pagination variable. - * - * @param string variable key - * @return mixed variable value if the key is found - * @return void if the key is not found - */ - public function __get($key) - { - if (isset($this->$key)) - return $this->$key; - } - - /** - * Adds a secondary interface for accessing properties, e.g. $pagination->total_pages(). - * Note that $pagination->total_pages is the recommended way to access properties. - * - * @param string function name - * @return string - */ - public function __call($func, $args = NULL) - { - return $this->__get($func); - } - -} // End Pagination Class
\ No newline at end of file diff --git a/system/libraries/Profiler.php b/system/libraries/Profiler.php 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 @@ -180,28 +179,6 @@ class View_Core { } /** - * Sets a view global variable. - * - * @param string|array name of variable or an array of variables - * @param mixed value when using a named variable - * @return void - */ - public static function set_global($name, $value = NULL) - { - if (is_array($name)) - { - foreach ($name as $key => $value) - { - View::$kohana_global_data[$key] = $value; - } - } - else - { - View::$kohana_global_data[$name] = $value; - } - } - - /** * Magically sets a view variable. * * @param string variable key @@ -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 @@ <?php defined('SYSPATH') OR die('No direct access allowed.'); /** - * Cache driver interface. + * Cache driver abstract class. * - * $Id: Cache.php 4046 2009-03-05 19:23:29Z 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 */ -interface Cache_Driver { - +abstract class Cache_Driver { /** - * Set a cache item. + * Set cache items */ - public function set($id, $data, array $tags = NULL, $lifetime); + abstract public function set($items, $tags = NULL, $lifetime = NULL); /** - * Find all of the cache ids for a given tag. + * Get a cache items by key */ - public function find($tag); + abstract public function get($keys, $single = FALSE); /** - * Get a cache item. - * Return NULL if the cache item is not found. + * Get cache items by tag */ - public function get($id); + abstract public function get_tag($tags); /** - * Delete cache items by id or tag. + * Delete cache item by key */ - public function delete($id, $tag = FALSE); + abstract public function delete($keys); /** - * Deletes all expired cache items. + * Delete cache items by tag */ - public function delete_expired(); + abstract public function delete_tag($tags); + /** + * Empty the cache + */ + abstract public function delete_all(); } // End Cache Driver
\ No newline at end of file diff --git a/system/libraries/drivers/Cache/Apc.php b/system/libraries/drivers/Cache/Apc.php deleted file mode 100644 index f7be048f..00000000 --- a/system/libraries/drivers/Cache/Apc.php +++ /dev/null @@ -1,64 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * APC-based Cache driver. - * - * $Id: Apc.php 4046 2009-03-05 19:23:29Z Shadowhand $ - * - * @package Cache - * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html - */ -class Cache_Apc_Driver implements Cache_Driver { - - public function __construct() - { - if ( ! extension_loaded('apc')) - throw new Kohana_Exception('cache.extension_not_loaded', 'apc'); - } - - public function get($id) - { - return (($return = apc_fetch($id)) === FALSE) ? NULL : $return; - } - - public function set($id, $data, array $tags = NULL, $lifetime) - { - if ( ! empty($tags)) - { - Kohana::log('error', 'Cache: tags are unsupported by the APC driver'); - } - - return apc_store($id, $data, $lifetime); - } - - public function find($tag) - { - Kohana::log('error', 'Cache: tags are unsupported by the APC driver'); - - return array(); - } - - public function delete($id, $tag = FALSE) - { - if ($tag === TRUE) - { - Kohana::log('error', 'Cache: tags are unsupported by the APC driver'); - return FALSE; - } - elseif ($id === TRUE) - { - return apc_clear_cache('user'); - } - else - { - return apc_delete($id); - } - } - - public function delete_expired() - { - return TRUE; - } - -} // End Cache APC Driver
\ No newline at end of file diff --git a/system/libraries/drivers/Cache/Eaccelerator.php b/system/libraries/drivers/Cache/Eaccelerator.php deleted file mode 100644 index a45616d5..00000000 --- a/system/libraries/drivers/Cache/Eaccelerator.php +++ /dev/null @@ -1,66 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * Eaccelerator-based Cache driver. - * - * $Id: Eaccelerator.php 4046 2009-03-05 19:23:29Z Shadowhand $ - * - * @package Cache - * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html - */ -class Cache_Eaccelerator_Driver implements Cache_Driver { - - public function __construct() - { - if ( ! extension_loaded('eaccelerator')) - throw new Kohana_Exception('cache.extension_not_loaded', 'eaccelerator'); - } - - public function get($id) - { - return eaccelerator_get($id); - } - - public function find($tag) - { - Kohana::log('error', 'tags are unsupported by the eAccelerator driver'); - - return array(); - } - - public function set($id, $data, array $tags = NULL, $lifetime) - { - if ( ! empty($tags)) - { - Kohana::log('error', 'tags are unsupported by the eAccelerator driver'); - } - - return eaccelerator_put($id, $data, $lifetime); - } - - public function delete($id, $tag = FALSE) - { - if ($tag === TRUE) - { - Kohana::log('error', 'tags are unsupported by the eAccelerator driver'); - return FALSE; - } - elseif ($id === TRUE) - { - return eaccelerator_clean(); - } - else - { - return eaccelerator_rm($id); - } - } - - public function delete_expired() - { - eaccelerator_gc(); - - return TRUE; - } - -} // End Cache eAccelerator Driver
\ No newline at end of file diff --git a/system/libraries/drivers/Cache/File.php b/system/libraries/drivers/Cache/File.php index cc9d48d3..fc20c22d 100644 --- a/system/libraries/drivers/Cache/File.php +++ b/system/libraries/drivers/Cache/File.php @@ -1,55 +1,54 @@ <?php defined('SYSPATH') OR die('No direct access allowed.'); /** - * File-based Cache driver. + * Memcache-based Cache driver. * - * $Id: File.php 4046 2009-03-05 19:23:29Z Shadowhand $ + * $Id: File.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_File_Driver implements Cache_Driver { +class Cache_File_Driver extends Cache_Driver { + protected $config; + protected $backend; - protected $directory = ''; - - /** - * Tests that the storage location is a directory and is writable. - */ - public function __construct($directory) + public function __construct($config) { - // Find the real path to the directory - $directory = str_replace('\\', '/', realpath($directory)).'/'; + $this->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 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * SQLite-based Cache driver. - * - * $Id: Sqlite.php 4046 2009-03-05 19:23:29Z Shadowhand $ - * - * @package Cache - * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html - */ -class Cache_Sqlite_Driver implements Cache_Driver { - - // SQLite database instance - protected $db; - - // Database error messages - protected $error; - - /** - * Logs an SQLite error. - */ - protected static function log_error($code) - { - // Log an error - Kohana::log('error', 'Cache: SQLite error: '.sqlite_error_string($error)); - } - - /** - * Tests that the storage location is a directory and is writable. - */ - public function __construct($filename) - { - // Get the directory name - $directory = str_replace('\\', '/', realpath(pathinfo($filename, PATHINFO_DIRNAME))).'/'; - - // Set the filename from the real directory path - $filename = $directory.basename($filename); - - // Make sure the cache directory is writable - if ( ! is_dir($directory) OR ! is_writable($directory)) - throw new Kohana_Exception('cache.unwritable', $directory); - - // Make sure the cache database is writable - if (is_file($filename) AND ! is_writable($filename)) - throw new Kohana_Exception('cache.unwritable', $filename); - - // Open up an instance of the database - $this->db = new SQLiteDatabase($filename, '0666', $error); - - // Throw an exception if there's an error - if ( ! empty($error)) - throw new Kohana_Exception('cache.driver_error', sqlite_error_string($error)); - - $query = "SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'caches'"; - $tables = $this->db->query($query, SQLITE_BOTH, $error); - - // Throw an exception if there's an error - if ( ! empty($error)) - throw new Kohana_Exception('cache.driver_error', sqlite_error_string($error)); - - if ($tables->numRows() == 0) - { - Kohana::log('error', 'Cache: Initializing new SQLite cache database'); - - // Issue a CREATE TABLE command - $this->db->unbufferedQuery(Kohana::config('cache_sqlite.schema')); - } - } - - /** - * Checks if a cache id is already set. - * - * @param string cache id - * @return boolean - */ - public function exists($id) - { - // Find the id that matches - $query = "SELECT id FROM caches WHERE id = '$id'"; - - return ($this->db->query($query)->numRows() > 0); - } - - /** - * Sets a cache item to the given data, tags, and lifetime. - * - * @param string cache id to set - * @param string data in the cache - * @param array cache tags - * @param integer lifetime - * @return bool - */ - public function set($id, $data, array $tags = NULL, $lifetime) - { - // Serialize and escape the data - $data = sqlite_escape_string(serialize($data)); - - if ( ! empty($tags)) - { - // Escape the tags, adding brackets so the tag can be explicitly matched - $tags = sqlite_escape_string('<'.implode('>,<', $tags).'>'); - } - - // Cache Sqlite driver expects unix timestamp - if ($lifetime !== 0) - { - $lifetime += time(); - } - - $query = $this->exists($id) - ? "UPDATE caches SET tags = '$tags', expiration = '$lifetime', cache = '$data' WHERE id = '$id'" - : "INSERT INTO caches VALUES('$id', '$tags', '$lifetime', '$data')"; - - // Run the query - $this->db->unbufferedQuery($query, SQLITE_BOTH, $error); - - if ( ! empty($error)) - { - self::log_error($error); - return FALSE; - } - else - { - return TRUE; - } - } - - /** - * Finds an array of ids for a given tag. - * - * @param string tag name - * @return array of ids that match the tag - */ - public function find($tag) - { - $query = "SELECT id,cache FROM caches WHERE tags LIKE '%<{$tag}>%'"; - $query = $this->db->query($query, SQLITE_BOTH, $error); - - // An array will always be returned - $result = array(); - - if ( ! empty($error)) - { - self::log_error($error); - } - elseif ($query->numRows() > 0) - { - // Disable notices for unserializing - $ER = error_reporting(~E_NOTICE); - - while ($row = $query->fetchObject()) - { - // Add each cache to the array - $result[$row->id] = unserialize($row->cache); - } - - // Turn notices back on - error_reporting($ER); - } - - return $result; - } - - /** - * Fetches a cache item. This will delete the item if it is expired or if - * the hash does not match the stored hash. - * - * @param string cache id - * @return mixed|NULL - */ - public function get($id) - { - $query = "SELECT id, expiration, cache FROM caches WHERE id = '$id' LIMIT 0, 1"; - $query = $this->db->query($query, SQLITE_BOTH, $error); - - if ( ! empty($error)) - { - self::log_error($error); - } - elseif ($cache = $query->fetchObject()) - { - // Make sure the expiration is valid and that the hash matches - if ($cache->expiration != 0 AND $cache->expiration <= time()) - { - // Cache is not valid, delete it now - $this->delete($cache->id); - } - else - { - // Disable notices for unserializing - $ER = error_reporting(~E_NOTICE); - - // Return the valid cache data - $data = $cache->cache; - - // Turn notices back on - error_reporting($ER); - } - } - - // No valid cache found - return NULL; - } - - /** - * Deletes a cache item by id or tag - * - * @param string cache id or tag, or TRUE for "all items" - * @param bool delete a tag - * @return bool - */ - public function delete($id, $tag = FALSE) - { - if ($id === TRUE) - { - // Delete all caches - $where = '1'; - } - elseif ($tag === TRUE) - { - // Delete by tag - $where = "tags LIKE '%<{$id}>%'"; - } - else - { - // Delete by id - $where = "id = '$id'"; - } - - $this->db->unbufferedQuery('DELETE FROM caches WHERE '.$where, SQLITE_BOTH, $error); - - if ( ! empty($error)) - { - self::log_error($error); - return FALSE; - } - else - { - return TRUE; - } - } - - /** - * Deletes all cache files that are older than the current time. - */ - public function delete_expired() - { - // Delete all expired caches - $query = 'DELETE FROM caches WHERE expiration != 0 AND expiration <= '.time(); - - $this->db->unbufferedQuery($query); - - return TRUE; - } - -} // End Cache SQLite Driver
\ No newline at end of file diff --git a/system/libraries/drivers/Cache/Xcache.php b/system/libraries/drivers/Cache/Xcache.php index 6254bbb6..ad11e6d7 100644 --- a/system/libraries/drivers/Cache/Xcache.php +++ b/system/libraries/drivers/Cache/Xcache.php @@ -1,85 +1,128 @@ <?php defined('SYSPATH') OR die('No direct access allowed.'); /** - * Xcache Cache driver. - * - * $Id: Xcache.php 4046 2009-03-05 19:23:29Z Shadowhand $ + * XCache-based Cache driver. + * + * $Id: Memcache.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 + * @TODO Check if XCache cleans its own keys. */ -class Cache_Xcache_Driver implements Cache_Driver { +class Cache_Xcache_Driver extends Cache_Driver { + protected $config; - public function __construct() + public function __construct($config) { if ( ! extension_loaded('xcache')) - throw new Kohana_Exception('cache.extension_not_loaded', 'xcache'); - } - - public function get($id) - { - if (xcache_isset($id)) - return xcache_get($id); + throw new Kohana_Exception('The xcache PHP extension must be loaded to use this driver.'); - return NULL; + $this->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 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * Captcha driver class. - * - * $Id: Captcha.php 3769 2008-12-15 00:48:56Z zombor $ - * - * @package Captcha - * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html - */ -abstract class Captcha_Driver { - - // The correct Captcha challenge answer - protected $response; - - // Image resource identifier and type ("png", "gif" or "jpeg") - protected $image; - protected $image_type = 'png'; - - /** - * Constructs a new challenge. - * - * @return void - */ - public function __construct() - { - // Generate a new challenge - $this->response = $this->generate_challenge(); - - // Store the correct Captcha response in a session - Event::add('system.post_controller', array($this, 'update_response_session')); - } - - /** - * Generate a new Captcha challenge. - * - * @return string the challenge answer - */ - abstract public function generate_challenge(); - - /** - * Output the Captcha challenge. - * - * @param boolean html output - * @return mixed the rendered Captcha (e.g. an image, riddle, etc.) - */ - abstract public function render($html); - - /** - * Stores the response for the current Captcha challenge in a session so it is available - * on the next page load for Captcha::valid(). This method is called after controller - * execution (in the system.post_controller event) in order not to overwrite itself too soon. - * - * @return void - */ - public function update_response_session() - { - Session::instance()->set('captcha_response', sha1(strtoupper($this->response))); - } - - /** - * Validates a Captcha response from a user. - * - * @param string captcha response - * @return boolean - */ - public function valid($response) - { - return (sha1(strtoupper($response)) === Session::instance()->get('captcha_response')); - } - - /** - * Returns the image type. - * - * @param string filename - * @return string|FALSE image type ("png", "gif" or "jpeg") - */ - public function image_type($filename) - { - switch (strtolower(substr(strrchr($filename, '.'), 1))) - { - case 'png': - return 'png'; - - case 'gif': - return 'gif'; - - case 'jpg': - case 'jpeg': - // Return "jpeg" and not "jpg" because of the GD2 function names - return 'jpeg'; - - default: - return FALSE; - } - } - - /** - * Creates an image resource with the dimensions specified in config. - * If a background image is supplied, the image dimensions are used. - * - * @throws Kohana_Exception if no GD2 support - * @param string path to the background image file - * @return void - */ - public function image_create($background = NULL) - { - // Check for GD2 support - if ( ! function_exists('imagegd2')) - throw new Kohana_Exception('captcha.requires_GD2'); - - // Create a new image (black) - $this->image = imagecreatetruecolor(Captcha::$config['width'], Captcha::$config['height']); - - // Use a background image - if ( ! empty($background)) - { - // Create the image using the right function for the filetype - $function = 'imagecreatefrom'.$this->image_type($background); - $this->background_image = $function($background); - - // Resize the image if needed - if (imagesx($this->background_image) !== Captcha::$config['width'] - OR imagesy($this->background_image) !== Captcha::$config['height']) - { - imagecopyresampled - ( - $this->image, $this->background_image, 0, 0, 0, 0, - Captcha::$config['width'], Captcha::$config['height'], - imagesx($this->background_image), imagesy($this->background_image) - ); - } - - // Free up resources - imagedestroy($this->background_image); - } - } - - /** - * Fills the background with a gradient. - * - * @param resource gd image color identifier for start color - * @param resource gd image color identifier for end color - * @param string direction: 'horizontal' or 'vertical', 'random' by default - * @return void - */ - public function image_gradient($color1, $color2, $direction = NULL) - { - $directions = array('horizontal', 'vertical'); - - // Pick a random direction if needed - if ( ! in_array($direction, $directions)) - { - $direction = $directions[array_rand($directions)]; - - // Switch colors - if (mt_rand(0, 1) === 1) - { - $temp = $color1; - $color1 = $color2; - $color2 = $temp; - } - } - - // Extract RGB values - $color1 = imagecolorsforindex($this->image, $color1); - $color2 = imagecolorsforindex($this->image, $color2); - - // Preparations for the gradient loop - $steps = ($direction === 'horizontal') ? Captcha::$config['width'] : Captcha::$config['height']; - - $r1 = ($color1['red'] - $color2['red']) / $steps; - $g1 = ($color1['green'] - $color2['green']) / $steps; - $b1 = ($color1['blue'] - $color2['blue']) / $steps; - - if ($direction === 'horizontal') - { - $x1 =& $i; - $y1 = 0; - $x2 =& $i; - $y2 = Captcha::$config['height']; - } - else - { - $x1 = 0; - $y1 =& $i; - $x2 = Captcha::$config['width']; - $y2 =& $i; - } - - // Execute the gradient loop - for ($i = 0; $i <= $steps; $i++) - { - $r2 = $color1['red'] - floor($i * $r1); - $g2 = $color1['green'] - floor($i * $g1); - $b2 = $color1['blue'] - floor($i * $b1); - $color = imagecolorallocate($this->image, $r2, $g2, $b2); - - imageline($this->image, $x1, $y1, $x2, $y2, $color); - } - } - - /** - * Returns the img html element or outputs the image to the browser. - * - * @param boolean html output - * @return mixed html string or void - */ - public function image_render($html) - { - // Output html element - if ($html) - return '<img alt="Captcha" src="'.url::site('captcha/'.Captcha::$config['group']).'" width="'.Captcha::$config['width'].'" height="'.Captcha::$config['height'].'" />'; - - // Send the correct HTTP header - header('Content-Type: image/'.$this->image_type); - - // Pick the correct output function - $function = 'image'.$this->image_type; - $function($this->image); - - // Free up resources - imagedestroy($this->image); - } - -} // End Captcha Driver
\ No newline at end of file diff --git a/system/libraries/drivers/Captcha/Alpha.php b/system/libraries/drivers/Captcha/Alpha.php deleted file mode 100644 index 27795804..00000000 --- a/system/libraries/drivers/Captcha/Alpha.php +++ /dev/null @@ -1,92 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * Captcha driver for "alpha" style. - * - * $Id: Alpha.php 4367 2009-05-27 21:23:57Z samsoir $ - * - * @package Captcha - * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html - */ -class Captcha_Alpha_Driver extends Captcha_Driver { - - /** - * Generates a new Captcha challenge. - * - * @return string the challenge answer - */ - public function generate_challenge() - { - // Complexity setting is used as character count - return text::random('distinct', max(1, Captcha::$config['complexity'])); - } - - /** - * Outputs the Captcha image. - * - * @param boolean html output - * @return mixed - */ - public function render($html) - { - // Creates $this->image - $this->image_create(Captcha::$config['background']); - - // Add a random gradient - if (empty(Captcha::$config['background'])) - { - $color1 = imagecolorallocate($this->image, mt_rand(0, 100), mt_rand(0, 100), mt_rand(0, 100)); - $color2 = imagecolorallocate($this->image, mt_rand(0, 100), mt_rand(0, 100), mt_rand(0, 100)); - $this->image_gradient($color1, $color2); - } - - // Add a few random circles - for ($i = 0, $count = mt_rand(10, Captcha::$config['complexity'] * 3); $i < $count; $i++) - { - $color = imagecolorallocatealpha($this->image, mt_rand(0, 255), mt_rand(0, 255), mt_rand(0, 255), mt_rand(80, 120)); - $size = mt_rand(5, Captcha::$config['height'] / 3); - imagefilledellipse($this->image, mt_rand(0, Captcha::$config['width']), mt_rand(0, Captcha::$config['height']), $size, $size, $color); - } - - // Calculate character font-size and spacing - $default_size = min(Captcha::$config['width'], Captcha::$config['height'] * 2) / strlen($this->response); - $spacing = (int) (Captcha::$config['width'] * 0.9 / strlen($this->response)); - - // Background alphabetic character attributes - $color_limit = mt_rand(96, 160); - $chars = 'ABEFGJKLPQRTVY'; - - // Draw each Captcha character with varying attributes - for ($i = 0, $strlen = strlen($this->response); $i < $strlen; $i++) - { - // Use different fonts if available - $font = Captcha::$config['fontpath'].Captcha::$config['fonts'][array_rand(Captcha::$config['fonts'])]; - - $angle = mt_rand(-40, 20); - // Scale the character size on image height - $size = $default_size / 10 * mt_rand(8, 12); - $box = imageftbbox($size, $angle, $font, $this->response[$i]); - - // Calculate character starting coordinates - $x = $spacing / 4 + $i * $spacing; - $y = Captcha::$config['height'] / 2 + ($box[2] - $box[5]) / 4; - - // Draw captcha text character - // Allocate random color, size and rotation attributes to text - $color = imagecolorallocate($this->image, mt_rand(150, 255), mt_rand(200, 255), mt_rand(0, 255)); - - // Write text character to image - imagefttext($this->image, $size, $angle, $x, $y, $color, $font, $this->response[$i]); - - // Draw "ghost" alphabetic character - $text_color = imagecolorallocatealpha($this->image, mt_rand($color_limit + 8, 255), mt_rand($color_limit + 8, 255), mt_rand($color_limit + 8, 255), mt_rand(70, 120)); - $char = $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 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * Captcha driver for "basic" style. - * - * $Id: Basic.php 3769 2008-12-15 00:48:56Z zombor $ - * - * @package Captcha - * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html - */ -class Captcha_Basic_Driver extends Captcha_Driver { - - /** - * Generates a new Captcha challenge. - * - * @return string the challenge answer - */ - public function generate_challenge() - { - // Complexity setting is used as character count - return text::random('distinct', max(1, Captcha::$config['complexity'])); - } - - /** - * Outputs the Captcha image. - * - * @param boolean html output - * @return mixed - */ - public function render($html) - { - // Creates $this->image - $this->image_create(Captcha::$config['background']); - - // Add a random gradient - if (empty(Captcha::$config['background'])) - { - $color1 = imagecolorallocate($this->image, mt_rand(200, 255), mt_rand(200, 255), mt_rand(150, 255)); - $color2 = imagecolorallocate($this->image, mt_rand(200, 255), mt_rand(200, 255), mt_rand(150, 255)); - $this->image_gradient($color1, $color2); - } - - // Add a few random lines - for ($i = 0, $count = mt_rand(5, Captcha::$config['complexity'] * 4); $i < $count; $i++) - { - $color = imagecolorallocatealpha($this->image, mt_rand(0, 255), mt_rand(0, 255), mt_rand(100, 255), mt_rand(50, 120)); - imageline($this->image, mt_rand(0, Captcha::$config['width']), 0, mt_rand(0, Captcha::$config['width']), Captcha::$config['height'], $color); - } - - // Calculate character font-size and spacing - $default_size = min(Captcha::$config['width'], Captcha::$config['height'] * 2) / (strlen($this->response) + 1); - $spacing = (int) (Captcha::$config['width'] * 0.9 / strlen($this->response)); - - // Draw each Captcha character with varying attributes - for ($i = 0, $strlen = strlen($this->response); $i < $strlen; $i++) - { - // Use different fonts if available - $font = Captcha::$config['fontpath'].Captcha::$config['fonts'][array_rand(Captcha::$config['fonts'])]; - - // Allocate random color, size and rotation attributes to text - $color = imagecolorallocate($this->image, mt_rand(0, 150), mt_rand(0, 150), mt_rand(0, 150)); - $angle = mt_rand(-40, 20); - - // Scale the character size on image height - $size = $default_size / 10 * mt_rand(8, 12); - $box = imageftbbox($size, $angle, $font, $this->response[$i]); - - // Calculate character starting coordinates - $x = $spacing / 4 + $i * $spacing; - $y = Captcha::$config['height'] / 2 + ($box[2] - $box[5]) / 4; - - // Write text character to image - imagefttext($this->image, $size, $angle, $x, $y, $color, $font, $this->response[$i]); - } - - // Output - return $this->image_render($html); - } - -} // End Captcha Basic Driver Class
\ No newline at end of file diff --git a/system/libraries/drivers/Captcha/Black.php b/system/libraries/drivers/Captcha/Black.php deleted file mode 100644 index 6a2e2226..00000000 --- a/system/libraries/drivers/Captcha/Black.php +++ /dev/null @@ -1,72 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * Captcha driver for "black" style. - * - * $Id: Black.php 3769 2008-12-15 00:48:56Z zombor $ - * - * @package Captcha - * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html - */ -class Captcha_Black_Driver extends Captcha_Driver { - - /** - * Generates a new Captcha challenge. - * - * @return string the challenge answer - */ - public function generate_challenge() - { - // Complexity setting is used as character count - return text::random('distinct', max(1, ceil(Captcha::$config['complexity'] / 1.5))); - } - - /** - * Outputs the Captcha image. - * - * @param boolean html output - * @return mixed - */ - public function render($html) - { - // Creates a black image to start from - $this->image_create(Captcha::$config['background']); - - // Add random white/gray arcs, amount depends on complexity setting - $count = (Captcha::$config['width'] + Captcha::$config['height']) / 2; - $count = $count / 5 * min(10, Captcha::$config['complexity']); - for ($i = 0; $i < $count; $i++) - { - imagesetthickness($this->image, mt_rand(1, 2)); - $color = imagecolorallocatealpha($this->image, 255, 255, 255, mt_rand(0, 120)); - imagearc($this->image, mt_rand(-Captcha::$config['width'], Captcha::$config['width']), mt_rand(-Captcha::$config['height'], Captcha::$config['height']), mt_rand(-Captcha::$config['width'], Captcha::$config['width']), mt_rand(-Captcha::$config['height'], Captcha::$config['height']), mt_rand(0, 360), mt_rand(0, 360), $color); - } - - // Use different fonts if available - $font = Captcha::$config['fontpath'].Captcha::$config['fonts'][array_rand(Captcha::$config['fonts'])]; - - // Draw the character's white shadows - $size = (int) min(Captcha::$config['height'] / 2, Captcha::$config['width'] * 0.8 / strlen($this->response)); - $angle = mt_rand(-15 + strlen($this->response), 15 - strlen($this->response)); - $x = mt_rand(1, Captcha::$config['width'] * 0.9 - $size * strlen($this->response)); - $y = ((Captcha::$config['height'] - $size) / 2) + $size; - $color = imagecolorallocate($this->image, 255, 255, 255); - imagefttext($this->image, $size, $angle, $x + 1, $y + 1, $color, $font, $this->response); - - // Add more shadows for lower complexities - (Captcha::$config['complexity'] < 10) and imagefttext($this->image, $size, $angle, $x - 1, $y - 1, $color, $font , $this->response); - (Captcha::$config['complexity'] < 8) and imagefttext($this->image, $size, $angle, $x - 2, $y + 2, $color, $font , $this->response); - (Captcha::$config['complexity'] < 6) and imagefttext($this->image, $size, $angle, $x + 2, $y - 2, $color, $font , $this->response); - (Captcha::$config['complexity'] < 4) and imagefttext($this->image, $size, $angle, $x + 3, $y + 3, $color, $font , $this->response); - (Captcha::$config['complexity'] < 2) and imagefttext($this->image, $size, $angle, $x - 3, $y - 3, $color, $font , $this->response); - - // Finally draw the foreground characters - $color = imagecolorallocate($this->image, 0, 0, 0); - imagefttext($this->image, $size, $angle, $x, $y, $color, $font, $this->response); - - // Output - return $this->image_render($html); - } - -} // End Captcha Black Driver Class
\ No newline at end of file diff --git a/system/libraries/drivers/Captcha/Math.php b/system/libraries/drivers/Captcha/Math.php deleted file mode 100644 index 4ac20248..00000000 --- a/system/libraries/drivers/Captcha/Math.php +++ /dev/null @@ -1,61 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * Captcha driver for "math" style. - * - * $Id: Math.php 3769 2008-12-15 00:48:56Z zombor $ - * - * @package Captcha - * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html - */ -class Captcha_Math_Driver extends Captcha_Driver { - - private $math_exercice; - - /** - * Generates a new Captcha challenge. - * - * @return string the challenge answer - */ - public function generate_challenge() - { - // Easy - if (Captcha::$config['complexity'] < 4) - { - $numbers[] = mt_rand(1, 5); - $numbers[] = mt_rand(1, 4); - } - // Normal - elseif (Captcha::$config['complexity'] < 7) - { - $numbers[] = mt_rand(10, 20); - $numbers[] = mt_rand(1, 10); - } - // Difficult, well, not really ;) - else - { - $numbers[] = mt_rand(100, 200); - $numbers[] = mt_rand(10, 20); - $numbers[] = mt_rand(1, 10); - } - - // Store the question for output - $this->math_exercice = implode(' + ', $numbers).' = '; - - // Return the answer - return array_sum($numbers); - } - - /** - * Outputs the Captcha riddle. - * - * @param boolean html output - * @return mixed - */ - public function render($html) - { - return $this->math_exercice; - } - -} // End Captcha Math Driver Class
\ No newline at end of file diff --git a/system/libraries/drivers/Captcha/Riddle.php b/system/libraries/drivers/Captcha/Riddle.php deleted file mode 100644 index 765eeaad..00000000 --- a/system/libraries/drivers/Captcha/Riddle.php +++ /dev/null @@ -1,47 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * Captcha driver for "riddle" style. - * - * $Id: Riddle.php 3769 2008-12-15 00:48:56Z zombor $ - * - * @package Captcha - * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html - */ -class Captcha_Riddle_Driver extends Captcha_Driver { - - private $riddle; - - /** - * Generates a new Captcha challenge. - * - * @return string the challenge answer - */ - public function generate_challenge() - { - // Load riddles from the current language - $riddles = Kohana::lang('captcha.riddles'); - - // Pick a random riddle - $riddle = $riddles[array_rand($riddles)]; - - // Store the question for output - $this->riddle = $riddle[0]; - - // Return the answer - return $riddle[1]; - } - - /** - * Outputs the Captcha riddle. - * - * @param boolean html output - * @return mixed - */ - public function render($html) - { - return $this->riddle; - } - -} // End Captcha Riddle Driver Class
\ No newline at end of file diff --git a/system/libraries/drivers/Captcha/Word.php b/system/libraries/drivers/Captcha/Word.php deleted file mode 100644 index 856bd9b4..00000000 --- a/system/libraries/drivers/Captcha/Word.php +++ /dev/null @@ -1,37 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * Captcha driver for "word" style. - * - * $Id: Word.php 3769 2008-12-15 00:48:56Z zombor $ - * - * @package Captcha - * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html - */ -class Captcha_Word_Driver extends Captcha_Basic_Driver { - - /** - * Generates a new Captcha challenge. - * - * @return string the challenge answer - */ - public function generate_challenge() - { - // Load words from the current language and randomize them - $words = Kohana::lang('captcha.words'); - shuffle($words); - - // Loop over each word... - foreach ($words as $word) - { - // ...until we find one of the desired length - if (abs(Captcha::$config['complexity'] - strlen($word)) < 2) - return strtoupper($word); - } - - // Return any random word as final fallback - return strtoupper($words[array_rand($words)]); - } - -} // End Captcha Word Driver Class
\ No newline at end of file diff --git a/system/libraries/drivers/Config.php b/system/libraries/drivers/Config.php new file mode 100644 index 00000000..a82684bc --- /dev/null +++ b/system/libraries/drivers/Config.php @@ -0,0 +1,257 @@ +<?php defined('SYSPATH') or die('No direct script access.'); +/** + * Kohana_Config abstract driver to get and set + * configuration options. + * + * Specific drivers should implement caching and encryption + * as they deem appropriate. + * + * $Id: Config.php 4679 2009-11-10 01:45:52Z isaiah $ + * + * @package Kohana_Config + * @author Kohana Team + * @copyright (c) 2007-2009 Kohana Team + * @license http://kohanaphp.com/license + * @abstract + */ +abstract class Config_Driver { + + /** + * Internal caching + * + * @var Cache + */ + protected $cache; + + /** + * The name of the internal cache + * + * @var string + */ + protected $cache_name = 'Kohana_Config_Cache'; + + /** + * Cache Lifetime + * + * @var mixed + */ + protected $cache_lifetime = FALSE; + + /** + * The Encryption library + * + * @var Encrypt + */ + protected $encrypt; + + /** + * The config loaded + * + * @var array + */ + protected $config = array(); + + /** + * The changed status of configuration values, + * current state versus the stored state. + * + * @var bool + */ + protected $changed = FALSE; + + /** + * Determines if any config has been loaded yet + */ + public $loaded = FALSE; + + /** + * Array driver constructor. Sets up the PHP array + * driver, including caching and encryption if + * required + * + * @access public + */ + public function __construct($config) + { + + if (($cache_setting = $config['internal_cache']) !== FALSE) + { + $this->cache_lifetime = $cache_setting; + // Restore the cached configuration + $this->config = $this->load_cache(); + + if (count($this->config) > 0) + $this->loaded = TRUE; + + // Add the save cache method to system.shutshut event + Event::add('system.shutdown', array($this, 'save_cache')); + } + + } + + /** + * Gets a value from config. If required is TRUE + * then get will throw an exception if value cannot + * be loaded. + * + * @param string key the setting to get + * @param bool slash remove trailing slashes + * @param bool required is setting required? + * @return mixed + * @access public + */ + public function get($key, $slash = FALSE, $required = FALSE) + { + // Get the group name from the key + $group = explode('.', $key, 2); + $group = $group[0]; + + // Check for existing value and load it dynamically if required + if ( ! isset($this->config[$group])) + $this->config[$group] = $this->load($group, $required); + + // Get the value of the key string + $value = Kohana::key_string($this->config, $key); + + if ($slash === TRUE AND is_string($value) AND $value !== '') + { + // Force the value to end with "/" + $value = rtrim($value, '/').'/'; + } + + if (($required === TRUE) AND ($value === null)) + throw new Kohana_Config_Exception('Value not found in config driver'); + + $this->loaded = TRUE; + return $value; + } + + /** + * Sets a new value to the configuration + * + * @param string key + * @param mixed value + * @return bool + * @access public + */ + public function set($key, $value) + { + // Do this to make sure that the config array is already loaded + $this->get($key); + + if (substr($key, 0, 7) === 'routes.') + { + // Routes cannot contain sub keys due to possible dots in regex + $keys = explode('.', $key, 2); + } + else + { + // Convert dot-noted key string to an array + $keys = explode('.', $key); + } + + // Used for recursion + $conf =& $this->config; + $last = count($keys) - 1; + + foreach ($keys as $i => $k) + { + if ($i === $last) + { + $conf[$k] = $value; + } + else + { + $conf =& $conf[$k]; + } + } + + if (substr($key,0,12) === 'core.modules') + { + // Reprocess the include paths + Kohana::include_paths(TRUE); + } + + // Set config to changed + return $this->changed = TRUE; + } + + /** + * Clear the configuration + * + * @param string group + * @return bool + * @access public + */ + public function clear($group) + { + // Remove the group from config + unset($this->config[$group]); + + // Set config to changed + return $this->changed = TRUE; + } + + /** + * Checks whether the setting exists in + * config + * + * @param string $key + * @return bool + * @access public + */ + public function setting_exists($key) + { + return $this->get($key) === NULL; + } + + /** + * Loads a configuration group based on the setting + * + * @param string group + * @param bool required + * @return array + * @access public + * @abstract + */ + abstract public function load($group, $required = FALSE); + + /** + * Loads the cached version of this configuration driver + * + * @return array + * @access public + */ + public function load_cache() + { + // Load the cache for this configuration + $cached_config = Kohana::cache($this->cache_name, $this->cache_lifetime); + + // If the configuration wasn't loaded from the cache + if ($cached_config === NULL) + $cached_config = array(); + + // Return the cached config + return $cached_config; + } + + /** + * Saves a cached version of this configuration driver + * + * @return bool + * @access public + */ + public function save_cache() + { + // If this configuration has changed + if ($this->get('core.internal_cache') !== FALSE AND $this->changed) + { + $data = $this->config; + + // Save the cache + return Kohana::cache_save($this->cache_name, $data, $this->cache_lifetime); + } + + return TRUE; + } +} // End Kohana_Config_Driver
\ No newline at end of file diff --git a/system/libraries/drivers/Config/Array.php b/system/libraries/drivers/Config/Array.php new file mode 100644 index 00000000..b2ca19ba --- /dev/null +++ b/system/libraries/drivers/Config/Array.php @@ -0,0 +1,83 @@ +<?php defined('SYSPATH') or die('No direct script access.'); +/** + * Kohana_Config Array driver to get and set + * configuration options using PHP arrays. + * + * This driver can cache and encrypt settings + * if required. + * + * $Id: Array.php 4679 2009-11-10 01:45:52Z isaiah $ + * + * @package Kohana_Config + * @author Kohana Team + * @copyright (c) 2007-2009 Kohana Team + * @license http://kohanaphp.com/license + */ +class Config_Array_Driver extends Config_Driver { + + /** + * Internal caching + * + * @var Cache + */ + protected $cache; + + /** + * The name of the internal cache + * + * @var string + */ + protected $cache_name = 'Kohana_Config_Array_Cache'; + + /** + * The Encryption library + * + * @var Encrypt + */ + protected $encrypt; + + /** + * Loads a configuration group based on the setting + * + * @param string group + * @param bool required + * @return array + * @access public + */ + public function load($group, $required = FALSE) + { + if ($group === 'core') + { + // Load the application configuration file + require APPPATH.'config/config'.EXT; + + if ( ! isset($config['site_domain'])) + { + // Invalid config file + throw new Kohana_Config_Exception('Your Kohana application configuration file is not valid.'); + } + + return $config; + } + + // Load matching configs + $configuration = array(); + + if ($files = Kohana::find_file('config', $group, $required)) + { + foreach ($files as $file) + { + require $file; + + if (isset($config) AND is_array($config)) + { + // Merge in configuration + $configuration = array_merge($configuration, $config); + } + } + } + + // Return merged configuration + return $configuration; + } +} // End Config_Array_Driver
\ No newline at end of file diff --git a/system/libraries/drivers/Database.php b/system/libraries/drivers/Database.php deleted file mode 100644 index 27f6ea8e..00000000 --- a/system/libraries/drivers/Database.php +++ /dev/null @@ -1,636 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * Database API driver - * - * $Id: Database.php 4343 2009-05-08 17:04:48Z jheathco $ - * - * @package Core - * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html - */ -abstract class Database_Driver { - - protected $query_cache; - - /** - * Connect to our database. - * Returns FALSE on failure or a MySQL resource. - * - * @return mixed - */ - abstract public function connect(); - - /** - * Perform a query based on a manually written query. - * - * @param string SQL query to execute - * @return Database_Result - */ - abstract public function query($sql); - - /** - * Builds a DELETE query. - * - * @param string table name - * @param array where clause - * @return string - */ - public function delete($table, $where) - { - return 'DELETE FROM '.$this->escape_table($table).' WHERE '.implode(' ', $where); - } - - /** - * Builds an UPDATE query. - * - * @param string table name - * @param array key => value pairs - * @param array where clause - * @return string - */ - public function update($table, $values, $where) - { - foreach ($values as $key => $val) - { - $valstr[] = $this->escape_column($key).' = '.$val; - } - return 'UPDATE '.$this->escape_table($table).' SET '.implode(', ', $valstr).' WHERE '.implode(' ',$where); - } - - /** - * Set the charset using 'SET NAMES <charset>'. - * - * @param string character set to use - */ - public function set_charset($charset) - { - throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__); - } - - /** - * Wrap the tablename in backticks, has support for: table.field syntax. - * - * @param string table name - * @return string - */ - abstract public function escape_table($table); - - /** - * Escape a column/field name, has support for special commands. - * - * @param string column name - * @return string - */ - abstract public function escape_column($column); - - /** - * Builds a WHERE portion of a query. - * - * @param mixed key - * @param string value - * @param string type - * @param int number of where clauses - * @param boolean escape the value - * @return string - */ - public function where($key, $value, $type, $num_wheres, $quote) - { - $prefix = ($num_wheres == 0) ? '' : $type; - - if ($quote === -1) - { - $value = ''; - } - else - { - if ($value === NULL) - { - if ( ! $this->has_operator($key)) - { - $key .= ' IS'; - } - - $value = ' NULL'; - } - elseif (is_bool($value)) - { - if ( ! $this->has_operator($key)) - { - $key .= ' ='; - } - - $value = ($value == TRUE) ? ' 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 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * MSSQL Database Driver - * - * @package Core - * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html - */ -class Database_Mssql_Driver extends Database_Driver -{ - /** - * Database connection link - */ - protected $link; - - /** - * Database configuration - */ - protected $db_config; - - /** - * Sets the config for the class. - * - * @param array database configuration - */ - public function __construct($config) - { - $this->db_config = $config; - - Kohana::log('debug', 'MSSQL Database Driver Initialized'); - } - - /** - * Closes the database connection. - */ - public function __destruct() - { - is_resource($this->link) and mssql_close($this->link); - } - - /** - * Make the connection - * - * @return return connection - */ - public function connect() - { - // Check if link already exists - if (is_resource($this->link)) - return $this->link; - - // Import the connect variables - extract($this->db_config['connection']); - - // Persistent connections enabled? - $connect = ($this->db_config['persistent'] == TRUE) ? 'mssql_pconnect' : 'mssql_connect'; - - // Build the connection info - $host = isset($host) ? $host : $socket; - - // Windows uses a comma instead of a colon - $port = (isset($port) AND is_string($port)) ? (KOHANA_IS_WIN ? ',' : ':').$port : ''; - - // Make the connection and select the database - if (($this->link = $connect($host.$port, $user, $pass, TRUE)) AND mssql_select_db($database, $this->link)) - { - /* This is being removed so I can use it, will need to come up with a more elegant workaround in the future... - * - if ($charset = $this->db_config['character_set']) - { - $this->set_charset($charset); - } - */ - - // Clear password after successful connect - $this->db_config['connection']['pass'] = NULL; - - return $this->link; - } - - return FALSE; - } - - public function query($sql) - { - // Only cache if it's turned on, and only cache if it's not a write statement - if ($this->db_config['cache'] AND ! preg_match('#\b(?:INSERT|UPDATE|REPLACE|SET)\b#i', $sql)) - { - $hash = $this->query_hash($sql); - - if ( ! isset($this->query_cache[$hash])) - { - // Set the cached object - $this->query_cache[$hash] = new Mssql_Result(mssql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql); - } - else - { - // Rewind cached result - $this->query_cache[$hash]->rewind(); - } - - // Return the cached query - return $this->query_cache[$hash]; - } - - return new Mssql_Result(mssql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql); - } - - public function escape_table($table) - { - if (stripos($table, ' AS ') !== FALSE) - { - // Force 'AS' to uppercase - $table = str_ireplace(' AS ', ' AS ', $table); - - // Runs escape_table on both sides of an AS statement - $table = array_map(array($this, __FUNCTION__), explode(' AS ', $table)); - - // Re-create the AS statement - return implode(' AS ', $table); - } - return '['.str_replace('.', '[.]', $table).']'; - } - - public function escape_column($column) - { - if (!$this->db_config['escape']) - return $column; - - if ($column == '*') - return $column; - - // This matches any functions we support to SELECT. - if ( preg_match('/(avg|count|sum|max|min)\(\s*(.*)\s*\)(\s*as\s*(.+)?)?/i', $column, $matches)) - { - if ( count($matches) == 3) - { - return $matches[1].'('.$this->escape_column($matches[2]).')'; - } - else if ( count($matches) == 5) - { - return $matches[1].'('.$this->escape_column($matches[2]).') AS '.$this->escape_column($matches[2]); - } - } - - // This matches any modifiers we support to SELECT. - if ( ! preg_match('/\b(?:rand|all|distinct(?:row)?|high_priority|sql_(?:small_result|b(?:ig_result|uffer_result)|no_cache|ca(?:che|lc_found_rows)))\s/i', $column)) - { - if (stripos($column, ' AS ') !== FALSE) - { - // Force 'AS' to uppercase - $column = str_ireplace(' AS ', ' AS ', $column); - - // Runs escape_column on both sides of an AS statement - $column = array_map(array($this, __FUNCTION__), explode(' AS ', $column)); - - // Re-create the AS statement - return implode(' AS ', $column); - } - - return preg_replace('/[^.*]+/', '[$0]', $column); - } - - $parts = explode(' ', $column); - $column = ''; - - for ($i = 0, $c = count($parts); $i < $c; $i++) - { - // The column is always last - if ($i == ($c - 1)) - { - $column .= preg_replace('/[^.*]+/', '[$0]', $parts[$i]); - } - else // otherwise, it's a modifier - { - $column .= $parts[$i].' '; - } - } - return $column; - } - - /** - * Limit in SQL Server 2000 only uses the keyword - * 'TOP'; 2007 may have an offset keyword, but - * I am unsure - for pagination style limit,offset - * functionality, a fancy query needs to be built. - * - * @param unknown_type $limit - * @return unknown - */ - public function limit($limit, $offset=null) - { - return 'TOP '.$limit; - } - - public function compile_select($database) - { - $sql = ($database['distinct'] == TRUE) ? 'SELECT DISTINCT ' : 'SELECT '; - $sql .= (count($database['select']) > 0) ? implode(', ', $database['select']) : '*'; - - if (count($database['from']) > 0) - { - // Escape the tables - $froms = array(); - foreach ($database['from'] as $from) - $froms[] = $this->escape_column($from); - $sql .= "\nFROM "; - $sql .= implode(', ', $froms); - } - - if (count($database['join']) > 0) - { - foreach($database['join'] AS $join) - { - $sql .= "\n".$join['type'].'JOIN '.implode(', ', $join['tables']).' ON '.$join['conditions']; - } - } - - if (count($database['where']) > 0) - { - $sql .= "\nWHERE "; - } - - $sql .= implode("\n", $database['where']); - - if (count($database['groupby']) > 0) - { - $sql .= "\nGROUP BY "; - $sql .= implode(', ', $database['groupby']); - } - - if (count($database['having']) > 0) - { - $sql .= "\nHAVING "; - $sql .= implode("\n", $database['having']); - } - - if (count($database['orderby']) > 0) - { - $sql .= "\nORDER BY "; - $sql .= implode(', ', $database['orderby']); - } - - if (is_numeric($database['limit'])) - { - $sql .= "\n"; - $sql .= $this->limit($database['limit']); - } - - return $sql; - } - - public function escape_str($str) - { - if (!$this->db_config['escape']) - return $str; - - is_resource($this->link) or $this->connect(); - //mssql_real_escape_string($str, $this->link); <-- this function doesn't exist - - $characters = array('/\x00/', '/\x1a/', '/\n/', '/\r/', '/\\\/', '/\'/'); - $replace = array('\\\x00', '\\x1a', '\\n', '\\r', '\\\\', "''"); - return preg_replace($characters, $replace, $str); - } - - public function list_tables() - { - $sql = 'SHOW TABLES FROM ['.$this->db_config['connection']['database'].']'; - $result = $this->query($sql)->result(FALSE, MSSQL_ASSOC); - - $retval = array(); - foreach ($result as $row) - { - $retval[] = current($row); - } - - return $retval; - } - - public function show_error() - { - return mssql_get_last_message($this->link); - } - - public function list_fields($table) - { - $result = array(); - - foreach ($this->field_data($table) as $row) - { - // Make an associative array - $result[$row->Field] = $this->sql_type($row->Type); - } - - return $result; - } - - public function field_data($table) - { - $query = $this->query("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 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * MySQL Database Driver - * - * $Id: Mysql.php 4344 2009-05-11 16:41:39Z zombor $ - * - * @package Core - * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html - */ -class Database_Mysql_Driver extends Database_Driver { - - /** - * Database connection link - */ - protected $link; - - /** - * Database configuration - */ - protected $db_config; - - /** - * Sets the config for the class. - * - * @param array database configuration - */ - public function __construct($config) - { - $this->db_config = $config; - - Kohana::log('debug', 'MySQL Database Driver Initialized'); - } - - /** - * Closes the database connection. - */ - public function __destruct() - { - is_resource($this->link) and mysql_close($this->link); - } - - public function connect() - { - // Check if link already exists - if (is_resource($this->link)) - return $this->link; - - // Import the connect variables - extract($this->db_config['connection']); - - // Persistent connections enabled? - $connect = ($this->db_config['persistent'] == TRUE) ? 'mysql_pconnect' : 'mysql_connect'; - - // Build the connection info - $host = isset($host) ? $host : $socket; - $port = isset($port) ? ':'.$port : ''; - - // Make the connection and select the database - if (($this->link = $connect($host.$port, $user, $pass, TRUE)) AND mysql_select_db($database, $this->link)) - { - if ($charset = $this->db_config['character_set']) - { - $this->set_charset($charset); - } - - // Clear password after successful connect - $this->db_config['connection']['pass'] = NULL; - - return $this->link; - } - - return FALSE; - } - - public function query($sql) - { - // Only cache if it's turned on, and only cache if it's not a write statement - if ($this->db_config['cache'] AND ! preg_match('#\b(?:INSERT|UPDATE|REPLACE|SET|DELETE|TRUNCATE)\b#i', $sql)) - { - $hash = $this->query_hash($sql); - - if ( ! isset($this->query_cache[$hash])) - { - // Set the cached object - $this->query_cache[$hash] = new Mysql_Result(mysql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql); - } - else - { - // Rewind cached result - $this->query_cache[$hash]->rewind(); - } - - // Return the cached query - return $this->query_cache[$hash]; - } - - return new Mysql_Result(mysql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql); - } - - public function set_charset($charset) - { - $this->query('SET NAMES '.$this->escape_str($charset)); - } - - public function escape_table($table) - { - if (!$this->db_config['escape']) - return $table; - - if (stripos($table, ' AS ') !== FALSE) - { - // Force 'AS' to uppercase - $table = str_ireplace(' AS ', ' AS ', $table); - - // Runs escape_table on both sides of an AS statement - $table = array_map(array($this, __FUNCTION__), explode(' AS ', $table)); - - // Re-create the AS statement - return implode(' AS ', $table); - } - return '`'.str_replace('.', '`.`', $table).'`'; - } - - public function escape_column($column) - { - if (!$this->db_config['escape']) - return $column; - - if ($column == '*') - return $column; - - // This matches any functions we support to SELECT. - if ( preg_match('/(avg|count|sum|max|min)\(\s*(.*)\s*\)(\s*as\s*(.+)?)?/i', $column, $matches)) - { - if ( count($matches) == 3) - { - return $matches[1].'('.$this->escape_column($matches[2]).')'; - } - else if ( count($matches) == 5) - { - return $matches[1].'('.$this->escape_column($matches[2]).') AS '.$this->escape_column($matches[2]); - } - } - - // This matches any modifiers we support to SELECT. - if ( ! preg_match('/\b(?:rand|all|distinct(?:row)?|high_priority|sql_(?:small_result|b(?:ig_result|uffer_result)|no_cache|ca(?:che|lc_found_rows)))\s/i', $column)) - { - if (stripos($column, ' AS ') !== FALSE) - { - // Force 'AS' to uppercase - $column = str_ireplace(' AS ', ' AS ', $column); - - // Runs escape_column on both sides of an AS statement - $column = array_map(array($this, __FUNCTION__), explode(' AS ', $column)); - - // Re-create the AS statement - return implode(' AS ', $column); - } - - return preg_replace('/[^.*]+/', '`$0`', $column); - } - - $parts = explode(' ', $column); - $column = ''; - - for ($i = 0, $c = count($parts); $i < $c; $i++) - { - // The column is always last - if ($i == ($c - 1)) - { - $column .= preg_replace('/[^.*]+/', '`$0`', $parts[$i]); - } - else // otherwise, it's a modifier - { - $column .= $parts[$i].' '; - } - } - return $column; - } - - public function regex($field, $match, $type, $num_regexs) - { - $prefix = ($num_regexs == 0) ? '' : $type; - - return $prefix.' '.$this->escape_column($field).' REGEXP \''.$this->escape_str($match).'\''; - } - - public function notregex($field, $match, $type, $num_regexs) - { - $prefix = $num_regexs == 0 ? '' : $type; - - return $prefix.' '.$this->escape_column($field).' NOT REGEXP \''.$this->escape_str($match) . '\''; - } - - public function merge($table, $keys, $values) - { - // Escape the column names - foreach ($keys as $key => $value) - { - $keys[$key] = $this->escape_column($value); - } - return 'REPLACE INTO '.$this->escape_table($table).' ('.implode(', ', $keys).') VALUES ('.implode(', ', $values).')'; - } - - public function limit($limit, $offset = 0) - { - return 'LIMIT '.$offset.', '.$limit; - } - - public function compile_select($database) - { - $sql = ($database['distinct'] == TRUE) ? 'SELECT DISTINCT ' : 'SELECT '; - $sql .= (count($database['select']) > 0) ? implode(', ', $database['select']) : '*'; - - if (count($database['from']) > 0) - { - // Escape the tables - $froms = array(); - foreach ($database['from'] as $from) - { - $froms[] = $this->escape_column($from); - } - $sql .= "\nFROM ("; - $sql .= implode(', ', $froms).")"; - } - - if (count($database['join']) > 0) - { - foreach($database['join'] AS $join) - { - $sql .= "\n".$join['type'].'JOIN '.implode(', ', $join['tables']).' ON '.$join['conditions']; - } - } - - if (count($database['where']) > 0) - { - $sql .= "\nWHERE "; - } - - $sql .= implode("\n", $database['where']); - - if (count($database['groupby']) > 0) - { - $sql .= "\nGROUP BY "; - $sql .= implode(', ', $database['groupby']); - } - - if (count($database['having']) > 0) - { - $sql .= "\nHAVING "; - $sql .= implode("\n", $database['having']); - } - - if (count($database['orderby']) > 0) - { - $sql .= "\nORDER BY "; - $sql .= implode(', ', $database['orderby']); - } - - if (is_numeric($database['limit'])) - { - $sql .= "\n"; - $sql .= $this->limit($database['limit'], $database['offset']); - } - - return $sql; - } - - public function escape_str($str) - { - if (!$this->db_config['escape']) - return $str; - - is_resource($this->link) or $this->connect(); - - return mysql_real_escape_string($str, $this->link); - } - - public function list_tables() - { - $tables = array(); - - if ($query = $this->query('SHOW TABLES FROM '.$this->escape_table($this->db_config['connection']['database']))) - { - foreach ($query->result(FALSE) as $row) - { - $tables[] = current($row); - } - } - - return $tables; - } - - public function show_error() - { - return mysql_error($this->link); - } - - public function list_fields($table) - { - $result = NULL; - - foreach ($this->field_data($table) as $row) - { - // Make an associative array - $result[$row->Field] = $this->sql_type($row->Type); - - if ($row->Key === 'PRI' AND $row->Extra === 'auto_increment') - { - // For sequenced (AUTO_INCREMENT) tables - $result[$row->Field]['sequenced'] = TRUE; - } - - if ($row->Null === 'YES') - { - // Set NULL status - $result[$row->Field]['null'] = TRUE; - } - } - - if (!isset($result)) - throw new Kohana_Database_Exception('database.table_not_found', $table); - - return $result; - } - - public function field_data($table) - { - $result = $this->query('SHOW COLUMNS FROM '.$this->escape_table($table)); - - return $result->result_array(TRUE); - } - -} // End Database_Mysql_Driver Class - -/** - * MySQL Result - */ -class Mysql_Result extends Database_Result { - - // Fetch function and return type - protected $fetch_type = 'mysql_fetch_object'; - protected $return_type = MYSQL_ASSOC; - - /** - * Sets up the result variables. - * - * @param resource query result - * @param resource database link - * @param boolean return objects or arrays - * @param string SQL query that was run - */ - public function __construct($result, $link, $object = TRUE, $sql) - { - $this->result = $result; - - // If the query is a resource, it was a SELECT, SHOW, DESCRIBE, EXPLAIN query - if (is_resource($result)) - { - $this->current_row = 0; - $this->total_rows = mysql_num_rows($this->result); - $this->fetch_type = ($object === TRUE) ? 'mysql_fetch_object' : 'mysql_fetch_array'; - } - elseif (is_bool($result)) - { - if ($result == FALSE) - { - // SQL error - throw new Kohana_Database_Exception('database.error', mysql_error($link).' - '.$sql); - } - else - { - // Its an DELETE, INSERT, REPLACE, or UPDATE query - $this->insert_id = mysql_insert_id($link); - $this->total_rows = mysql_affected_rows($link); - } - } - - // Set result type - $this->result($object); - - // Store the SQL - $this->sql = $sql; - } - - /** - * Destruct, the cleanup crew! - */ - public function __destruct() - { - if (is_resource($this->result)) - { - mysql_free_result($this->result); - } - } - - public function result($object = TRUE, $type = MYSQL_ASSOC) - { - $this->fetch_type = ((bool) $object) ? 'mysql_fetch_object' : 'mysql_fetch_array'; - - // This check has to be outside the previous statement, because we do not - // know the state of fetch_type when $object = NULL - // NOTE - The class set by $type must be defined before fetching the result, - // autoloading is disabled to save a lot of stupid overhead. - if ($this->fetch_type == 'mysql_fetch_object' AND $object === TRUE) - { - $this->return_type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass'; - } - else - { - $this->return_type = $type; - } - - return $this; - } - - public function as_array($object = NULL, $type = MYSQL_ASSOC) - { - return $this->result_array($object, $type); - } - - public function result_array($object = NULL, $type = MYSQL_ASSOC) - { - $rows = array(); - - if (is_string($object)) - { - $fetch = $object; - } - elseif (is_bool($object)) - { - if ($object === TRUE) - { - $fetch = 'mysql_fetch_object'; - - $type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass'; - } - else - { - $fetch = 'mysql_fetch_array'; - } - } - else - { - // Use the default config values - $fetch = $this->fetch_type; - - if ($fetch == 'mysql_fetch_object') - { - $type = (is_string($this->return_type) AND Kohana::auto_load($this->return_type)) ? $this->return_type : 'stdClass'; - } - } - - if (mysql_num_rows($this->result)) - { - // Reset the pointer location to make sure things work properly - mysql_data_seek($this->result, 0); - - while ($row = $fetch($this->result, $type)) - { - $rows[] = $row; - } - } - - return isset($rows) ? $rows : array(); - } - - public function list_fields() - { - $field_names = array(); - while ($field = mysql_fetch_field($this->result)) - { - $field_names[] = $field->name; - } - - return $field_names; - } - - public function seek($offset) - { - if ($this->offsetExists($offset) AND mysql_data_seek($this->result, $offset)) - { - // Set the current row to the offset - $this->current_row = $offset; - - return TRUE; - } - else - { - return FALSE; - } - } - -} // End Mysql_Result Class diff --git a/system/libraries/drivers/Database/Mysqli.php b/system/libraries/drivers/Database/Mysqli.php deleted file mode 100644 index 0dd9f05c..00000000 --- a/system/libraries/drivers/Database/Mysqli.php +++ /dev/null @@ -1,358 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * MySQLi Database Driver - * - * $Id: Mysqli.php 4344 2009-05-11 16:41:39Z zombor $ - * - * @package Core - * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html - */ -class Database_Mysqli_Driver extends Database_Mysql_Driver { - - // Database connection link - protected $link; - protected $db_config; - protected $statements = array(); - - /** - * Sets the config for the class. - * - * @param array database configuration - */ - public function __construct($config) - { - $this->db_config = $config; - - Kohana::log('debug', 'MySQLi Database Driver Initialized'); - } - - /** - * Closes the database connection. - */ - public function __destruct() - { - is_object($this->link) and $this->link->close(); - } - - public function connect() - { - // Check if link already exists - if (is_object($this->link)) - return $this->link; - - // Import the connect variables - extract($this->db_config['connection']); - - // Build the connection info - $host = isset($host) ? $host : $socket; - - // Make the connection and select the database - if ($this->link = new mysqli($host, $user, $pass, $database, $port)) - { - if ($charset = $this->db_config['character_set']) - { - $this->set_charset($charset); - } - - // Clear password after successful connect - $this->db_config['connection']['pass'] = NULL; - - return $this->link; - } - - return FALSE; - } - - public function query($sql) - { - // Only cache if it's turned on, and only cache if it's not a write statement - if ($this->db_config['cache'] AND ! preg_match('#\b(?:INSERT|UPDATE|REPLACE|SET|DELETE|TRUNCATE)\b#i', $sql)) - { - $hash = $this->query_hash($sql); - - if ( ! isset($this->query_cache[$hash])) - { - // Set the cached object - $this->query_cache[$hash] = new Kohana_Mysqli_Result($this->link, $this->db_config['object'], $sql); - } - else - { - // Rewind cached result - $this->query_cache[$hash]->rewind(); - } - - // Return the cached query - return $this->query_cache[$hash]; - } - - return new Kohana_Mysqli_Result($this->link, $this->db_config['object'], $sql); - } - - public function set_charset($charset) - { - if ($this->link->set_charset($charset) === FALSE) - throw new Kohana_Database_Exception('database.error', $this->show_error()); - } - - public function escape_str($str) - { - if (!$this->db_config['escape']) - return $str; - - is_object($this->link) or $this->connect(); - - return $this->link->real_escape_string($str); - } - - public function show_error() - { - return $this->link->error; - } - -} // End Database_Mysqli_Driver Class - -/** - * MySQLi Result - */ -class Kohana_Mysqli_Result extends Database_Result { - - // Database connection - protected $link; - - // Data fetching types - protected $fetch_type = 'mysqli_fetch_object'; - protected $return_type = MYSQLI_ASSOC; - - /** - * Sets up the result variables. - * - * @param object database link - * @param boolean return objects or arrays - * @param string SQL query that was run - */ - public function __construct($link, $object = TRUE, $sql) - { - $this->link = $link; - - if ( ! $this->link->multi_query($sql)) - { - // SQL error - throw new Kohana_Database_Exception('database.error', $this->link->error.' - '.$sql); - } - else - { - $this->result = $this->link->store_result(); - - // If the query is an object, it was a SELECT, SHOW, DESCRIBE, EXPLAIN query - if (is_object($this->result)) - { - $this->current_row = 0; - $this->total_rows = $this->result->num_rows; - $this->fetch_type = ($object === TRUE) ? 'fetch_object' : 'fetch_array'; - } - elseif ($this->link->error) - { - // SQL error - throw new Kohana_Database_Exception('database.error', $this->link->error.' - '.$sql); - } - else - { - // Its an DELETE, INSERT, REPLACE, or UPDATE query - $this->insert_id = $this->link->insert_id; - $this->total_rows = $this->link->affected_rows; - } - } - - // Set result type - $this->result($object); - - // Store the SQL - $this->sql = $sql; - } - - /** - * Magic __destruct function, frees the result. - */ - public function __destruct() - { - if (is_object($this->result)) - { - $this->result->free_result(); - - // this is kinda useless, but needs to be done to avoid the "Commands out of sync; you - // can't run this command now" error. Basically, we get all results after the first one - // (the one we actually need) and free them. - if (is_resource($this->link) AND $this->link->more_results()) - { - do - { - if ($result = $this->link->store_result()) - { - $result->free_result(); - } - } while ($this->link->next_result()); - } - } - } - - public function result($object = TRUE, $type = MYSQLI_ASSOC) - { - $this->fetch_type = ((bool) $object) ? 'fetch_object' : 'fetch_array'; - - // This check has to be outside the previous statement, because we do not - // know the state of fetch_type when $object = NULL - // NOTE - The class set by $type must be defined before fetching the result, - // autoloading is disabled to save a lot of stupid overhead. - if ($this->fetch_type == 'fetch_object') - { - $this->return_type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass'; - } - else - { - $this->return_type = $type; - } - - return $this; - } - - public function as_array($object = NULL, $type = MYSQLI_ASSOC) - { - return $this->result_array($object, $type); - } - - public function result_array($object = NULL, $type = MYSQLI_ASSOC) - { - $rows = array(); - - if (is_string($object)) - { - $fetch = $object; - } - elseif (is_bool($object)) - { - if ($object === TRUE) - { - $fetch = 'fetch_object'; - - // NOTE - The class set by $type must be defined before fetching the result, - // autoloading is disabled to save a lot of stupid overhead. - $type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass'; - } - else - { - $fetch = 'fetch_array'; - } - } - else - { - // Use the default config values - $fetch = $this->fetch_type; - - if ($fetch == 'fetch_object') - { - $type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass'; - } - } - - if ($this->result->num_rows) - { - // Reset the pointer location to make sure things work properly - $this->result->data_seek(0); - - while ($row = $this->result->$fetch($type)) - { - $rows[] = $row; - } - } - - return isset($rows) ? $rows : array(); - } - - public function list_fields() - { - $field_names = array(); - while ($field = $this->result->fetch_field()) - { - $field_names[] = $field->name; - } - - return $field_names; - } - - public function seek($offset) - { - if ($this->offsetExists($offset) AND $this->result->data_seek($offset)) - { - // Set the current row to the offset - $this->current_row = $offset; - - return TRUE; - } - - return FALSE; - } - - public function offsetGet($offset) - { - if ( ! $this->seek($offset)) - return FALSE; - - // Return the row - $fetch = $this->fetch_type; - return $this->result->$fetch($this->return_type); - } - -} // End Mysqli_Result Class - -/** - * MySQLi Prepared Statement (experimental) - */ -class Kohana_Mysqli_Statement { - - protected $link = NULL; - protected $stmt; - protected $var_names = array(); - protected $var_values = array(); - - public function __construct($sql, $link) - { - $this->link = $link; - - $this->stmt = $this->link->prepare($sql); - - return $this; - } - - public function __destruct() - { - $this->stmt->close(); - } - - // Sets the bind parameters - public function bind_params($param_types, $params) - { - $this->var_names = array_keys($params); - $this->var_values = array_values($params); - call_user_func_array(array($this->stmt, 'bind_param'), array_merge($param_types, $var_names)); - - return $this; - } - - public function bind_result($params) - { - call_user_func_array(array($this->stmt, 'bind_result'), $params); - } - - // Runs the statement - public function execute() - { - foreach ($this->var_names as $key => $name) - { - $$name = $this->var_values[$key]; - } - $this->stmt->execute(); - return $this->stmt; - } -} diff --git a/system/libraries/drivers/Database/Pdosqlite.php b/system/libraries/drivers/Database/Pdosqlite.php deleted file mode 100644 index c2d1bb21..00000000 --- a/system/libraries/drivers/Database/Pdosqlite.php +++ /dev/null @@ -1,486 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/* - * Class: Database_PdoSqlite_Driver - * Provides specific database items for Sqlite. - * - * Connection string should be, eg: "pdosqlite://path/to/database.db" - * - * Version 1.0 alpha - * author - Doutu, updated by gregmac - * copyright - (c) BSD - * license - <no> - */ - -class Database_Pdosqlite_Driver extends Database_Driver { - - // Database connection link - protected $link; - protected $db_config; - - /* - * Constructor: __construct - * Sets up the config for the class. - * - * Parameters: - * config - database configuration - * - */ - public function __construct($config) - { - $this->db_config = $config; - - Kohana::log('debug', 'PDO:Sqlite Database Driver Initialized'); - } - - public function connect() - { - // Import the connect variables - extract($this->db_config['connection']); - - try - { - $this->link = new PDO('sqlite:'.$socket.$database, $user, $pass, - array(PDO::ATTR_PERSISTENT => $this->db_config['persistent'])); - - $this->link->setAttribute(PDO::ATTR_CASE, PDO::CASE_NATURAL); - //$this->link->query('PRAGMA count_changes=1;'); - - if ($charset = $this->db_config['character_set']) - { - $this->set_charset($charset); - } - } - catch (PDOException $e) - { - throw new Kohana_Database_Exception('database.error', $e->getMessage()); - } - - // Clear password after successful connect - $this->db_config['connection']['pass'] = NULL; - - return $this->link; - } - - public function query($sql) - { - try - { - $sth = $this->link->prepare($sql); - } - catch (PDOException $e) - { - throw new Kohana_Database_Exception('database.error', $e->getMessage()); - } - return new Pdosqlite_Result($sth, $this->link, $this->db_config['object'], $sql); - } - - public function set_charset($charset) - { - $this->link->query('PRAGMA encoding = '.$this->escape_str($charset)); - } - - public function escape_table($table) - { - if ( ! $this->db_config['escape']) - return $table; - - return '`'.str_replace('.', '`.`', $table).'`'; - } - - public function escape_column($column) - { - if ( ! $this->db_config['escape']) - return $column; - - if ($column == '*') - return $column; - - // This matches any functions we support to SELECT. - if ( preg_match('/(avg|count|sum|max|min)\(\s*(.*)\s*\)(\s*as\s*(.+)?)?/i', $column, $matches)) - { - if ( count($matches) == 3) - { - return $matches[1].'('.$this->escape_column($matches[2]).')'; - } - else if ( count($matches) == 5) - { - return $matches[1].'('.$this->escape_column($matches[2]).') AS '.$this->escape_column($matches[2]); - } - } - - // This matches any modifiers we support to SELECT. - if ( ! preg_match('/\b(?:rand|all|distinct(?:row)?|high_priority|sql_(?:small_result|b(?:ig_result|uffer_result)|no_cache|ca(?:che|lc_found_rows)))\s/i', $column)) - { - if (stripos($column, ' AS ') !== FALSE) - { - // Force 'AS' to uppercase - $column = str_ireplace(' AS ', ' AS ', $column); - - // Runs escape_column on both sides of an AS statement - $column = array_map(array($this, __FUNCTION__), explode(' AS ', $column)); - - // Re-create the AS statement - return implode(' AS ', $column); - } - - return preg_replace('/[^.*]+/', '`$0`', $column); - } - - $parts = explode(' ', $column); - $column = ''; - - for ($i = 0, $c = count($parts); $i < $c; $i++) - { - // The column is always last - if ($i == ($c - 1)) - { - $column .= preg_replace('/[^.*]+/', '`$0`', $parts[$i]); - } - else // otherwise, it's a modifier - { - $column .= $parts[$i].' '; - } - } - return $column; - } - - public function limit($limit, $offset = 0) - { - return 'LIMIT '.$offset.', '.$limit; - } - - public function compile_select($database) - { - $sql = ($database['distinct'] == TRUE) ? 'SELECT DISTINCT ' : 'SELECT '; - $sql .= (count($database['select']) > 0) ? implode(', ', $database['select']) : '*'; - - if (count($database['from']) > 0) - { - $sql .= "\nFROM "; - $sql .= implode(', ', $database['from']); - } - - if (count($database['join']) > 0) - { - foreach($database['join'] AS $join) - { - $sql .= "\n".$join['type'].'JOIN '.implode(', ', $join['tables']).' ON '.$join['conditions']; - } - } - - if (count($database['where']) > 0) - { - $sql .= "\nWHERE "; - } - - $sql .= implode("\n", $database['where']); - - if (count($database['groupby']) > 0) - { - $sql .= "\nGROUP BY "; - $sql .= implode(', ', $database['groupby']); - } - - if (count($database['having']) > 0) - { - $sql .= "\nHAVING "; - $sql .= implode("\n", $database['having']); - } - - if (count($database['orderby']) > 0) - { - $sql .= "\nORDER BY "; - $sql .= implode(', ', $database['orderby']); - } - - if (is_numeric($database['limit'])) - { - $sql .= "\n"; - $sql .= $this->limit($database['limit'], $database['offset']); - } - - return $sql; - } - - public function escape_str($str) - { - if ( ! $this->db_config['escape']) - return $str; - - if (function_exists('sqlite_escape_string')) - { - $res = sqlite_escape_string($str); - } - else - { - $res = str_replace("'", "''", $str); - } - return $res; - } - - public function list_tables() - { - $sql = "SELECT `name` FROM `sqlite_master` WHERE `type`='table' ORDER BY `name`;"; - try - { - $result = $this->query($sql)->result(FALSE, PDO::FETCH_ASSOC); - $tables = array(); - foreach ($result as $row) - { - $tables[] = current($row); - } - } - catch (PDOException $e) - { - throw new Kohana_Database_Exception('database.error', $e->getMessage()); - } - return $tables; - } - - public function show_error() - { - $err = $this->link->errorInfo(); - return isset($err[2]) ? $err[2] : 'Unknown error!'; - } - - public function list_fields($table, $query = FALSE) - { - static $tables; - if (is_object($query)) - { - if (empty($tables[$table])) - { - $tables[$table] = array(); - - foreach ($query->result() as $row) - { - $tables[$table][] = $row->name; - } - } - - return $tables[$table]; - } - else - { - $result = $this->link->query( 'PRAGMA table_info('.$this->escape_table($table).')' ); - - foreach ($result as $row) - { - $tables[$table][$row['name']] = $this->sql_type($row['type']); - } - - return $tables[$table]; - } - } - - public function field_data($table) - { - Kohana::log('error', 'This method is under developing'); - } - /** - * Version number query string - * - * @access public - * @return string - */ - function version() - { - return $this->link->getAttribute(constant("PDO::ATTR_SERVER_VERSION")); - } - -} // End Database_PdoSqlite_Driver Class - -/* - * PDO-sqlite Result - */ -class Pdosqlite_Result extends Database_Result { - - // Data fetching types - protected $fetch_type = PDO::FETCH_OBJ; - protected $return_type = PDO::FETCH_ASSOC; - - /** - * Sets up the result variables. - * - * @param resource query result - * @param resource database link - * @param boolean return objects or arrays - * @param string SQL query that was run - */ - public function __construct($result, $link, $object = TRUE, $sql) - { - if (is_object($result) OR $result = $link->prepare($sql)) - { - // run the query. Return true if success, false otherwise - if( ! $result->execute()) - { - // Throw Kohana Exception with error message. See PDOStatement errorInfo() method - $arr_infos = $result->errorInfo(); - throw new Kohana_Database_Exception('database.error', $arr_infos[2]); - } - - if (preg_match('/^SELECT|PRAGMA|EXPLAIN/i', $sql)) - { - $this->result = $result; - $this->current_row = 0; - - $this->total_rows = $this->sqlite_row_count(); - - $this->fetch_type = ($object === TRUE) ? PDO::FETCH_OBJ : PDO::FETCH_ASSOC; - } - elseif (preg_match('/^DELETE|INSERT|UPDATE/i', $sql)) - { - $this->insert_id = $link->lastInsertId(); - - $this->total_rows = $result->rowCount(); - } - } - else - { - // SQL error - throw new Kohana_Database_Exception('database.error', $link->errorInfo().' - '.$sql); - } - - // Set result type - $this->result($object); - - // Store the SQL - $this->sql = $sql; - } - - private function sqlite_row_count() - { - $count = 0; - while ($this->result->fetch()) - { - $count++; - } - - // The query must be re-fetched now. - $this->result->execute(); - - return $count; - } - - /* - * Destructor: __destruct - * Magic __destruct function, frees the result. - */ - public function __destruct() - { - if (is_object($this->result)) - { - $this->result->closeCursor(); - $this->result = NULL; - } - } - - public function result($object = TRUE, $type = PDO::FETCH_BOTH) - { - $this->fetch_type = (bool) $object ? PDO::FETCH_OBJ : PDO::FETCH_BOTH; - - if ($this->fetch_type == PDO::FETCH_OBJ) - { - $this->return_type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass'; - } - else - { - $this->return_type = $type; - } - - return $this; - } - - public function as_array($object = NULL, $type = PDO::FETCH_ASSOC) - { - return $this->result_array($object, $type); - } - - public function result_array($object = NULL, $type = PDO::FETCH_ASSOC) - { - $rows = array(); - - if (is_string($object)) - { - $fetch = $object; - } - elseif (is_bool($object)) - { - if ($object === TRUE) - { - $fetch = PDO::FETCH_OBJ; - - // NOTE - The class set by $type must be defined before fetching the result, - // autoloading is disabled to save a lot of stupid overhead. - $type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass'; - } - else - { - $fetch = PDO::FETCH_OBJ; - } - } - else - { - // Use the default config values - $fetch = $this->fetch_type; - - if ($fetch == PDO::FETCH_OBJ) - { - $type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass'; - } - } - try - { - while ($row = $this->result->fetch($fetch)) - { - $rows[] = $row; - } - } - catch(PDOException $e) - { - throw new Kohana_Database_Exception('database.error', $e->getMessage()); - return FALSE; - } - return $rows; - } - - public function list_fields() - { - $field_names = array(); - for ($i = 0, $max = $this->result->columnCount(); $i < $max; $i++) - { - $info = $this->result->getColumnMeta($i); - $field_names[] = $info['name']; - } - return $field_names; - } - - public function seek($offset) - { - // To request a scrollable cursor for your PDOStatement object, you must - // set the PDO::ATTR_CURSOR attribute to PDO::CURSOR_SCROLL when you - // prepare the statement. - Kohana::log('error', get_class($this).' does not support scrollable cursors, '.__FUNCTION__.' call ignored'); - - return FALSE; - } - - public function offsetGet($offset) - { - try - { - return $this->result->fetch($this->fetch_type, PDO::FETCH_ORI_ABS, $offset); - } - catch(PDOException $e) - { - throw new Kohana_Database_Exception('database.error', $e->getMessage()); - } - } - - public function rewind() - { - // Same problem that seek() has, see above. - return $this->seek(0); - } - -} // End PdoSqlite_Result Class
\ No newline at end of file diff --git a/system/libraries/drivers/Database/Pgsql.php b/system/libraries/drivers/Database/Pgsql.php deleted file mode 100644 index c53c8439..00000000 --- a/system/libraries/drivers/Database/Pgsql.php +++ /dev/null @@ -1,538 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); -/** - * PostgreSQL 8.1+ Database Driver - * - * $Id: Pgsql.php 4344 2009-05-11 16:41:39Z zombor $ - * - * @package Core - * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html - */ -class Database_Pgsql_Driver extends Database_Driver { - - // Database connection link - protected $link; - protected $db_config; - - /** - * Sets the config for the class. - * - * @param array database configuration - */ - public function __construct($config) - { - $this->db_config = $config; - - Kohana::log('debug', 'PgSQL Database Driver Initialized'); - } - - public function connect() - { - // Check if link already exists - if (is_resource($this->link)) - return $this->link; - - // Import the connect variables - extract($this->db_config['connection']); - - // Persistent connections enabled? - $connect = ($this->db_config['persistent'] == TRUE) ? 'pg_pconnect' : 'pg_connect'; - - // Build the connection info - $port = isset($port) ? 'port=\''.$port.'\'' : ''; - $host = isset($host) ? 'host=\''.$host.'\' '.$port : ''; // if no host, connect with the socket - - $connection_string = $host.' dbname=\''.$database.'\' user=\''.$user.'\' password=\''.$pass.'\''; - // Make the connection and select the database - if ($this->link = $connect($connection_string)) - { - if ($charset = $this->db_config['character_set']) - { - echo $this->set_charset($charset); - } - - // Clear password after successful connect - $this->db_config['connection']['pass'] = NULL; - - return $this->link; - } - - return FALSE; - } - - public function query($sql) - { - // Only cache if it's turned on, and only cache if it's not a write statement - if ($this->db_config['cache'] AND ! preg_match('#\b(?:INSERT|UPDATE|SET)\b#i', $sql)) - { - $hash = $this->query_hash($sql); - - if ( ! isset($this->query_cache[$hash])) - { - // Set the cached object - $this->query_cache[$hash] = new Pgsql_Result(pg_query($this->link, $sql), $this->link, $this->db_config['object'], $sql); - } - else - { - // Rewind cached result - $this->query_cache[$hash]->rewind(); - } - - return $this->query_cache[$hash]; - } - - // Suppress warning triggered when a database error occurs (e.g., a constraint violation) - return new Pgsql_Result(@pg_query($this->link, $sql), $this->link, $this->db_config['object'], $sql); - } - - public function set_charset($charset) - { - $this->query('SET client_encoding TO '.pg_escape_string($this->link, $charset)); - } - - public function escape_table($table) - { - if (!$this->db_config['escape']) - return $table; - - return '"'.str_replace('.', '"."', $table).'"'; - } - - public function escape_column($column) - { - if (!$this->db_config['escape']) - return $column; - - if ($column == '*') - return $column; - - // This matches any functions we support to SELECT. - if ( preg_match('/(avg|count|sum|max|min)\(\s*(.*)\s*\)(\s*as\s*(.+)?)?/i', $column, $matches)) - { - if ( count($matches) == 3) - { - return $matches[1].'('.$this->escape_column($matches[2]).')'; - } - else if ( count($matches) == 5) - { - return $matches[1].'('.$this->escape_column($matches[2]).') AS '.$this->escape_column($matches[2]); - } - } - - // This matches any modifiers we support to SELECT. - if ( ! preg_match('/\b(?:all|distinct)\s/i', $column)) - { - if (stripos($column, ' AS ') !== FALSE) - { - // Force 'AS' to uppercase - $column = str_ireplace(' AS ', ' AS ', $column); - - // Runs escape_column on both sides of an AS statement - $column = array_map(array($this, __FUNCTION__), explode(' AS ', $column)); - - // Re-create the AS statement - return implode(' AS ', $column); - } - - return preg_replace('/[^.*]+/', '"$0"', $column); - } - - $parts = explode(' ', $column); - $column = ''; - - for ($i = 0, $c = count($parts); $i < $c; $i++) - { - // The column is always last - if ($i == ($c - 1)) - { - $column .= preg_replace('/[^.*]+/', '"$0"', $parts[$i]); - } - else // otherwise, it's a modifier - { - $column .= $parts[$i].' '; - } - } - return $column; - } - - public function regex($field, $match, $type, $num_regexs) - { - $prefix = ($num_regexs == 0) ? '' : $type; - - return $prefix.' '.$this->escape_column($field).' ~* \''.$this->escape_str($match).'\''; - } - - public function notregex($field, $match, $type, $num_regexs) - { - $prefix = $num_regexs == 0 ? '' : $type; - - return $prefix.' '.$this->escape_column($field).' !~* \''.$this->escape_str($match) . '\''; - } - - public function limit($limit, $offset = 0) - { - return 'LIMIT '.$limit.' OFFSET '.$offset; - } - - public function compile_select($database) - { - $sql = ($database['distinct'] == TRUE) ? 'SELECT DISTINCT ' : 'SELECT '; - $sql .= (count($database['select']) > 0) ? implode(', ', $database['select']) : '*'; - - if (count($database['from']) > 0) - { - $sql .= "\nFROM "; - $sql .= implode(', ', $database['from']); - } - - if (count($database['join']) > 0) - { - foreach($database['join'] AS $join) - { - $sql .= "\n".$join['type'].'JOIN '.implode(', ', $join['tables']).' ON '.$join['conditions']; - } - } - - if (count($database['where']) > 0) - { - $sql .= "\nWHERE "; - } - - $sql .= implode("\n", $database['where']); - - if (count($database['groupby']) > 0) - { - $sql .= "\nGROUP BY "; - $sql .= implode(', ', $database['groupby']); - } - - if (count($database['having']) > 0) - { - $sql .= "\nHAVING "; - $sql .= implode("\n", $database['having']); - } - - if (count($database['orderby']) > 0) - { - $sql .= "\nORDER BY "; - $sql .= implode(', ', $database['orderby']); - } - - if (is_numeric($database['limit'])) - { - $sql .= "\n"; - $sql .= $this->limit($database['limit'], $database['offset']); - } - - return $sql; - } - - public function escape_str($str) - { - if (!$this->db_config['escape']) - return $str; - - is_resource($this->link) or $this->connect(); - - return pg_escape_string($this->link, $str); - } - - public function list_tables() - { - $sql = 'SELECT table_schema || \'.\' || table_name FROM information_schema.tables WHERE table_schema NOT IN (\'pg_catalog\', \'information_schema\')'; - $result = $this->query($sql)->result(FALSE, PGSQL_ASSOC); - - $retval = array(); - foreach ($result as $row) - { - $retval[] = current($row); - } - - return $retval; - } - - public function show_error() - { - return pg_last_error($this->link); - } - - public function list_fields($table) - { - $result = NULL; - - foreach ($this->field_data($table) as $row) - { - // Make an associative array - $result[$row->column_name] = $this->sql_type($row->data_type); - - if (!strncmp($row->column_default, 'nextval(', 8)) - { - $result[$row->column_name]['sequenced'] = TRUE; - } - - if ($row->is_nullable === 'YES') - { - $result[$row->column_name]['null'] = TRUE; - } - } - - if (!isset($result)) - throw new Kohana_Database_Exception('database.table_not_found', $table); - - return $result; - } - - public function field_data($table) - { - // http://www.postgresql.org/docs/8.3/static/infoschema-columns.html - $result = $this->query(' - SELECT column_name, column_default, is_nullable, data_type, udt_name, - character_maximum_length, numeric_precision, numeric_precision_radix, numeric_scale - FROM information_schema.columns - WHERE table_name = \''. $this->escape_str($table) .'\' - ORDER BY ordinal_position - '); - - return $result->result_array(TRUE); - } - -} // End Database_Pgsql_Driver Class - -/** - * PostgreSQL Result - */ -class Pgsql_Result extends Database_Result { - - // Data fetching types - protected $fetch_type = 'pgsql_fetch_object'; - protected $return_type = PGSQL_ASSOC; - - /** - * Sets up the result variables. - * - * @param resource query result - * @param resource database link - * @param boolean return objects or arrays - * @param string SQL query that was run - */ - public function __construct($result, $link, $object = TRUE, $sql) - { - $this->link = $link; - $this->result = $result; - - // If the query is a resource, it was a SELECT, SHOW, DESCRIBE, EXPLAIN query - if (is_resource($result)) - { - // Its an DELETE, INSERT, REPLACE, or UPDATE query - if (preg_match('/^(?:delete|insert|replace|update)\b/iD', trim($sql), $matches)) - { - $this->insert_id = (strtolower($matches[0]) == 'insert') ? $this->insert_id() : FALSE; - $this->total_rows = pg_affected_rows($this->result); - } - else - { - $this->current_row = 0; - $this->total_rows = pg_num_rows($this->result); - $this->fetch_type = ($object === TRUE) ? 'pg_fetch_object' : 'pg_fetch_array'; - } - } - else - { - throw new Kohana_Database_Exception('database.error', pg_last_error().' - '.$sql); - } - - // Set result type - $this->result($object); - - // Store the SQL - $this->sql = $sql; - } - - /** - * Magic __destruct function, frees the result. - */ - public function __destruct() - { - if (is_resource($this->result)) - { - pg_free_result($this->result); - } - } - - public function result($object = TRUE, $type = PGSQL_ASSOC) - { - $this->fetch_type = ((bool) $object) ? 'pg_fetch_object' : 'pg_fetch_array'; - - // This check has to be outside the previous statement, because we do not - // know the state of fetch_type when $object = NULL - // NOTE - The class set by $type must be defined before fetching the result, - // autoloading is disabled to save a lot of stupid overhead. - if ($this->fetch_type == 'pg_fetch_object') - { - $this->return_type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass'; - } - else - { - $this->return_type = $type; - } - - return $this; - } - - public function as_array($object = NULL, $type = PGSQL_ASSOC) - { - return $this->result_array($object, $type); - } - - public function result_array($object = NULL, $type = PGSQL_ASSOC) - { - $rows = array(); - - if (is_string($object)) - { - $fetch = $object; - } - elseif (is_bool($object)) - { - if ($object === TRUE) - { - $fetch = 'pg_fetch_object'; - - // NOTE - The class set by $type must be defined before fetching the result, - // autoloading is disabled to save a lot of stupid overhead. - $type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass'; - } - else - { - $fetch = 'pg_fetch_array'; - } - } - else - { - // Use the default config values - $fetch = $this->fetch_type; - - if ($fetch == 'pg_fetch_object') - { - $type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass'; - } - } - - if ($this->total_rows) - { - pg_result_seek($this->result, 0); - - while ($row = $fetch($this->result, NULL, $type)) - { - $rows[] = $row; - } - } - - return $rows; - } - - public function insert_id() - { - if ($this->insert_id === NULL) - { - $query = 'SELECT LASTVAL() AS insert_id'; - - // Disable error reporting for this, just to silence errors on - // tables that have no serial column. - $ER = error_reporting(0); - - $result = pg_query($this->link, $query); - $insert_id = pg_fetch_array($result, NULL, PGSQL_ASSOC); - - $this->insert_id = $insert_id['insert_id']; - - // Reset error reporting - error_reporting($ER); - } - - return $this->insert_id; - } - - public function seek($offset) - { - if ($this->offsetExists($offset) AND pg_result_seek($this->result, $offset)) - { - // Set the current row to the offset - $this->current_row = $offset; - - return TRUE; - } - - return FALSE; - } - - public function list_fields() - { - $field_names = array(); - - $fields = pg_num_fields($this->result); - for ($i = 0; $i < $fields; ++$i) - { - $field_names[] = pg_field_name($this->result, $i); - } - - return $field_names; - } - - /** - * ArrayAccess: offsetGet - */ - public function offsetGet($offset) - { - if ( ! $this->seek($offset)) - return FALSE; - - // Return the row by calling the defined fetching callback - $fetch = $this->fetch_type; - return $fetch($this->result, NULL, $this->return_type); - } - -} // End Pgsql_Result Class - -/** - * PostgreSQL Prepared Statement (experimental) - */ -class Kohana_Pgsql_Statement { - - protected $link = NULL; - protected $stmt; - - public function __construct($sql, $link) - { - $this->link = $link; - - $this->stmt = $this->link->prepare($sql); - - return $this; - } - - public function __destruct() - { - $this->stmt->close(); - } - - // Sets the bind parameters - public function bind_params() - { - $argv = func_get_args(); - return $this; - } - - // sets the statement values to the bound parameters - public function set_vals() - { - return $this; - } - - // Runs the statement - public function execute() - { - return $this; - } -} diff --git a/system/libraries/drivers/Image.php b/system/libraries/drivers/Image.php 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 @@ +<?php defined('SYSPATH') OR die('No direct access allowed.'); +/** + * Log API driver. + * + * $Id: Log.php 4679 2009-11-10 01:45:52Z isaiah $ + * + * @package Kohana_Log + * @author Kohana Team + * @copyright (c) 2007-2009 Kohana Team + * @license http://kohanaphp.com/license + */ +abstract class Log_Driver { + + protected $config = array(); + + public function __construct(array $config) + { + $this->config = $config; + } + + abstract public function save(array $messages); +}
\ No newline at end of file diff --git a/system/libraries/drivers/Log/Database.php b/system/libraries/drivers/Log/Database.php new file mode 100644 index 00000000..19db9747 --- /dev/null +++ b/system/libraries/drivers/Log/Database.php @@ -0,0 +1,40 @@ +<?php defined('SYSPATH') OR die('No direct access allowed.'); +/** + * Log API driver. + * + * $Id: Database.php 4679 2009-11-10 01:45:52Z isaiah $ + * + * @package Kohana_Log + * @author Kohana Team + * @copyright (c) 2007-2009 Kohana Team + * @license http://kohanaphp.com/license + */ +class Log_Database_Driver extends Log_Driver { + + public function save(array $messages) + { + $insert = db::build($this->config['group']) + ->insert($this->config['table']) + ->columns(array('date', 'level', 'message')); + + $run_insert = FALSE; + + foreach ($messages AS $message) + { + if ($this->config['log_levels'][$message['type']] <= $this->config['log_threshold']) + { + // Add new message to database + $insert->values($message); + + // There is data to insert + $run_insert = TRUE; + } + } + + // Update the database + if ($run_insert) + { + $insert->execute(); + } + } +}
\ No newline at end of file diff --git a/system/libraries/drivers/Log/File.php b/system/libraries/drivers/Log/File.php new file mode 100644 index 00000000..6ad565b4 --- /dev/null +++ b/system/libraries/drivers/Log/File.php @@ -0,0 +1,44 @@ +<?php defined('SYSPATH') OR die('No direct access allowed.'); +/** + * Log API driver. + * + * $Id: File.php 4679 2009-11-10 01:45:52Z isaiah $ + * + * @package Kohana_Log + * @author Kohana Team + * @copyright (c) 2007-2009 Kohana Team + * @license http://kohanaphp.com/license + */ +class Log_File_Driver extends Log_Driver { + + public function save(array $messages) + { + // Filename of the log + $filename = $this->config['log_directory'].'/'.date('Y-m-d').'.log'.EXT; + + if ( ! is_file($filename)) + { + // Write the SYSPATH checking header + file_put_contents($filename, + '<?php defined(\'SYSPATH\') or die(\'No direct script access.\'); ?>'.PHP_EOL.PHP_EOL); + + // Prevent external writes + chmod($filename, $this->config['posix_permissions']); + } + + foreach ($messages AS $message) + { + if ($this->config['log_levels'][$message['type']] <= $this->config['log_threshold']) + { + // Add a new message line + $messages_to_write[] = date($this->config['date_format'], $message['date']).' --- '.$message['type'].': '.$message['message']; + } + } + + if ( ! empty($messages_to_write)) + { + // Write messages to log file + file_put_contents($filename, implode(PHP_EOL, $messages_to_write).PHP_EOL, FILE_APPEND); + } + } +}
\ No newline at end of file diff --git a/system/libraries/drivers/Log/Syslog.php b/system/libraries/drivers/Log/Syslog.php new file mode 100644 index 00000000..5da5d255 --- /dev/null +++ b/system/libraries/drivers/Log/Syslog.php @@ -0,0 +1,34 @@ +<?php defined('SYSPATH') OR die('No direct access allowed.'); +/** + * Log API driver. + * + * @package Kohana_Log + * @author Kohana Team + * @copyright (c) 2007-2009 Kohana Team + * @license http://kohanaphp.com/license + */ +class Log_Syslog_Driver extends Log_Driver { + + protected $syslog_levels = array('error' => LOG_ERR, + 'alert' => LOG_WARNING, + 'info' => LOG_INFO, + 'debug' => LOG_DEBUG); + + public function save(array $messages) + { + // Open the connection to syslog + openlog($this->config['ident'], LOG_CONS, LOG_USER); + + do + { + // Load the next message + list ($date, $type, $text) = array_shift($messages); + + syslog($this->syslog_levels[$type], $text); + } + while ( ! empty($messages)); + + // Close connection to syslog + closelog(); + } +}
\ No newline at end of file diff --git a/system/libraries/drivers/Session.php b/system/libraries/drivers/Session.php 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; } diff --git a/system/messages/core.php b/system/messages/core.php new file mode 100644 index 00000000..4bf6ee8c --- /dev/null +++ b/system/messages/core.php @@ -0,0 +1,32 @@ +<?php + +$messages = array +( + 'errors' => 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', +);
\ No newline at end of file diff --git a/system/vendor/Markdown.php b/system/vendor/Markdown.php new file mode 100644 index 00000000..b649f6c1 --- /dev/null +++ b/system/vendor/Markdown.php @@ -0,0 +1,2909 @@ +<?php +# +# Markdown Extra - A text-to-HTML conversion tool for web writers +# +# PHP Markdown & Extra +# Copyright (c) 2004-2008 Michel Fortin +# <http://www.michelf.com/projects/php-markdown/> +# +# Original Markdown +# Copyright (c) 2004-2006 John Gruber +# <http://daringfireball.net/projects/markdown/> +# + + +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: <a href="http://daringfireball.net/projects/markdown/syntax">Markdown syntax</a> allows you to write using an easy-to-read, easy-to-write plain text format. Based on the original Perl version by <a href="http://daringfireball.net/">John Gruber</a>. <a href="http://www.michelf.com/projects/php-markdown/">More...</a> +Version: 1.2.2 +Author: Michel Fortin +Author URI: http://www.michelf.com/ +*/ + +if (isset($wp_version)) { + # More details about how it works here: + # <http://www.michelf.com/weblog/2005/wordpress-text-flow-vs-markdown/> + + # 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(' ', + '<p> </p> <pre> </pre> <ol> </ol> <ul> </ul> <li> </li>'); + $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 = '<p>'.$text.'</p>'; + $text = preg_replace('{\n{2,}}', "</p>\n\n<p>", $text); + } + return $text; + } + + function mdwp_strip_p($t) { return preg_replace('{</?p>}i', '', $t); } + + function mdwp_hide_tags($text) { + global $mdwp_hidden_tags, $mdwp_placeholders; + return str_replace($mdwp_hidden_tags, $mdwp_placeholders, $text); + } + function mdwp_show_tags($text) { + global $mdwp_hidden_tags, $mdwp_placeholders; + return str_replace($mdwp_placeholders, $mdwp_hidden_tags, $text); + } +} + + +### bBlog Plugin Info ### + +function identify_modifier_markdown() { + return array( + 'name' => 'markdown', + 'type' => 'modifier', + 'nicename' => 'PHP Markdown Extra', + 'description' => 'A text-to-HTML conversion tool for web writers', + 'authors' => 'Michel Fortin and John Gruber', + 'licence' => 'GPL', + 'version' => MARKDOWNEXTRA_VERSION, + 'help' => '<a href="http://daringfireball.net/projects/markdown/syntax">Markdown syntax</a> allows you to write using an easy-to-read, easy-to-write plain text format. Based on the original Perl version by <a href="http://daringfireball.net/">John Gruber</a>. <a href="http://www.michelf.com/projects/php-markdown/">More...</a>', + ); +} + + +### Smarty Modifier Interface ### + +function smarty_modifier_markdown($text) { + return Markdown($text); +} + + +### Textile Compatibility Mode ### + +# Rename this file to "classTextile.php" and it can replace Textile everywhere. + +if (strcasecmp(substr(__FILE__, -16), "classTextile.php") == 0) { + # Try to include PHP SmartyPants. Should be in the same directory. + @include_once 'smartypants.php'; + # Fake Textile class. It calls Markdown instead. + class Textile { + function TextileThis($text, $lite='', $encode='') { + if ($lite == '' && $encode == '') $text = Markdown($text); + if (function_exists('SmartyPants')) $text = SmartyPants($text); + return $text; + } + # Fake restricted version: restrictions are not supported for now. + function TextileRestricted($text, $lite='', $noimage='') { + return $this->TextileThis($text, $lite); + } + # Workaround to ensure compatibility with TextPattern 4.0.3. + function blockLite($text) { return $text; } + } +} + + + +# +# Markdown Parser Class +# + +class Markdown_Parser { + + # Regex to match balanced [brackets]. + # Needed to insert a maximum bracked depth while converting to PHP. + var $nested_brackets_depth = 6; + var $nested_brackets_re; + + var $nested_url_parenthesis_depth = 4; + var $nested_url_parenthesis_re; + + # Table of hash values for escaped characters: + var $escape_chars = '\`*_{}[]()>#+-.!'; + var $escape_chars_re; + + # Change to ">" for HTML output. + var $empty_element_suffix = MARKDOWN_EMPTY_ELEMENT_SUFFIX; + var $tab_width = MARKDOWN_TAB_WIDTH; + + # Change to `true` to disallow markup or entities. + var $no_markup = false; + var $no_entities = false; + + # Predefined urls and titles for reference links and images. + var $predef_urls = array(); + var $predef_titles = array(); + + + function Markdown_Parser() { + # + # Constructor function. Initialize appropriate member variables. + # + $this->_initDetab(); + $this->prepareItalicsAndBold(); + + $this->nested_brackets_re = + str_repeat('(?>[^\[\]]+|\[', $this->nested_brackets_depth). + str_repeat('\])*', $this->nested_brackets_depth); + + $this->nested_url_parenthesis_re = + str_repeat('(?>[^()\s]+|\(', $this->nested_url_parenthesis_depth). + str_repeat('(?>\)))*', $this->nested_url_parenthesis_depth); + + $this->escape_chars_re = '['.preg_quote($this->escape_chars).']'; + + # Sort document, block, and span gamut in ascendent priority order. + asort($this->document_gamut); + asort($this->block_gamut); + asort($this->span_gamut); + } + + + # Internal hashes used during transformation. + var $urls = array(); + var $titles = array(); + var $html_hashes = array(); + + # Status flag to avoid invalid nesting. + var $in_anchor = false; + + + function setup() { + # + # Called before the transformation process starts to setup parser + # states. + # + # Clear global hashes. + $this->urls = $this->predef_urls; + $this->titles = $this->predef_titles; + $this->html_hashes = array(); + + $in_anchor = false; + } + + function teardown() { + # + # Called after the transformation process to clear any variable + # which may be taking up memory unnecessarly. + # + $this->urls = array(); + $this->titles = array(); + $this->html_hashes = array(); + } + + + function transform($text) { + # + # Main function. Performs some preprocessing on the input text + # and pass it through the document gamut. + # + $this->setup(); + + # Remove UTF-8 BOM and marker character in input, if present. + $text = preg_replace('{^\xEF\xBB\xBF|\x1A}', '', $text); + + # Standardize line endings: + # DOS to Unix and Mac to Unix + $text = preg_replace('{\r\n?}', "\n", $text); + + # Make sure $text ends with a couple of newlines: + $text .= "\n\n"; + + # Convert all tabs to spaces. + $text = $this->detab($text); + + # Turn block-level HTML blocks into hash entries + $text = $this->hashHTMLBlocks($text); + + # Strip any lines consisting only of spaces and tabs. + # This makes subsequent regexen easier to write, because we can + # match consecutive blank lines with /\n+/ instead of something + # contorted like /[ ]*\n+/ . + $text = preg_replace('/^[ ]+$/m', '', $text); + + # Run document gamut methods. + foreach ($this->document_gamut as $method => $priority) { + $text = $this->$method($text); + } + + $this->teardown(); + + return $text . "\n"; + } + + var $document_gamut = array( + # Strip link definitions, store in hashes. + "stripLinkDefinitions" => 20, + + "runBasicBlockGamut" => 30, + ); + + + function stripLinkDefinitions($text) { + # + # Strips link definitions from text, stores the URLs and titles in + # hash references. + # + $less_than_tab = $this->tab_width - 1; + + # Link defs are in the form: ^[id]: url "optional title" + $text = preg_replace_callback('{ + ^[ ]{0,'.$less_than_tab.'}\[(.+)\][ ]?: # id = $1 + [ ]* + \n? # maybe *one* newline + [ ]* + <?(\S+?)>? # url = $2 + [ ]* + \n? # maybe one newline + [ ]* + (?: + (?<=\s) # lookbehind for whitespace + ["(] + (.*?) # title = $3 + [")] + [ ]* + )? # title is optional + (?:\n+|\Z) + }xm', + array(&$this, '_stripLinkDefinitions_callback'), + $text); + return $text; + } + function _stripLinkDefinitions_callback($matches) { + $link_id = strtolower($matches[1]); + $this->urls[$link_id] = $matches[2]; + $this->titles[$link_id] =& $matches[3]; + return ''; # String that will replace the block + } + + + function hashHTMLBlocks($text) { + if ($this->no_markup) return $text; + + $less_than_tab = $this->tab_width - 1; + + # Hashify HTML blocks: + # We only want to do this for block-level HTML tags, such as headers, + # lists, and tables. That's because we still want to wrap <p>s around + # "paragraphs" that are wrapped in non-block-level tags, such as anchors, + # phrase emphasis, and spans. The list of tags we're looking for is + # hard-coded: + # + # * List "a" is made of tags which can be both inline or block-level. + # These will be treated block-level when the start tag is alone on + # its line, otherwise they're not matched here and will be taken as + # inline later. + # * List "b" is made of tags which are always block-level; + # + $block_tags_a_re = 'ins|del'; + $block_tags_b_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|'. + 'script|noscript|form|fieldset|iframe|math'; + + # Regular expression for the content of a block tag. + $nested_tags_level = 4; + $attr = ' + (?> # optional tag attributes + \s # starts with whitespace + (?> + [^>"/]+ # text outside quotes + | + /+(?!>) # slash not followed by ">" + | + "[^"]*" # text inside double quotes (tolerate ">") + | + \'[^\']*\' # text inside single quotes (tolerate ">") + )* + )? + '; + $content = + str_repeat(' + (?> + [^<]+ # content without tag + | + <\2 # nested opening tag + '.$attr.' # attributes + (?> + /> + | + >', $nested_tags_level). # end of opening tag + '.*?'. # last level nested tag content + str_repeat(' + </\2\s*> # closing nested tag + ) + | + <(?!/\2\s*> # other tags with a different name + ) + )*', + $nested_tags_level); + $content2 = str_replace('\2', '\3', $content); + + # First, look for nested blocks, e.g.: + # <div> + # <div> + # tags for inner block must be indented. + # </div> + # </div> + # + # 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 `<div>` and stop at the first `</div>`. + $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<tag>` to `</tag>\n`, handling nested tags + # in between. + + [ ]{0,'.$less_than_tab.'} + <('.$block_tags_b_re.')# start tag = $2 + '.$attr.'> # attributes followed by > and \n + '.$content.' # content, support nesting + </\2> # the matching end tag + [ ]* # trailing spaces/tabs + (?=\n+|\Z) # followed by a newline or end of document + + | # Special version for tags of group a. + + [ ]{0,'.$less_than_tab.'} + <('.$block_tags_a_re.')# start tag = $3 + '.$attr.'>[ ]*\n # attributes followed by > + '.$content2.' # content, support nesting + </\3> # the matching end tag + [ ]* # trailing spaces/tabs + (?=\n+|\Z) # followed by a newline or end of document + + | # Special case just for <hr />. It was easier to make a special + # case than to make the other regex more complicated. + + [ ]{0,'.$less_than_tab.'} + <(hr) # start tag = $2 + '.$attr.' # attributes + /?> # the matching end tag + [ ]* + (?=\n{2,}|\Z) # followed by a blank line or end of document + + | # Special case for standalone HTML comments: + + [ ]{0,'.$less_than_tab.'} + (?s: + <!-- .*? --> + ) + [ ]* + (?=\n{2,}|\Z) # followed by a blank line or end of document + + | # PHP and ASP-style processor instructions (<? and <%) + + [ ]{0,'.$less_than_tab.'} + (?s: + <([?%]) # $2 + .*? + \2> + ) + [ ]* + (?=\n{2,}|\Z) # followed by a blank line or end of document + + ) + )}Sxmi', + array(&$this, '_hashHTMLBlocks_callback'), + $text); + + return $text; + } + function _hashHTMLBlocks_callback($matches) { + $text = $matches[1]; + $key = $this->hashBlock($text); + return "\n\n$key\n\n"; + } + + + function hashPart($text, $boundary = 'X') { + # + # Called whenever a tag must be hashed when a function insert an atomic + # element in the text stream. Passing $text to through this function gives + # a unique text-token which will be reverted back when calling unhash. + # + # The $boundary argument specify what character should be used to surround + # the token. By convension, "B" is used for block elements that needs not + # to be wrapped into paragraph tags at the end, ":" is used for elements + # that are word separators and "X" is used in the general case. + # + # Swap back any tag hash found in $text so we do not have to `unhash` + # multiple times at the end. + $text = $this->unhash($text); + + # Then hash the block. + static $i = 0; + $key = "$boundary\x1A" . ++$i . $boundary; + $this->html_hashes[$key] = $text; + return $key; # String that will replace the tag. + } + + + function hashBlock($text) { + # + # Shortcut function for hashPart with block-level boundaries. + # + return $this->hashPart($text, 'B'); + } + + + var $block_gamut = array( + # + # These are all the transformations that form block-level + # tags like paragraphs, headers, and list items. + # + "doHeaders" => 10, + "doHorizontalRules" => 20, + + "doLists" => 40, + "doCodeBlocks" => 50, + "doBlockQuotes" => 60, + ); + + function runBlockGamut($text) { + # + # Run block gamut tranformations. + # + # We need to escape raw HTML in Markdown source before doing anything + # else. This need to be done for each block, and not only at the + # begining in the Markdown function since hashed blocks can be part of + # list items and could have been indented. Indented blocks would have + # been seen as a code block in a previous pass of hashHTMLBlocks. + $text = $this->hashHTMLBlocks($text); + + return $this->runBasicBlockGamut($text); + } + + function runBasicBlockGamut($text) { + # + # Run block gamut tranformations, without hashing HTML blocks. This is + # useful when HTML blocks are known to be already hashed, like in the first + # whole-document pass. + # + foreach ($this->block_gamut as $method => $priority) { + $text = $this->$method($text); + } + + # Finally form paragraph and restore hashed blocks. + $text = $this->formParagraphs($text); + + return $text; + } + + + function doHorizontalRules($text) { + # Do Horizontal Rules: + return preg_replace( + '{ + ^[ ]{0,3} # Leading space + ([-*_]) # $1: First marker + (?> # Repeated marker group + [ ]{0,2} # Zero, one, or two spaces. + \1 # Marker character + ){2,} # Group repeated at least twice + [ ]* # Tailing spaces + $ # End of line. + }mx', + "\n".$this->hashBlock("<hr$this->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 `<http://example.com/>` + # Must come after doAnchors, because you can use < and > + # delimiters in inline links like [this](<url>). + "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("<br$this->empty_element_suffix\n"); + } + + + function doAnchors($text) { + # + # Turn Markdown link shortcuts into XHTML <a> 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 = "<a href=\"$url\""; + if ( isset( $this->titles[$link_id] ) ) { + $title = $this->titles[$link_id]; + $title = $this->encodeAttribute($title); + $result .= " title=\"$title\""; + } + + $link_text = $this->runSpanGamut($link_text); + $result .= ">$link_text</a>"; + $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 = "<a href=\"$url\""; + if (isset($title)) { + $title = $this->encodeAttribute($title); + $result .= " title=\"$title\""; + } + + $link_text = $this->runSpanGamut($link_text); + $result .= ">$link_text</a>"; + + return $this->hashPart($result); + } + + + function doImages($text) { + # + # Turn Markdown image shortcuts into <img> tags. + # + # + # First, handle reference-style labeled images: ![alt text][id] + # + $text = preg_replace_callback('{ + ( # wrap whole match in $1 + !\[ + ('.$this->nested_brackets_re.') # alt text = $2 + \] + + [ ]? # one optional space + (?:\n[ ]*)? # one optional newline followed by spaces + + \[ + (.*?) # id = $3 + \] + + ) + }xs', + array(&$this, '_doImages_reference_callback'), $text); + + # + # Next, handle inline images:  + # Don't forget: encode * and _ + # + $text = preg_replace_callback('{ + ( # wrap whole match in $1 + !\[ + ('.$this->nested_brackets_re.') # alt text = $2 + \] + \s? # One optional whitespace character + \( # literal paren + [ ]* + (?: + <(\S*)> # src url = $3 + | + ('.$this->nested_url_parenthesis_re.') # src url = $4 + ) + [ ]* + ( # $5 + ([\'"]) # quote char = $6 + (.*?) # title = $7 + \6 # matching quote + [ ]* + )? # title is optional + \) + ) + }xs', + array(&$this, '_doImages_inline_callback'), $text); + + return $text; + } + function _doImages_reference_callback($matches) { + $whole_match = $matches[1]; + $alt_text = $matches[2]; + $link_id = strtolower($matches[3]); + + if ($link_id == "") { + $link_id = strtolower($alt_text); # for shortcut links like ![this][]. + } + + $alt_text = $this->encodeAttribute($alt_text); + if (isset($this->urls[$link_id])) { + $url = $this->encodeAttribute($this->urls[$link_id]); + $result = "<img src=\"$url\" alt=\"$alt_text\""; + if (isset($this->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 = "<img src=\"$url\" alt=\"$alt_text\""; + if (isset($title)) { + $title = $this->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 = "<h$level>".$this->runSpanGamut($matches[1])."</h$level>"; + return "\n" . $this->hashBlock($block) . "\n\n"; + } + function _doHeaders_callback_atx($matches) { + $level = strlen($matches[1]); + $block = "<h$level>".$this->runSpanGamut($matches[2])."</h$level>"; + return "\n" . $this->hashBlock($block) . "\n\n"; + } + + + function doLists($text) { + # + # Form HTML ordered (numbered) and unordered (bulleted) lists. + # + $less_than_tab = $this->tab_width - 1; + + # Re-usable patterns to match list item bullets and number markers: + $marker_ul_re = '[*+-]'; + $marker_ol_re = '\d+[.]'; + $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)"; + + $markers_relist = array($marker_ul_re, $marker_ol_re); + + foreach ($markers_relist as $marker_re) { + # Re-usable pattern to match any entirel ul or ol list: + $whole_list_re = ' + ( # $1 = whole list + ( # $2 + [ ]{0,'.$less_than_tab.'} + ('.$marker_re.') # $3 = first list item marker + [ ]+ + ) + (?s:.+?) + ( # $4 + \z + | + \n{2,} + (?=\S) + (?! # Negative lookahead for another list item marker + [ ]* + '.$marker_re.'[ ]+ + ) + ) + ) + '; // mx + + # We use a different prefix before nested lists than top-level lists. + # See extended comment in _ProcessListItems(). + + if ($this->list_level) { + $text = preg_replace_callback('{ + ^ + '.$whole_list_re.' + }mx', + array(&$this, '_doLists_callback'), $text); + } + else { + $text = preg_replace_callback('{ + (?:(?<=\n)\n|\A\n?) # Must eat the newline + '.$whole_list_re.' + }mx', + array(&$this, '_doLists_callback'), $text); + } + } + + return $text; + } + function _doLists_callback($matches) { + # Re-usable patterns to match list item bullets and number markers: + $marker_ul_re = '[*+-]'; + $marker_ol_re = '\d+[.]'; + $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)"; + + $list = $matches[1]; + $list_type = preg_match("/$marker_ul_re/", $matches[3]) ? "ul" : "ol"; + + $marker_any_re = ( $list_type == "ul" ? $marker_ul_re : $marker_ol_re ); + + $list .= "\n"; + $result = $this->processListItems($list, $marker_any_re); + + $result = $this->hashBlock("<$list_type>\n" . $result . "</$list_type>"); + return "\n". $result ."\n\n"; + } + + var $list_level = 0; + + function processListItems($list_str, $marker_any_re) { + # + # Process the contents of a single ordered or unordered list, splitting it + # into individual list items. + # + # The $this->list_level global keeps track of when we're inside a list. + # Each time we enter a list, we increment it; when we leave a list, + # we decrement. If it's zero, we're not in a list anymore. + # + # We do this because when we're not inside a list, we want to treat + # something like this: + # + # I recommend upgrading to version + # 8. Oops, now this line is treated + # as a sub-list. + # + # As a single paragraph, despite the fact that the second line starts + # with a digit-period-space sequence. + # + # Whereas when we're inside a list (or sub-list), that line will be + # treated as the start of a sub-list. What a kludge, huh? This is + # an aspect of Markdown's syntax that's hard to parse perfectly + # without resorting to mind-reading. Perhaps the solution is to + # change the syntax rules such that sub-lists must start with a + # starting cardinal number; e.g. "1." or "a.". + + $this->list_level++; + + # trim trailing blank lines: + $list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str); + + $list_str = preg_replace_callback('{ + (\n)? # leading line = $1 + (^[ ]*) # leading whitespace = $2 + ('.$marker_any_re.' # list marker and space = $3 + (?:[ ]+|(?=\n)) # space only required if item is not empty + ) + ((?s:.*?)) # list item text = $4 + (?:(\n+(?=\n))|\n) # tailing blank line = $5 + (?= \n* (\z | \2 ('.$marker_any_re.') (?:[ ]+|(?=\n)))) + }xm', + array(&$this, '_processListItems_callback'), $list_str); + + $this->list_level--; + return $list_str; + } + function _processListItems_callback($matches) { + $item = $matches[4]; + $leading_line =& $matches[1]; + $leading_space =& $matches[2]; + $marker_space = $matches[3]; + $tailing_blank_line =& $matches[5]; + + if ($leading_line || $tailing_blank_line || + preg_match('/\n{2,}/', $item)) + { + # Replace marker with the appropriate whitespace indentation + $item = $leading_space . str_repeat(' ', strlen($marker_space)) . $item; + $item = $this->runBlockGamut($this->outdent($item)."\n"); + } + else { + # Recursion for sub-lists: + $item = $this->doLists($this->outdent($item)); + $item = preg_replace('/\n+$/', '', $item); + $item = $this->runSpanGamut($item); + } + + return "<li>" . $item . "</li>\n"; + } + + + function doCodeBlocks($text) { + # + # Process Markdown `<pre><code>` 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 = "<pre><code>$codeblock\n</code></pre>"; + 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>$code</code>"); + } + + + var $em_relist = array( + '' => '(?:(?<!\*)\*(?!\*)|(?<!_)_(?!_))(?=\S)(?![.,:;]\s)', + '*' => '(?<=\S)(?<!\*)\*(?!\*)', + '_' => '(?<=\S)(?<!_)_(?!_)', + ); + var $strong_relist = array( + '' => '(?:(?<!\*)\*\*(?!\*)|(?<!_)__(?!_))(?=\S)(?![.,:;]\s)', + '**' => '(?<=\S)(?<!\*)\*\*(?!\*)', + '__' => '(?<=\S)(?<!_)__(?!_)', + ); + var $em_strong_relist = array( + '' => '(?:(?<!\*)\*\*\*(?!\*)|(?<!_)___(?!_))(?=\S)(?![.,:;]\s)', + '***' => '(?<=\S)(?<!\*)\*\*\*(?!\*)', + '___' => '(?<=\S)(?<!_)___(?!_)', + ); + var $em_strong_prepared_relist; + + function prepareItalicsAndBold() { + # + # Prepare regular expressions for seraching emphasis tokens in any + # context. + # + foreach ($this->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 = "<strong><em>$span</em></strong>"; + $text_stack[0] .= $this->hashPart($span); + $em = ''; + $strong = ''; + } else { + # Other closing marker: close one em or strong and + # change current token state to match the other + $token_stack[0] = str_repeat($token{0}, 3-$token_len); + $tag = $token_len == 2 ? "strong" : "em"; + $span = $text_stack[0]; + $span = $this->runSpanGamut($span); + $span = "<$tag>$span</$tag>"; + $text_stack[0] = $this->hashPart($span); + $$tag = ''; # $$tag stands for $em or $strong + } + $tree_char_em = false; + } else if ($token_len == 3) { + if ($em) { + # Reached closing marker for both em and strong. + # Closing strong marker: + for ($i = 0; $i < 2; ++$i) { + $shifted_token = array_shift($token_stack); + $tag = strlen($shifted_token) == 2 ? "strong" : "em"; + $span = array_shift($text_stack); + $span = $this->runSpanGamut($span); + $span = "<$tag>$span</$tag>"; + $text_stack[0] .= $this->hashPart($span); + $$tag = ''; # $$tag stands for $em or $strong + } + } else { + # Reached opening three-char emphasis marker. Push on token + # stack; will be handled by the special condition above. + $em = $token{0}; + $strong = "$em$em"; + array_unshift($token_stack, $token); + array_unshift($text_stack, ''); + $tree_char_em = true; + } + } else if ($token_len == 2) { + if ($strong) { + # Unwind any dangling emphasis marker: + if (strlen($token_stack[0]) == 1) { + $text_stack[1] .= array_shift($token_stack); + $text_stack[0] .= array_shift($text_stack); + } + # Closing strong marker: + array_shift($token_stack); + $span = array_shift($text_stack); + $span = $this->runSpanGamut($span); + $span = "<strong>$span</strong>"; + $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 = "<em>$span</em>"; + $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 <pre> content, + # so we need to fix that: + $bq = preg_replace_callback('{(\s*<pre>.+?</pre>)}sx', + array(&$this, '_DoBlockQuotes_callback2'), $bq); + + return "\n". $this->hashBlock("<blockquote>\n$bq\n</blockquote>")."\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 <p> 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 <p> 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('/^([ ]*)/', "<p>", $value); + $value .= "</p>"; + $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 = <div> tag +// <div \s+ +// [^>]* +// \b +// markdown\s*=\s* ([\'"]) # $2 = attr quote char +// 1 +// \2 +// [^>]* +// > +// ) +// ( # $3 = contents +// .* +// ) +// (</div>) # $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: <http://bumppo.net/projects/amputator/> + $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: <address@domain.foo> + $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 = "<a href=\"$url\">$url</a>"; + 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.: + # + # <p><a href="mailto:foo + # @example.co + # m">foo@exampl + # e.com</a></p> + # + # Based by a filter by Matthew Wickline, posted to BBEdit-Talk. + # With some optimizations by Milian Wolff. + # + $addr = "mailto:" . $addr; + $chars = preg_split('/(?<!^)(?!$)/', $addr); + $seed = (int)abs(crc32($addr) / strlen($addr)); # Deterministic seed. + + foreach ($chars as $key => $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 = "<a href=\"$addr\">$text</a>"; + + 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.' + | + (?<![`\\\\]) + `+ # code span marker + '.( $this->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 <p>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: + # + # <div> + # <div markdown="1"> + # Hello World. <-- Is this a Markdown code block or text? + # </div> <-- Is this a Markdown code block or a real tag? + # <div> + # + # If you don't like this, just don't indent the tag on which + # you apply the markdown="1" attribute. + # + # * If $enclosing_tag_re is not empty, stops at the first unmatched closing + # tag with that name. Nested tags supported. + # + # * If $span is true, text inside must treated as span. So any double + # newline will be replaced by a single newline so that it does not create + # paragraphs. + # + # Returns an array of that form: ( processed text , remaining text ) + # + if ($text === '') return array('', ''); + + # Regex to check for the presense of newlines around a block tag. + $newline_before_re = '/(?:^\n?|\n\n)*$/'; + $newline_after_re = + '{ + ^ # Start of text following the tag. + (?>[ ]*<!--.*?-->)? # Optional comment. + [ ]*\n # Must be followed by newline. + }xs'; + + # Regex to match any tag. + $block_tag_re = + '{ + ( # $2: Capture hole tag. + </? # Any opening or closing tag. + (?> # Tag name. + '.$this->block_tags_re.' | + '.$this->context_block_tags_re.' | + '.$this->clean_tags_re.' | + (?!\s)'.$enclosing_tag_re.' + ) + (?: + (?=[\s"\'/a-zA-Z0-9]) # Allowed characters after tag name. + (?> + ".*?" | # Double quotes (can contain `>`) + \'.*?\' | # Single quotes (can contain `>`) + .+? # Anything but quotes and `>`. + )*? + )? + > # End of tag. + | + <!-- .*? --> # HTML Comment + | + <\?.*?\?> | <%.*?%> # Processing instruction + | + <!\[CDATA\[.*?\]\]> # 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))*?(?<!`)'.$tag_re.'(?!`)}', + $text, $matches)) + { + # End marker found: pass text unchanged until marker. + $parsed .= $tag . $matches[0]; + $text = substr($text, strlen($matches[0])); + } + else { + # Unmatched marker: just skip it. + $parsed .= $tag; + } + } + # + # Check for: Indented code block or fenced code block marker. + # + else if ($tag{0} == "\n" || $tag{0} == "~") { + if ($tag{1} == "\n" || $tag{1} == " ") { + # Indented code block: pass it unchanged, will be handled + # later. + $parsed .= $tag; + } + else { + # Fenced code block marker: find matching end marker. + $tag_re = preg_quote(trim($tag)); + if (preg_match('{^(?>.*\n)+?'.$tag_re.' *\n}', $text, + $matches)) + { + # End marker found: pass text unchanged until marker. + $parsed .= $tag . $matches[0]; + $text = substr($text, strlen($matches[0])); + } + else { + # No end marker: just skip it. + $parsed .= $tag; + } + } + } + # + # Check for: Opening Block level tag or + # Opening Context Block tag (like ins and del) + # used as a block tag (tag is alone on it's line). + # + else if (preg_match('{^<(?:'.$this->block_tags_re.')\b}', $tag) || + ( preg_match('{^<(?:'.$this->context_block_tags_re.')\b}', $tag) && + preg_match($newline_before_re, $parsed) && + preg_match($newline_after_re, $text) ) + ) + { + # Need to parse tag and following text using the HTML parser. + list($block_text, $text) = + $this->_hashHTMLBlocks_inHTML($tag . $text, "hashBlock", true); + + # Make sure it stays outside of any paragraph by adding newlines. + $parsed .= "\n\n$block_text\n\n"; + } + # + # Check for: Clean tag (like script, math) + # HTML Comments, processing instructions. + # + else if (preg_match('{^<(?:'.$this->clean_tags_re.')\b}', $tag) || + $tag{1} == '!' || $tag{1} == '?') + { + # Need to parse tag and following text using the HTML parser. + # (don't check for markdown attribute) + list($block_text, $text) = + $this->_hashHTMLBlocks_inHTML($tag . $text, "hashClean", false); + + $parsed .= $block_text; + } + # + # Check for: Tag with same name as enclosing tag. + # + else if ($enclosing_tag_re !== '' && + # Same name as enclosing tag. + preg_match('{^</?(?:'.$enclosing_tag_re.')\b}', $tag)) + { + # + # Increase/decrease nested tag count. + # + if ($tag{1} == '/') $depth--; + else if ($tag{strlen($tag)-2} != '/') $depth++; + + if ($depth < 0) { + # + # Going out of parent element. Clean up and break so we + # return to the calling function. + # + $text = $tag . $text; + break; + } + + $parsed .= $tag; + } + else { + $parsed .= $tag; + } + } while ($depth >= 0); + + return array($parsed, $text); + } + function _hashHTMLBlocks_inHTML($text, $hash_method, $md_attr) { + # + # Parse HTML, calling _HashHTMLBlocks_InMarkdown for block tags. + # + # * Calls $hash_method to convert any blocks. + # * Stops when the first opening tag closes. + # * $md_attr indicate if the use of the `markdown="1"` attribute is allowed. + # (it is not inside clean tags) + # + # Returns an array of that form: ( processed text , remaining text ) + # + if ($text === '') return array('', ''); + + # Regex to match `markdown` attribute inside of a tag. + $markdown_attr_re = ' + { + \s* # Eat whitespace before the `markdown` attribute + markdown + \s*=\s* + (?> + (["\']) # $1: quote delimiter + (.*?) # $2: attribute value + \1 # matching delimiter + | + ([^\s>]*) # $3: unquoted attribute value + ) + () # $4: make $3 always defined (avoid warnings) + }xs'; + + # Regex to match any tag. + $tag_re = '{ + ( # $2: Capture hole tag. + </? # Any opening or closing tag. + [\w:$]+ # Tag name. + (?: + (?=[\s"\'/a-zA-Z0-9]) # Allowed characters after tag name. + (?> + ".*?" | # Double quotes (can contain `>`) + \'.*?\' | # Single quotes (can contain `>`) + .+? # Anything but quotes and `>`. + )*? + )? + > # End of tag. + | + <!-- .*? --> # HTML Comment + | + <\?.*?\?> | <%.*?%> # Processing instruction + | + <!\[CDATA\[.*?\]\]> # 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 <hr/>) + # Comments and Processing Instructions. + # + if (preg_match('{^</?(?:'.$this->auto_close_tags_re.')\b}', $tag) || + $tag{1} == '!' || $tag{1} == '?') + { + # Just add the tag to the block as if it was text. + $block_text .= $tag; + } + else { + # + # Increase/decrease nested tag count. Only do so if + # the tag's name match base tag's. + # + if (preg_match('{^</?'.$base_tag_name_re.'\b}', $tag)) { + if ($tag{1} == '/') $depth--; + else if ($tag{strlen($tag)-2} != '/') $depth++; + } + + # + # Check for `markdown="1"` attribute and handle it. + # + if ($md_attr && + preg_match($markdown_attr_re, $tag, $attr_m) && + preg_match('/^1|block|span$/', $attr_m[2] . $attr_m[3])) + { + # Remove `markdown` attribute from opening tag. + $tag = preg_replace($markdown_attr_re, '', $tag); + + # Check if text inside this tag must be parsed in span mode. + $this->mode = $attr_m[2] . $attr_m[3]; + $span_mode = $this->mode == 'span' || $this->mode != 'block' && + preg_match('{^<(?:'.$this->contain_span_tags_re.')\b}', $tag); + + # Calculate indent before tag. + if (preg_match('/(?:^|\n)( *?)(?! ).*?$/', $block_text, $matches)) { + $strlen = $this->utf8_strlen; + $indent = $strlen($matches[1], 'UTF-8'); + } else { + $indent = 0; + } + + # End preceding block with this tag. + $block_text .= $tag; + $parsed .= $this->$hash_method($block_text); + + # Get enclosing tag name for the ParseMarkdown function. + # (This pattern makes $tag_name_re safe without quoting.) + preg_match('/^<([\w:$]*)\b/', $tag, $matches); + $tag_name_re = $matches[1]; + + # Parse the content using the HTML-in-Markdown parser. + list ($block_text, $text) + = $this->_hashHTMLBlocks_inMarkdown($text, $indent, + $tag_name_re, $span_mode); + + # Outdent markdown text. + if ($indent > 0) { + $block_text = preg_replace("/^[ ]{1,$indent}/m", "", + $block_text); + } + + # Append tag content to parsed text. + if (!$span_mode) $parsed .= "\n\n$block_text\n\n"; + else $parsed .= "$block_text"; + + # Start over a new block. + $block_text = ""; + } + else $block_text .= $tag; + } + + } while ($depth > 0); + + # + # Hash last block text that wasn't processed inside the loop. + # + $parsed .= $this->$hash_method($block_text); + + return array($parsed, $text); + } + + + function hashClean($text) { + # + # Called whenever a tag must be hashed when a function insert a "clean" tag + # in $text, it pass through this function and is automaticaly escaped, + # blocking invalid nested overlap. + # + return $this->hashPart($text, 'C'); + } + + + function doHeaders($text) { + # + # Redefined to add id attribute support. + # + # Setext-style headers: + # Header 1 {#header1} + # ======== + # + # Header 2 {#header2} + # -------- + # + $text = preg_replace_callback( + '{ + (^.+?) # $1: Header text + (?:[ ]+\{\#([-_:a-zA-Z0-9]+)\})? # $2: Id attribute + [ ]*\n(=+|-+)[ ]*\n+ # $3: Header footer + }mx', + array(&$this, '_doHeaders_callback_setext'), $text); + + # atx-style headers: + # # Header 1 {#header1} + # ## Header 2 {#header2} + # ## Header 2 with closing hashes ## {#header3} + # ... + # ###### Header 6 {#header2} + # + $text = preg_replace_callback('{ + ^(\#{1,6}) # $1 = string of #\'s + [ ]* + (.+?) # $2 = Header text + [ ]* + \#* # optional closing #\'s (not counted) + (?:[ ]+\{\#([-_:a-zA-Z0-9]+)\})? # id attribute + [ ]* + \n+ + }xm', + array(&$this, '_doHeaders_callback_atx'), $text); + + return $text; + } + function _doHeaders_attr($attr) { + if (empty($attr)) return ""; + return " id=\"$attr\""; + } + function _doHeaders_callback_setext($matches) { + if ($matches[3] == '-' && preg_match('{^- }', $matches[1])) + return $matches[0]; + $level = $matches[3]{0} == '=' ? 1 : 2; + $attr = $this->_doHeaders_attr($id =& $matches[2]); + $block = "<h$level$attr>".$this->runSpanGamut($matches[1])."</h$level>"; + return "\n" . $this->hashBlock($block) . "\n\n"; + } + function _doHeaders_callback_atx($matches) { + $level = strlen($matches[1]); + $attr = $this->_doHeaders_attr($id =& $matches[3]); + $block = "<h$level$attr>".$this->runSpanGamut($matches[2])."</h$level>"; + 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 = "<table>\n"; + $text .= "<thead>\n"; + $text .= "<tr>\n"; + foreach ($headers as $n => $header) + $text .= " <th$attr[$n]>".$this->runSpanGamut(trim($header))."</th>\n"; + $text .= "</tr>\n"; + $text .= "</thead>\n"; + + # Split content by row. + $rows = explode("\n", trim($content, "\n")); + + $text .= "<tbody>\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 .= "<tr>\n"; + foreach ($row_cells as $n => $cell) + $text .= " <td$attr[$n]>".$this->runSpanGamut(trim($cell))."</td>\n"; + $text .= "</tr>\n"; + } + $text .= "</tbody>\n"; + $text .= "</table>"; + + 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 = "<dl>\n" . $result . "\n</dl>"; + 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.'} [:][ ] | + <dt> | \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<dt>" . $term . "</dt>"; + } + 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<dd>" . $def . "</dd>\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 = "<pre><code>$codeblock</code></pre>"; + return "\n\n".$this->hashBlock($codeblock)."\n\n"; + } + function _doFencedCodeBlocks_newlines($matches) { + return str_repeat("<br$this->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( + '' => '(?:(?<!\*)\*(?!\*)|(?<![a-zA-Z0-9_])_(?!_))(?=\S)(?![.,:;]\s)', + '*' => '(?<=\S)(?<!\*)\*(?!\*)', + '_' => '(?<=\S)(?<!_)_(?![a-zA-Z0-9_])', + ); + var $strong_relist = array( + '' => '(?:(?<!\*)\*\*(?!\*)|(?<![a-zA-Z0-9_])__(?!_))(?=\S)(?![.,:;]\s)', + '**' => '(?<=\S)(?<!\*)\*\*(?!\*)', + '__' => '(?<=\S)(?<!_)__(?![a-zA-Z0-9_])', + ); + var $em_strong_relist = array( + '' => '(?:(?<!\*)\*\*\*(?!\*)|(?<![a-zA-Z0-9_])___(?!_))(?=\S)(?![.,:;]\s)', + '***' => '(?<=\S)(?<!\*)\*\*\*(?!\*)', + '___' => '(?<=\S)(?<!_)___(?![a-zA-Z0-9_])', + ); + + + function formParagraphs($text) { + # + # Params: + # $text - string to process with html <p> 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 <p> 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 = "<p>$value</p>"; + } + $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 .= "<div class=\"footnotes\">\n"; + $text .= "<hr". MARKDOWN_EMPTY_ELEMENT_SUFFIX ."\n"; + $text .= "<ol>\n\n"; + + $attr = " rev=\"footnote\""; + if ($this->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 = "<a href=\"#fnref:$note_id\"$attr>↩</a>"; + if (preg_match('{</p>$}', $footnote)) { + $footnote = substr($footnote, 0, -4) . " $backlink</p>"; + } else { + $footnote .= "\n\n<p>$backlink</p>"; + } + + $text .= "<li id=\"fn:$note_id\">\n"; + $text .= $footnote . "\n"; + $text .= "</li>\n\n"; + } + + $text .= "</ol>\n"; + $text .= "</div>"; + } + 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 + "<sup id=\"fnref:$node_id\">". + "<a href=\"#fn:$node_id\"$attr>$num</a>". + "</sup>"; + } + + 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 <abbr> elements. + # + if ($this->abbr_word_re) { + // cannot use the /x modifier because abbr_word_re may + // contain significant spaces: + $text = preg_replace_callback('{'. + '(?<![\w\x1A])'. + '(?:'.$this->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>$abbr</abbr>"); + } else { + $desc = $this->encodeAttribute($desc); + return $this->hashPart("<abbr title=\"$desc\">$abbr</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 <div> and <table> as well). + +For more information about Markdown's syntax, see: + +<http://daringfireball.net/projects/markdown/> + + +Bugs +---- + +To file bug reports please send email to: + +<michel.fortin@michelf.com> + +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 +<http://www.michelf.com/> +All rights reserved. + +Based on Markdown +Copyright (c) 2003-2006 John Gruber +<http://daringfireball.net/> +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 diff --git a/system/views/kohana/error.php b/system/views/kohana/error.php new file mode 100644 index 00000000..b40c0f8a --- /dev/null +++ b/system/views/kohana/error.php @@ -0,0 +1,252 @@ +<?php defined('SYSPATH') OR die('No direct access allowed.'); +// Unique error identifier +$error_id = uniqid('error'); +?> +<style type="text/css"> + + #kohana_error { + background: #CFF292; + font-size: 1em; + font-family: sans-serif; + text-align: left; + color: #111; + } + + #kohana_error h1, #kohana_error h2 { + margin: 0; + padding: 1em; + font-size: 1em; + font-weight: normal; + background: #CFF292; + color: #000000; + } + + #kohana_error h1 a, #kohana_error h2 a { + color: #000; + } + + #kohana_error h2 { + background: #CFF292; + border-top: 1px dotted; + } + + #kohana_error h3 { + margin: 0; + padding: 0.4em 0 0; + font-size: 1em; + font-weight: normal; + } + + #kohana_error p { + margin: 0; + padding: 0.2em 0; + } + + #kohana_error a { + color: #1b323b; + } + + #kohana_error pre { + overflow: auto; + white-space: pre-wrap; + } + + #kohana_error table { + width: 100%; + display: block; + margin: 0 0 0.4em; + padding: 0; + border-collapse: collapse; + background: #fff; + } + + #kohana_error table td { + border: solid 1px #ddd; + text-align: left; + vertical-align: top; + padding: 0.4em; + } + + #kohana_error div.content { + padding: 0.4em 1em 1em; + overflow: hidden; + border-top: 1px dotted; + } + + #kohana_error pre.source { + margin: 0 0 1em; + padding: 0.4em; + background: #fff; + border: dotted 1px #b7c680; + line-height: 1.2em; + } + + #kohana_error pre.source span.line { + display: block; + } + + #kohana_error pre.source span.highlight { + background: #f0eb96; + } + + #kohana_error pre.source span.line span.number { + color: #666; + } + + #kohana_error ol.trace { + display: block; + margin: 0 0 0 2em; + padding: 0; + list-style: decimal; + } + + #kohana_error ol.trace li { + margin: 0; + padding: 0; + } +</style> +<script type="text/javascript"> + document.write('<style type="text/css"> .collapsed { display: none; } </style>'); + function koggle(elem) + { + elem = document.getElementById(elem); + + if (elem.style && elem.style['display']) + // Only works with the "style" attr + var disp = elem.style['display']; + else + if (elem.currentStyle) + // For MSIE, naturally + var disp = elem.currentStyle['display']; + else + if (window.getComputedStyle) + // For most other browsers + var disp = document.defaultView.getComputedStyle(elem, null).getPropertyValue('display'); + + // Toggle the state of the "display" style + elem.style.display = disp == 'block' ? 'none' : 'block'; + return false; + } +</script> +<div id="kohana_error"> + <h1> + <span class="type"> +<?php echo $type?> [ <?php echo $code?> ]: + </span> + <span class="message"> +<?php echo $message?> + </span> + </h1> + <div id="<?php echo $error_id ?>" class="content"> + <p> + <span class="file"> +<?php echo Kohana_Exception::debug_path($file)?>[ <?php echo $line?> ] + </span> + </p> + +<?php if (Kohana_Exception::$source_output AND $source_code = Kohana_Exception::debug_source($file, $line)) : ?> + <pre class="source"><code><?php foreach ($source_code as $num => $row) : ?><span class="line <?php if ($num == $line) echo 'highlight' ?>"><span class="number"><?php echo $num ?></span><?php echo htmlspecialchars($row, ENT_NOQUOTES, Kohana::CHARSET) ?></span><?php endforeach ?></code></pre> +<?php endif ?> + +<?php if (Kohana_Exception::$trace_output) : ?> + <ol class="trace"> + <?php foreach (Kohana_Exception::trace($trace) as $i=>$step): ?> + <li> + <p> + <span class="file"> + <?php if ($step['file']): $source_id = $error_id.'source'.$i; ?> + <?php if (Kohana_Exception::$source_output AND $step['source']) : ?> + <a href="#<?php echo $source_id ?>" onclick="return koggle('<?php echo $source_id ?>')"><?php echo Kohana_Exception::debug_path($step['file'])?>[ <?php echo $step['line']?> ]</a> + <?php else : ?> + <span class="file"><?php echo Kohana_Exception::debug_path($step['file'])?>[ <?php echo $step['line']?> ]</span> + <?php endif ?> + <?php else : ?> + {<?php echo __('PHP internal call')?>} + <?php endif?> + </span> + » + <?php echo $step['function']?>(<?php if ($step['args']): $args_id = $error_id.'args'.$i; ?><a href="#<?php echo $args_id ?>" onclick="return koggle('<?php echo $args_id ?>')"><?php echo __('arguments')?></a> +<?php endif?>) + </p> + <?php if (isset($args_id)): ?> + <div id="<?php echo $args_id ?>" class="collapsed"> + <table cellspacing="0"> + <?php foreach ($step['args'] as $name=>$arg): ?> + <tr> + <td> + <code> +<?php echo $name?> + </code> + </td> + <td> + <pre><?php echo Kohana_Exception::dump($arg) ?></pre> + </td> + </tr> + <?php endforeach?> + </table> + </div> + <?php endif?> + <?php if (Kohana_Exception::$source_output AND $step['source'] AND isset($source_id)): ?> + <pre id="<?php echo $source_id ?>" class="source collapsed"><code><?php foreach ($step['source'] as $num => $row) : ?><span class="line <?php if ($num == $step['line']) echo 'highlight' ?>"><span class="number"><?php echo $num ?></span><?php echo htmlspecialchars($row, ENT_NOQUOTES, Kohana::CHARSET) ?></span><?php endforeach ?></code></pre> + <?php endif?> + </li> + <?php unset($args_id, $source_id); ?> + <?php endforeach?> + </ol> +<?php endif ?> + + </div> + <h2><a href="#<?php echo $env_id = $error_id.'environment' ?>" onclick="return koggle('<?php echo $env_id ?>')"><?php echo __('Environment')?></a></h2> + <div id="<?php echo $env_id ?>" class="content collapsed"> + <?php $included = get_included_files()?> + <h3><a href="#<?php echo $env_id = $error_id.'environment_included' ?>" onclick="return koggle('<?php echo $env_id ?>')"><?php echo __('Included files')?></a>(<?php echo count($included)?>)</h3> + <div id="<?php echo $env_id ?>" class="collapsed"> + <table cellspacing="0"> + <?php foreach ($included as $file): ?> + <tr> + <td> + <code> +<?php echo Kohana_Exception::debug_path($file)?> + </code> + </td> + </tr> + <?php endforeach?> + </table> + </div> + <?php $included = get_loaded_extensions()?> + <h3><a href="#<?php echo $env_id = $error_id.'environment_loaded' ?>" onclick="return koggle('<?php echo $env_id ?>')"><?php echo __('Loaded extensions')?></a>(<?php echo count($included)?>)</h3> + <div id="<?php echo $env_id ?>" class="collapsed"> + <table cellspacing="0"> + <?php foreach ($included as $file): ?> + <tr> + <td> + <code> +<?php echo Kohana_Exception::debug_path($file)?> + </code> + </td> + </tr> + <?php endforeach?> + </table> + </div> + <?php foreach (array('_SESSION', '_GET', '_POST', '_FILES', '_COOKIE', '_SERVER') as $var): ?> + <?php if ( empty($GLOBALS[$var]) OR ! is_array($GLOBALS[$var])) continue ?> + <h3><a href="#<?php echo $env_id = $error_id.'environment'.strtolower($var) ?>" onclick="return koggle('<?php echo $env_id ?>')">$<?php echo $var?></a></h3> + <div id="<?php echo $env_id ?>" class="collapsed"> + <table cellspacing="0"> + <?php foreach ($GLOBALS[$var] as $key=>$value): ?> + <tr> + <td> + <code> +<?php echo $key?> + </code> + </td> + <td> + <pre><?php echo Kohana_Exception::dump($value) ?></pre> + </td> + </tr> + <?php endforeach?> + </table> + </div> + <?php endforeach?> + </div> +</div> diff --git a/system/views/kohana/error_disabled.php b/system/views/kohana/error_disabled.php new file mode 100644 index 00000000..1024eb1e --- /dev/null +++ b/system/views/kohana/error_disabled.php @@ -0,0 +1,19 @@ +<?php defined('SYSPATH') OR die('No direct access allowed.'); ?> + <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> + <title><?php echo htmlspecialchars(__('Unable to Complete Request'), ENT_QUOTES, Kohana::CHARSET) ?></title> + </head> + <body> + <div id="framework_error" style="width:24em;margin:50px auto;"> + <h3 style="text-align:center"><?php echo htmlspecialchars(__('Unable to Complete Request'), ENT_QUOTES, Kohana::CHARSET) ?></h3> + <p style="text-align:center"> +<?php + echo __('You can go to the <a href="%site%">home page</a> or <a href="%uri%">try again</a>.', + array('%site%' => htmlspecialchars(url::site(), ENT_QUOTES, Kohana::CHARSET), '%uri%' => htmlspecialchars(url::site(Router::$current_uri), ENT_QUOTES, Kohana::CHARSET))); +?> + </p> + </div> + </body> +</html> diff --git a/system/views/kohana/template.php b/system/views/kohana/template.php index b090fd88..84ddbff5 100644 --- a/system/views/kohana/template.php +++ b/system/views/kohana/template.php @@ -5,7 +5,7 @@ <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> - <title><?php echo html::specialchars($title) ?></title> + <title><?php echo html::chars(__($title)) ?></title> <style type="text/css"> html { background: #83c018 url(<?php echo url::base(FALSE) ?>kohana.png) 50% 0 no-repeat; } @@ -24,11 +24,11 @@ </head> <body> - <h1><?php echo html::specialchars($title) ?></h1> + <h1><?php echo html::chars(__($title)) ?></h1> <?php echo $content ?> <p class="copyright"> - Rendered in {execution_time} seconds, using {memory_usage} of memory<br /> + <?php echo __('Rendered in {execution_time} seconds, using {memory_usage} of memory')?><br /> Copyright ©2007–2008 Kohana Team </p> diff --git a/system/views/kohana_error_disabled.php b/system/views/kohana_error_disabled.php deleted file mode 100644 index cd911328..00000000 --- a/system/views/kohana_error_disabled.php +++ /dev/null @@ -1,17 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); ?> -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> -<head> -<style type="text/css"> -<?php include Kohana::find_file('views', 'kohana_errors', FALSE, 'css') ?> -</style> -<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> -<title><?php echo $error ?></title> -</head> -<body> -<div id="framework_error" style="width:24em;margin:50px auto;"> -<h3><?php echo html::specialchars($error) ?></h3> -<p style="text-align:center"><?php echo $message ?></p> -</div> -</body> -</html>
\ No newline at end of file diff --git a/system/views/kohana_error_page.php b/system/views/kohana_error_page.php deleted file mode 100644 index 944064cc..00000000 --- a/system/views/kohana_error_page.php +++ /dev/null @@ -1,27 +0,0 @@ -<?php defined('SYSPATH') OR die('No direct access allowed.'); ?> -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> -<head> -<style type="text/css"> -<?php include Kohana::find_file('views', 'kohana_errors', FALSE, 'css') ?> -</style> -<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> -<title><?php echo $error ?></title> -<base href="http://php.net/" /> -</head> -<body> -<div id="framework_error" style="width:42em;margin:20px auto;"> -<h3><?php echo html::specialchars($error) ?></h3> -<p><?php echo html::specialchars($description) ?></p> -<?php if ( ! empty($line) AND ! empty($file)): ?> -<p><?php echo Kohana::lang('core.error_file_line', $file, $line) ?></p> -<?php endif ?> -<p><code class="block"><?php echo $message ?></code></p> -<?php if ( ! empty($trace)): ?> -<h3><?php echo Kohana::lang('core.stack_trace') ?></h3> -<?php echo $trace ?> -<?php endif ?> -<p class="stats"><?php echo Kohana::lang('core.stats_footer') ?></p> -</div> -</body> -</html>
\ No newline at end of file diff --git a/system/views/kohana_errors.css b/system/views/kohana_errors.css deleted file mode 100644 index 1341f57d..00000000 --- a/system/views/kohana_errors.css +++ /dev/null @@ -1,21 +0,0 @@ -div#framework_error { background:#fff; border:solid 1px #ccc; font-family:sans-serif; color:#111; font-size:14px; line-height:130%; } -div#framework_error h3 { color:#fff; font-size:16px; padding:8px 6px; margin:0 0 8px; background:#f15a00; text-align:center; } -div#framework_error a { color:#228; text-decoration:none; } -div#framework_error a:hover { text-decoration:underline; } -div#framework_error strong { color:#900; } -div#framework_error p { margin:0; padding:4px 6px 10px; } -div#framework_error tt, -div#framework_error pre, -div#framework_error code { font-family:monospace; padding:2px 4px; font-size:12px; color:#333; - white-space:pre-wrap; /* CSS 2.1 */ - white-space:-moz-pre-wrap; /* For Mozilla */ - word-wrap:break-word; /* For IE5.5+ */ -} -div#framework_error tt { font-style:italic; } -div#framework_error tt:before { content:">"; color:#aaa; } -div#framework_error code tt:before { content:""; } -div#framework_error pre, -div#framework_error code { background:#eaeee5; border:solid 0 #D6D8D1; border-width:0 1px 1px 0; } -div#framework_error .block { display:block; text-align:left; } -div#framework_error .stats { padding:4px; background: #eee; border-top:solid 1px #ccc; text-align:center; font-size:10px; color:#888; } -div#framework_error .backtrace { margin:0; padding:0 6px; list-style:none; line-height:12px; }
\ No newline at end of file diff --git a/system/views/kohana_profiler.php b/system/views/profiler/profiler.php index da77a669..7b9ae951 100644 --- a/system/views/kohana_profiler.php +++ b/system/views/profiler/profiler.php @@ -33,5 +33,5 @@ foreach ($profiles as $profile) echo $profile->render(); } ?> -<p class="kp-meta">Profiler executed in <?php echo number_format($execution_time, 3) ?>s</p> +<p class="kp-meta"><?php echo __('Profiler executed in :execution_timess', array(':execution_times' => number_format($execution_time, 3))) ?></p> </div>
\ No newline at end of file diff --git a/system/views/kohana_profiler_table.css b/system/views/profiler/table.css index 6e7601c9..41a1c9a3 100644 --- a/system/views/kohana_profiler_table.css +++ b/system/views/profiler/table.css @@ -1,53 +1,53 @@ -#kohana-profiler .kp-table
-{
- font-size: 1.0em;
- color: #4D6171;
- width: 100%;
- border-collapse: collapse;
- border-top: 1px solid #E5EFF8;
- border-right: 1px solid #E5EFF8;
- border-left: 1px solid #E5EFF8;
- margin-bottom: 10px;
-}
-#kohana-profiler .kp-table td
-{
- background-color: #FFFFFF;
- border-bottom: 1px solid #E5EFF8;
- padding: 3px;
- vertical-align: top;
-}
-#kohana-profiler .kp-table .kp-title td
-{
- font-weight: bold;
- background-color: inherit;
-}
-#kohana-profiler .kp-table .kp-altrow td
-{
- background-color: #F7FBFF;
-}
-#kohana-profiler .kp-table .kp-totalrow td
-{
- background-color: #FAFAFA;
- border-top: 1px solid #D2DCE5;
- font-weight: bold;
-}
-#kohana-profiler .kp-table .kp-column
-{
- width: 100px;
- border-left: 1px solid #E5EFF8;
- text-align: center;
-}
-#kohana-profiler .kp-table .kp-data, #kohana-profiler .kp-table .kp-name
-{
- background-color: #FAFAFB;
- vertical-align: top;
-}
-#kohana-profiler .kp-table .kp-name
-{
- width: 200px;
- border-right: 1px solid #E5EFF8;
-}
-#kohana-profiler .kp-table .kp-altrow .kp-data, #kohana-profiler .kp-table .kp-altrow .kp-name
-{
- background-color: #F6F8FB;
+#kohana-profiler .kp-table +{ + font-size: 1.0em; + color: #4D6171; + width: 100%; + border-collapse: collapse; + border-top: 1px solid #E5EFF8; + border-right: 1px solid #E5EFF8; + border-left: 1px solid #E5EFF8; + margin-bottom: 10px; +} +#kohana-profiler .kp-table td +{ + background-color: #FFFFFF; + border-bottom: 1px solid #E5EFF8; + padding: 3px; + vertical-align: top; +} +#kohana-profiler .kp-table .kp-title td +{ + font-weight: bold; + background-color: inherit; +} +#kohana-profiler .kp-table .kp-altrow td +{ + background-color: #F7FBFF; +} +#kohana-profiler .kp-table .kp-totalrow td +{ + background-color: #FAFAFA; + border-top: 1px solid #D2DCE5; + font-weight: bold; +} +#kohana-profiler .kp-table .kp-column +{ + width: 100px; + border-left: 1px solid #E5EFF8; + text-align: center; +} +#kohana-profiler .kp-table .kp-data, #kohana-profiler .kp-table .kp-name +{ + background-color: #FAFAFB; + vertical-align: top; +} +#kohana-profiler .kp-table .kp-name +{ + width: 200px; + border-right: 1px solid #E5EFF8; +} +#kohana-profiler .kp-table .kp-altrow .kp-data, #kohana-profiler .kp-table .kp-altrow .kp-name +{ + background-color: #F6F8FB; }
\ No newline at end of file diff --git a/system/views/kohana_profiler_table.php b/system/views/profiler/table.php index b6b46530..7cdf79dd 100644 --- a/system/views/kohana_profiler_table.php +++ b/system/views/profiler/table.php @@ -13,13 +13,12 @@ $style = empty($row['style']) ? '' : ' style="'.$row['style'].'"'; $class = empty($column['class']) ? '' : ' class="'.$column['class'].'"'; $style = empty($column['style']) ? '' : ' style="'.$column['style'].'"'; $value = $row['data'][$index]; - $value = (is_array($value) OR is_object($value)) ? '<pre>'.html::specialchars(print_r($value, TRUE)).'</pre>' : html::specialchars($value); - echo '<td', $style, $class, '>', $value, '</td>'; + $value = (is_array($value) OR is_object($value)) ? '<pre>'.htmlspecialchars(print_r($value, TRUE), ENT_QUOTES, Kohana::CHARSET).'</pre>' : htmlspecialchars($value, ENT_QUOTES, Kohana::CHARSET); + echo '<td' . $style . $class . '>' . wordwrap($value, 100, '<br />', true) . '</td>'; } ?> </tr> <?php - endforeach; ?> -</table>
\ No newline at end of file +</table> |