summaryrefslogtreecommitdiff
path: root/system
diff options
context:
space:
mode:
authorTristan Zur <tzur@webserver.ccwn.org>2015-06-10 20:55:53 +0200
committerTristan Zur <tzur@webserver.ccwn.org>2015-06-10 20:55:53 +0200
commit406abd7c4df1ace2cd3e4e17159e8941a2e8c0c4 (patch)
treea324be16021f44f2fd6d55e609f47024e945b1db /system
Initial import
Diffstat (limited to 'system')
-rw-r--r--system/KohanaLicense.html30
-rw-r--r--system/config/cache.php37
-rw-r--r--system/config/cookie.php55
-rw-r--r--system/config/credit_cards.php70
-rw-r--r--system/config/database.php53
-rw-r--r--system/config/encryption.php39
-rw-r--r--system/config/http.php30
-rw-r--r--system/config/image.php22
-rw-r--r--system/config/inflector.php70
-rw-r--r--system/config/locale.php26
-rw-r--r--system/config/log.php35
-rw-r--r--system/config/mimes.php228
-rw-r--r--system/config/profiler.php16
-rw-r--r--system/config/routes.php58
-rw-r--r--system/config/session.php56
-rw-r--r--system/config/sql_types.php99
-rw-r--r--system/config/upload.php24
-rw-r--r--system/config/user_agents.php120
-rw-r--r--system/config/view.php25
-rw-r--r--system/controllers/template.php54
-rw-r--r--system/core/Benchmark.php126
-rw-r--r--system/core/Event.php231
-rw-r--r--system/core/Kohana.php1118
-rw-r--r--system/core/Kohana_Config.php329
-rw-r--r--system/core/Kohana_Exception.php622
-rw-r--r--system/helpers/arr.php273
-rw-r--r--system/helpers/cookie.php149
-rw-r--r--system/helpers/date.php395
-rw-r--r--system/helpers/db.php47
-rw-r--r--system/helpers/download.php135
-rw-r--r--system/helpers/expires.php129
-rw-r--r--system/helpers/feed.php120
-rw-r--r--system/helpers/file.php184
-rw-r--r--system/helpers/form.php466
-rw-r--r--system/helpers/format.php112
-rw-r--r--system/helpers/html.php364
-rw-r--r--system/helpers/inflector.php252
-rw-r--r--system/helpers/num.php24
-rw-r--r--system/helpers/remote.php64
-rw-r--r--system/helpers/request.php618
-rw-r--r--system/helpers/security.php35
-rw-r--r--system/helpers/text.php596
-rw-r--r--system/helpers/upload.php157
-rw-r--r--system/helpers/url.php264
-rw-r--r--system/helpers/utf8.php744
-rw-r--r--system/helpers/valid.php453
-rw-r--r--system/libraries/Cache.php248
-rw-r--r--system/libraries/Cache_Exception.php11
-rw-r--r--system/libraries/Controller.php44
-rw-r--r--system/libraries/Database.php648
-rw-r--r--system/libraries/Database_Builder.php1241
-rw-r--r--system/libraries/Database_Cache_Result.php81
-rw-r--r--system/libraries/Database_Exception.php15
-rw-r--r--system/libraries/Database_Expression.php23
-rw-r--r--system/libraries/Database_Mysql.php226
-rw-r--r--system/libraries/Database_Mysql_Result.php174
-rw-r--r--system/libraries/Database_Mysqli.php90
-rw-r--r--system/libraries/Database_Mysqli_Result.php175
-rw-r--r--system/libraries/Database_Query.php95
-rw-r--r--system/libraries/Database_Result.php170
-rw-r--r--system/libraries/Encrypt.php176
-rw-r--r--system/libraries/I18n.php100
-rw-r--r--system/libraries/Image.php501
-rw-r--r--system/libraries/Input.php507
-rw-r--r--system/libraries/Kohana_404_Exception.php56
-rw-r--r--system/libraries/Kohana_Log.php90
-rw-r--r--system/libraries/Kohana_PHP_Exception.php99
-rw-r--r--system/libraries/Kohana_User_Exception.php30
-rw-r--r--system/libraries/Model.php62
-rw-r--r--system/libraries/ORM.php1528
-rw-r--r--system/libraries/ORM_Iterator.php266
-rw-r--r--system/libraries/ORM_Validation_Exception.php24
-rw-r--r--system/libraries/Profiler.php306
-rw-r--r--system/libraries/Profiler_Table.php67
-rw-r--r--system/libraries/Router.php315
-rw-r--r--system/libraries/Session.php500
-rw-r--r--system/libraries/URI.php279
-rw-r--r--system/libraries/Validation.php815
-rw-r--r--system/libraries/View.php329
-rw-r--r--system/libraries/drivers/Cache.php42
-rw-r--r--system/libraries/drivers/Cache/File.php255
-rw-r--r--system/libraries/drivers/Cache/Memcache.php132
-rw-r--r--system/libraries/drivers/Cache/Xcache.php161
-rw-r--r--system/libraries/drivers/Config.php257
-rw-r--r--system/libraries/drivers/Config/Array.php83
-rw-r--r--system/libraries/drivers/Image.php158
-rw-r--r--system/libraries/drivers/Image/GD.php440
-rw-r--r--system/libraries/drivers/Image/GraphicsMagick.php225
-rw-r--r--system/libraries/drivers/Image/ImageMagick.php233
-rw-r--r--system/libraries/drivers/Log.php22
-rw-r--r--system/libraries/drivers/Log/Database.php40
-rw-r--r--system/libraries/drivers/Log/File.php44
-rw-r--r--system/libraries/drivers/Log/Syslog.php34
-rw-r--r--system/libraries/drivers/Session.php70
-rw-r--r--system/libraries/drivers/Session/Cache.php108
-rw-r--r--system/libraries/drivers/Session/Cookie.php83
-rw-r--r--system/libraries/drivers/Session/Database.php178
-rw-r--r--system/messages/kohana/core.php37
-rw-r--r--system/messages/validation/default.php25
-rw-r--r--system/views/kohana/error.php252
-rw-r--r--system/views/kohana/error_disabled.php19
-rw-r--r--system/views/kohana/template.php36
-rw-r--r--system/views/profiler/profiler.php37
-rw-r--r--system/views/profiler/table.css53
-rw-r--r--system/views/profiler/table.php24
105 files changed, 21493 insertions, 0 deletions
diff --git a/system/KohanaLicense.html b/system/KohanaLicense.html
new file mode 100644
index 0000000..bc4bce2
--- /dev/null
+++ b/system/KohanaLicense.html
@@ -0,0 +1,30 @@
+<!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>Kohana License</title>
+
+</head>
+<body>
+
+<h2>Kohana License Agreement</h2>
+
+<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-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>
+
+<ul>
+<li>Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.</li>
+<li>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.</li>
+<li>Neither the name of the Kohana nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.</li>
+</ul>
+
+<p>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.</p>
+
+<p><small>NOTE: This license is modeled after the BSD software license.</small></p>
+
+</body>
+</html> \ No newline at end of file
diff --git a/system/config/cache.php b/system/config/cache.php
new file mode 100644
index 0000000..6b2f787
--- /dev/null
+++ b/system/config/cache.php
@@ -0,0 +1,37 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Cache settings, defined as arrays, or "groups". If no group name is
+ * used when loading the cache library, the group named "default" will be used.
+ *
+ * Each group can be used independently, and multiple groups can be used at once.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+
+/**
+ * Group Options:
+ *
+ * - driver - Cache backend driver. Kohana comes with file, database, and memcache drivers.
+ * - File cache is fast and reliable, but requires many filesystem lookups.
+ * - Database cache can be used to cache items remotely, but is slower.
+ * - Memcache is very high performance, but prevents cache tags from being used.
+ *
+ * - params - Driver parameters, specific to each driver.
+ *
+ * - lifetime - Default lifetime of caches in seconds. By default caches are stored for
+ * thirty minutes. Specific lifetime can also be set when creating a new cache.
+ * Setting this to 0 will never automatically delete caches.
+ *
+ * - prefix - Adds a prefix to all keys and tags. This can have a severe performance impact.
+ *
+ */
+$config['default'] = array
+(
+ 'driver' => 'file',
+ 'params' => array('directory' => APPPATH.'cache', 'gc_probability' => 1000),
+ 'lifetime' => 1800,
+ 'prefix' => NULL
+);
diff --git a/system/config/cookie.php b/system/config/cookie.php
new file mode 100644
index 0000000..5340f05
--- /dev/null
+++ b/system/config/cookie.php
@@ -0,0 +1,55 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Cookie config settings. These are the default settings used by the [cookie]
+ * helper. You can override these settings by passing parameters to the cookie
+ * helper functions.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+
+/**
+ * Domain, to restrict the cookie to a specific website domain. For security,
+ * you are encouraged to set this option. An empty setting allows the cookie
+ * to be read by any website domain.
+ * @default ''
+ */
+$config['domain'] = '';
+
+/**
+ * Restrict cookies to a specific path, typically the installation directory.
+ * @default '/'
+ */
+$config['path'] = '/';
+
+/**
+ * Lifetime of the cookie. A setting of 0 makes the cookie active until the
+ * users browser is closed or the cookie is deleted.
+ * @default = 0
+ */
+$config['expire'] = 0;
+
+/**
+ * Enable this option to only allow the cookie to be read when using the a
+ * secure protocol.
+ * @default FALSE
+ */
+$config['secure'] = FALSE;
+
+/**
+ * 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.
+ * @default FALSE
+ */
+$config['httponly'] = FALSE;
+
+/**
+ * Cookie salt for signed cookies.
+ * Make sure you change this to a unique value.
+ *
+ * [!!] Changing this value will invalidate all existing cookies!
+ * @default 'K0hAN4 15 Th3 B357'
+ */
+$config['salt'] = 'K0hAN4 15 Th3 B357'; \ No newline at end of file
diff --git a/system/config/credit_cards.php b/system/config/credit_cards.php
new file mode 100644
index 0000000..46c6afb
--- /dev/null
+++ b/system/config/credit_cards.php
@@ -0,0 +1,70 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Credit card validation configuration.
+ *
+ * Options for each credit card:
+ *
+ * - length - All the allowed card number lengths, in a comma separated string
+ * - prefix - The digits the card needs to start with, in regex format
+ * - luhn - Enable or disable card number validation by the Luhn algorithm
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+
+/**
+ * Default credit card configuration
+ */
+$config = array
+(
+ 'default' => array
+ (
+ 'length' => '13,14,15,16,17,18,19',
+ 'prefix' => '',
+ 'luhn' => TRUE
+ ),
+ 'american express' => array
+ (
+ 'length' => '15',
+ 'prefix' => '3[47]',
+ 'luhn' => TRUE
+ ),
+ 'diners club' => array
+ (
+ 'length' => '14,16',
+ 'prefix' => '36|55|30[0-5]',
+ 'luhn' => TRUE
+ ),
+ 'discover' => array
+ (
+ 'length' => '16',
+ 'prefix' => '6(?:5|011)',
+ 'luhn' => TRUE,
+ ),
+ 'jcb' => array
+ (
+ 'length' => '15,16',
+ 'prefix' => '3|1800|2131',
+ 'luhn' => TRUE
+ ),
+ 'maestro' => array
+ (
+ 'length' => '16,18',
+ 'prefix' => '50(?:20|38)|6(?:304|759)',
+ 'luhn' => TRUE
+ ),
+ 'mastercard' => array
+ (
+ 'length' => '16',
+ 'prefix' => '5[1-5]',
+ 'luhn' => TRUE
+ ),
+ 'visa' => array
+ (
+ 'length' => '13,16',
+ 'prefix' => '4',
+ 'luhn' => TRUE
+ ),
+); \ No newline at end of file
diff --git a/system/config/database.php b/system/config/database.php
new file mode 100644
index 0000000..dd6bf46
--- /dev/null
+++ b/system/config/database.php
@@ -0,0 +1,53 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Database connection settings, defined as arrays, or "groups". If no group
+ * name is used when loading the database library, the group named "default"
+ * will be used.
+ *
+ * Each group can be connected to independently, and multiple groups can be
+ * connected at once. For more information about connecting to a database group
+ * see the [Database] library.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+
+/**
+ * Group Options:
+ *
+ * - benchmark - Enable or disable database benchmarking
+ * - persistent - Enable or disable a persistent connection
+ * - connection - Array of connection specific parameters; alternatively,
+ * you can use a DSN though it is not as fast and certain
+ * characters could create problems (like an '@' character
+ * in a password):
+ * 'connection' => 'mysql://dbuser:secret@localhost/kohana'
+ * - character_set - Database character set
+ * - table_prefix - Database table prefix
+ * - object - Enable or disable object results
+ * - cache - Enable or disable query caching
+ * - escape - Enable automatic query builder escaping
+ */
+$config['default'] = array
+(
+ 'benchmark' => FALSE,
+ 'persistent' => FALSE,
+ 'connection' => array
+ (
+ 'type' => 'mysql',
+ 'user' => 'dbuser',
+ 'pass' => 'p@ssw0rd',
+ 'host' => 'localhost',
+ 'port' => FALSE,
+ 'socket' => FALSE,
+ 'database' => 'kohana',
+ 'params' => NULL,
+ ),
+ 'character_set' => 'utf8',
+ 'table_prefix' => '',
+ 'object' => TRUE,
+ 'cache' => FALSE,
+ 'escape' => TRUE
+); \ No newline at end of file
diff --git a/system/config/encryption.php b/system/config/encryption.php
new file mode 100644
index 0000000..12ff7ae
--- /dev/null
+++ b/system/config/encryption.php
@@ -0,0 +1,39 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Encrypt configuration is defined in groups which allows you to easily switch
+ * between different encryption settings for different uses.
+ *
+ * [!!] All groups inherit and overwrite the default group.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+
+/**
+ * Group Options:
+ *
+ * For best security, your encryption key should be at least 16 characters
+ * long and contain letters, numbers, and symbols.
+ *
+ * - key - Encryption key used to do encryption and decryption. The default option
+ * should never be used for a production website.
+ *
+ * [!!] Do not use a hash as your key. This significantly lowers encryption entropy.
+ *
+ * - mode - MCrypt encryption mode. By default, MCRYPT_MODE_NOFB is used. This mode
+ * offers initialization vector support, is suited to short strings, and
+ * produces the shortest encrypted output.
+ *
+ * - cipher - MCrypt encryption cipher. By default, the MCRYPT_RIJNDAEL_128 cipher is used.
+ * This is also known as 128-bit AES.
+ *
+ * For more information about mcrypt modes and cipers see the [mcrypt php docs](http://php.net/mcrypt).
+ */
+$config['default'] = array
+(
+ 'key' => 'K0H@NA+PHP_7hE-SW!FtFraM3w0R|<',
+ 'mode' => MCRYPT_MODE_NOFB,
+ 'cipher' => MCRYPT_RIJNDAEL_128
+);
diff --git a/system/config/http.php b/system/config/http.php
new file mode 100644
index 0000000..7cc11dd
--- /dev/null
+++ b/system/config/http.php
@@ -0,0 +1,30 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * HTTP Config
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+
+/**
+ * HTTP-EQUIV type meta tags
+ *
+ */
+$config['meta_equiv'] = array
+(
+ 'cache-control',
+ 'content-type', 'content-script-type', 'content-style-type',
+ 'content-disposition',
+ 'content-language',
+ 'default-style',
+ 'expires',
+ 'ext-cache',
+ 'pics-label',
+ 'pragma',
+ 'refresh',
+ 'set-cookie',
+ 'vary',
+ 'window-target',
+); \ No newline at end of file
diff --git a/system/config/image.php b/system/config/image.php
new file mode 100644
index 0000000..976315c
--- /dev/null
+++ b/system/config/image.php
@@ -0,0 +1,22 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Image library config
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+
+/**
+ * Image driver
+ *
+ * @default: 'GD'
+ */
+$config['driver'] = 'GD';
+
+/**
+ * Driver parameters:
+ * ImageMagick - set the "directory" parameter to your ImageMagick installation directory
+ */
+$config['params'] = array(); \ No newline at end of file
diff --git a/system/config/inflector.php b/system/config/inflector.php
new file mode 100644
index 0000000..70744f5
--- /dev/null
+++ b/system/config/inflector.php
@@ -0,0 +1,70 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Inflector Config. Lists of words that are uncountable or irregular.
+ * If you would like to add a word to these lists please open a new issue on the
+ * [issue tracker](http://dev.kohanaphp.com/projects/kohana2/issues)
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+
+$config['uncountable'] = array
+(
+ 'access',
+ 'advice',
+ 'art',
+ 'baggage',
+ 'dances',
+ 'data',
+ 'equipment',
+ 'fish',
+ 'fuel',
+ 'furniture',
+ 'food',
+ 'heat',
+ 'honey',
+ 'homework',
+ 'impatience',
+ 'information',
+ 'knowledge',
+ 'luggage',
+ 'metadata',
+ 'money',
+ 'music',
+ 'news',
+ 'patience',
+ 'progress',
+ 'pollution',
+ 'research',
+ 'rice',
+ 'sand',
+ 'series',
+ 'sheep',
+ 'sms',
+ 'species',
+ 'staff',
+ 'toothpaste',
+ 'traffic',
+ 'understanding',
+ 'water',
+ 'weather',
+ 'work',
+);
+
+$config['irregular'] = array
+(
+ 'child' => 'children',
+ 'clothes' => 'clothing',
+ 'man' => 'men',
+ 'movie' => 'movies',
+ 'person' => 'people',
+ 'woman' => 'women',
+ 'mouse' => 'mice',
+ 'goose' => 'geese',
+ 'ox' => 'oxen',
+ 'leaf' => 'leaves',
+ 'course' => 'courses',
+ 'size' => 'sizes',
+);
diff --git a/system/config/locale.php b/system/config/locale.php
new file mode 100644
index 0000000..3f73b8f
--- /dev/null
+++ b/system/config/locale.php
@@ -0,0 +1,26 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Set your default language and timezone here. For more information about
+ * i18n support see the [i18n] library.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+
+/**
+ * Default language locale name(s).
+ * First item must be a valid i18n directory name, subsequent items are alternative locales
+ * for OS's that don't support the first (e.g. Windows). The first valid locale in the array will be used.
+ * @see http://php.net/setlocale
+ */
+$config['language'] = array('en_US', 'English_United States');
+
+/**
+ * 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'] = 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 0000000..3b43777
--- /dev/null
+++ b/system/config/log.php
@@ -0,0 +1,35 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Log Config
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+
+/**
+ * Different log levels
+ */
+$config['log_levels'] = array
+(
+ 'error' => 1,
+ 'alert' => 2,
+ 'info' => 3,
+ 'debug' => 4,
+);
+
+/**
+ * See different log levels above
+ */
+$config['log_threshold'] = 1;
+
+/**
+ * Log Date format
+ */
+$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
new file mode 100644
index 0000000..f8f8aaf
--- /dev/null
+++ b/system/config/mimes.php
@@ -0,0 +1,228 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * A list of mime types. Our list is generally more complete and accurate than
+ * the operating system MIME list.
+ *
+ * If there are any missing options, please create a ticket on our issue tracker,
+ * 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.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+
+$config = array
+(
+ '323' => array('text/h323'),
+ '7z' => array('application/x-7z-compressed'),
+ 'abw' => array('application/x-abiword'),
+ 'acx' => array('application/internet-property-stream'),
+ 'ai' => array('application/postscript'),
+ 'aif' => array('audio/x-aiff'),
+ 'aifc' => array('audio/x-aiff'),
+ 'aiff' => array('audio/x-aiff'),
+ 'asf' => array('video/x-ms-asf'),
+ 'asr' => array('video/x-ms-asf'),
+ 'asx' => array('video/x-ms-asf'),
+ 'atom' => array('application/atom+xml'),
+ 'avi' => array('video/avi', 'video/msvideo', 'video/x-msvideo'),
+ 'bin' => array('application/octet-stream','application/macbinary'),
+ 'bmp' => array('image/bmp'),
+ 'c' => array('text/x-csrc'),
+ 'c++' => array('text/x-c++src'),
+ 'cab' => array('application/x-cab'),
+ 'cc' => array('text/x-c++src'),
+ 'cda' => array('application/x-cdf'),
+ 'class' => array('application/octet-stream'),
+ 'cpp' => array('text/x-c++src'),
+ 'cpt' => array('application/mac-compactpro'),
+ 'csh' => array('text/x-csh'),
+ 'css' => array('text/css'),
+ 'csv' => array('text/x-comma-separated-values', 'application/vnd.ms-excel', 'text/comma-separated-values', 'text/csv'),
+ 'dbk' => array('application/docbook+xml'),
+ 'dcr' => array('application/x-director'),
+ 'deb' => array('application/x-debian-package'),
+ 'diff' => array('text/x-diff'),
+ 'dir' => array('application/x-director'),
+ 'divx' => array('video/divx'),
+ 'dll' => array('application/octet-stream', 'application/x-msdos-program'),
+ 'dmg' => array('application/x-apple-diskimage'),
+ 'dms' => array('application/octet-stream'),
+ 'doc' => array('application/msword'),
+ 'dvi' => array('application/x-dvi'),
+ 'dxr' => array('application/x-director'),
+ 'eml' => array('message/rfc822'),
+ 'eps' => array('application/postscript'),
+ 'evy' => array('application/envoy'),
+ 'exe' => array('application/x-msdos-program', 'application/octet-stream'),
+ 'fla' => array('application/octet-stream'),
+ 'flac' => array('application/x-flac'),
+ 'flc' => array('video/flc'),
+ 'fli' => array('video/fli'),
+ 'flv' => array('video/x-flv'),
+ 'gif' => array('image/gif'),
+ 'gtar' => array('application/x-gtar'),
+ 'gz' => array('application/x-gzip'),
+ 'h' => array('text/x-chdr'),
+ 'h++' => array('text/x-c++hdr'),
+ 'hh' => array('text/x-c++hdr'),
+ 'hpp' => array('text/x-c++hdr'),
+ 'hqx' => array('application/mac-binhex40'),
+ 'hs' => array('text/x-haskell'),
+ 'htm' => array('text/html'),
+ 'html' => array('text/html'),
+ 'ico' => array('image/x-icon'),
+ 'ics' => array('text/calendar'),
+ 'iii' => array('application/x-iphone'),
+ 'ins' => array('application/x-internet-signup'),
+ 'iso' => array('application/x-iso9660-image'),
+ 'isp' => array('application/x-internet-signup'),
+ 'jar' => array('application/java-archive'),
+ 'java' => array('application/x-java-applet'),
+ 'jpe' => array('image/jpeg', 'image/pjpeg'),
+ 'jpeg' => array('image/jpeg', 'image/pjpeg'),
+ 'jpg' => array('image/jpeg', 'image/pjpeg'),
+ 'js' => array('application/x-javascript'),
+ 'json' => array('application/json'),
+ 'latex' => array('application/x-latex'),
+ 'lha' => array('application/octet-stream'),
+ 'log' => array('text/plain', 'text/x-log'),
+ 'lzh' => array('application/octet-stream'),
+ 'm4a' => array('audio/mpeg'),
+ 'm4p' => array('video/mp4v-es'),
+ 'm4v' => array('video/mp4'),
+ 'man' => array('application/x-troff-man'),
+ 'mdb' => array('application/x-msaccess'),
+ 'midi' => array('audio/midi'),
+ 'mid' => array('audio/midi'),
+ 'mif' => array('application/vnd.mif'),
+ 'mka' => array('audio/x-matroska'),
+ 'mkv' => array('video/x-matroska'),
+ 'mov' => array('video/quicktime'),
+ 'movie' => array('video/x-sgi-movie'),
+ 'mp2' => array('audio/mpeg'),
+ 'mp3' => array('audio/mpeg'),
+ 'mp4' => array('application/mp4','audio/mp4','video/mp4'),
+ 'mpa' => array('video/mpeg'),
+ 'mpe' => array('video/mpeg'),
+ 'mpeg' => array('video/mpeg'),
+ 'mpg' => array('video/mpeg'),
+ 'mpg4' => array('video/mp4'),
+ 'mpga' => array('audio/mpeg'),
+ 'mpp' => array('application/vnd.ms-project'),
+ 'mpv' => array('video/x-matroska'),
+ 'mpv2' => array('video/mpeg'),
+ 'ms' => array('application/x-troff-ms'),
+ 'msg' => array('application/msoutlook','application/x-msg'),
+ 'msi' => array('application/x-msi'),
+ 'nws' => array('message/rfc822'),
+ 'oda' => array('application/oda'),
+ 'odb' => array('application/vnd.oasis.opendocument.database'),
+ 'odc' => array('application/vnd.oasis.opendocument.chart'),
+ 'odf' => array('application/vnd.oasis.opendocument.forumla'),
+ 'odg' => array('application/vnd.oasis.opendocument.graphics'),
+ 'odi' => array('application/vnd.oasis.opendocument.image'),
+ 'odm' => array('application/vnd.oasis.opendocument.text-master'),
+ 'odp' => array('application/vnd.oasis.opendocument.presentation'),
+ 'ods' => array('application/vnd.oasis.opendocument.spreadsheet'),
+ 'odt' => array('application/vnd.oasis.opendocument.text'),
+ 'oga' => array('audio/ogg'),
+ 'ogg' => array('application/ogg'),
+ 'ogv' => array('video/ogg'),
+ 'otg' => array('application/vnd.oasis.opendocument.graphics-template'),
+ 'oth' => array('application/vnd.oasis.opendocument.web'),
+ 'otp' => array('application/vnd.oasis.opendocument.presentation-template'),
+ 'ots' => array('application/vnd.oasis.opendocument.spreadsheet-template'),
+ 'ott' => array('application/vnd.oasis.opendocument.template'),
+ 'p' => array('text/x-pascal'),
+ 'pas' => array('text/x-pascal'),
+ 'patch' => array('text/x-diff'),
+ 'pbm' => array('image/x-portable-bitmap'),
+ 'pdf' => array('application/pdf', 'application/x-download'),
+ 'php' => array('application/x-httpd-php'),
+ 'php3' => array('application/x-httpd-php'),
+ 'php4' => array('application/x-httpd-php'),
+ 'php5' => array('application/x-httpd-php'),
+ 'phps' => array('application/x-httpd-php-source'),
+ 'phtml' => array('application/x-httpd-php'),
+ 'pl' => array('text/x-perl'),
+ 'pm' => array('text/x-perl'),
+ 'png' => array('image/png', 'image/x-png'),
+ 'po' => array('text/x-gettext-translation'),
+ 'pot' => array('application/vnd.ms-powerpoint'),
+ 'pps' => array('application/vnd.ms-powerpoint'),
+ 'ppt' => array('application/powerpoint'),
+ 'ps' => array('application/postscript'),
+ 'psd' => array('application/x-photoshop', 'image/x-photoshop'),
+ 'pub' => array('application/x-mspublisher'),
+ 'py' => array('text/x-python'),
+ 'qt' => array('video/quicktime'),
+ 'ra' => array('audio/x-realaudio'),
+ 'ram' => array('audio/x-realaudio', 'audio/x-pn-realaudio'),
+ 'rar' => array('application/rar'),
+ 'rgb' => array('image/x-rgb'),
+ 'rm' => array('audio/x-pn-realaudio'),
+ 'rpm' => array('audio/x-pn-realaudio-plugin', 'application/x-redhat-package-manager'),
+ 'rss' => array('application/rss+xml'),
+ 'rtf' => array('text/rtf'),
+ 'rtx' => array('text/richtext'),
+ 'rv' => array('video/vnd.rn-realvideo'),
+ 'sea' => array('application/octet-stream'),
+ 'sh' => array('text/x-sh'),
+ 'shtml' => array('text/html'),
+ 'sit' => array('application/x-stuffit'),
+ 'smi' => array('application/smil'),
+ 'smil' => array('application/smil'),
+ 'so' => array('application/octet-stream'),
+ 'src' => array('application/x-wais-source'),
+ 'svg' => array('image/svg+xml'),
+ 'swf' => array('application/x-shockwave-flash'),
+ 't' => array('application/x-troff'),
+ 'tar' => array('application/x-tar'),
+ 'tcl' => array('text/x-tcl'),
+ 'tex' => array('application/x-tex'),
+ 'text' => array('text/plain'),
+ 'texti' => array('application/x-texinfo'),
+ 'textinfo' => array('application/x-texinfo'),
+ 'tgz' => array('application/x-tar'),
+ 'tif' => array('image/tiff'),
+ 'tiff' => array('image/tiff'),
+ 'torrent' => array('application/x-bittorrent'),
+ 'tr' => array('application/x-troff'),
+ 'tsv' => array('text/tab-separated-values'),
+ 'txt' => array('text/plain'),
+ 'wav' => array('audio/x-wav'),
+ 'wax' => array('audio/x-ms-wax'),
+ 'wbxml' => array('application/wbxml'),
+ 'wm' => array('video/x-ms-wm'),
+ 'wma' => array('audio/x-ms-wma'),
+ 'wmd' => array('application/x-ms-wmd'),
+ 'wmlc' => array('application/wmlc'),
+ 'wmv' => array('video/x-ms-wmv', 'application/octet-stream'),
+ 'wmx' => array('video/x-ms-wmx'),
+ 'wmz' => array('application/x-ms-wmz'),
+ 'word' => array('application/msword', 'application/octet-stream'),
+ 'wp5' => array('application/wordperfect5.1'),
+ 'wpd' => array('application/vnd.wordperfect'),
+ 'wvx' => array('video/x-ms-wvx'),
+ 'xbm' => array('image/x-xbitmap'),
+ 'xcf' => array('image/xcf'),
+ 'xhtml' => array('application/xhtml+xml'),
+ 'xht' => array('application/xhtml+xml'),
+ 'xl' => array('application/excel', 'application/vnd.ms-excel'),
+ 'xla' => array('application/excel', 'application/vnd.ms-excel'),
+ 'xlc' => array('application/excel', 'application/vnd.ms-excel'),
+ 'xlm' => array('application/excel', 'application/vnd.ms-excel'),
+ 'xls' => array('application/excel', 'application/vnd.ms-excel'),
+ 'xlt' => array('application/excel', 'application/vnd.ms-excel'),
+ 'xml' => array('text/xml'),
+ 'xof' => array('x-world/x-vrml'),
+ 'xpm' => array('image/x-xpixmap'),
+ 'xsl' => array('text/xml'),
+ 'xvid' => array('video/x-xvid'),
+ 'xwd' => array('image/x-xwindowdump'),
+ 'z' => array('application/x-compress'),
+ 'zip' => array('application/x-zip', 'application/zip', 'application/x-zip-compressed')
+);
diff --git a/system/config/profiler.php b/system/config/profiler.php
new file mode 100644
index 0000000..d30a1b4
--- /dev/null
+++ b/system/config/profiler.php
@@ -0,0 +1,16 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Array of section names to display in the Profiler, TRUE to display all of them.
+ * Built in sections are benchmarks, database, session, post and cookies, custom sections can be used too.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+
+$config['show'] = TRUE;
+
+$config['time_decimals'] = 3;
+
+$config['memory_decimals'] = 2; \ No newline at end of file
diff --git a/system/config/routes.php b/system/config/routes.php
new file mode 100644
index 0000000..fff890c
--- /dev/null
+++ b/system/config/routes.php
@@ -0,0 +1,58 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * ##### Custom Routes
+ * Before changing this file you should copy it to your application/config directory.
+ *
+ * [!!] Routes will run in the order they are defined. Higher routes will always take precedence over lower ones.
+ *
+ * __Default Route__
+ *
+ * $config['_default'] = 'welcome';
+ *
+ * $config['_default'] specifies the default route. It is used to indicate which controller
+ * should be used when a URI contains no segments. For example, if your web application is at
+ * www.example.com and you visit this address with a web browser, the welcome controller would
+ * be used even though it wasn't specified in the URI. The result would be the same as if the
+ * browser had gone to www.example.com/welcome.
+ *
+ * __Custom Routes__
+ *
+ * In addition to the default route above, you can also specify your own routes. The basic
+ * format for a routing rule is:
+ *
+ * $config['route'] = 'class/method';
+ *
+ * Where *route* is the URI you want to route, and *class/method* would replace it.
+ *
+ * For example, if your Kohana web application was installed at www.example.com and
+ * you had the following routing rule: `$config['test'] = 'foo/bar';`
+ * Browsing to www.example.com/test would be *internally* redirected to www.example.com/foo/bar.
+ *
+ * __Advanced Routes with Regex__
+ *
+ * The route part of a routing rule is actually a regular expression. If you are unfamiliar
+ * with regular expressions you can read more about them at the PHP website. Using regular expressions,
+ * you can be more selective about which URIs will match your routing rules, and you can make use of the
+ * sub-pattern back referencing technique to re-use parts of the URI in it's replacement.
+ *
+ * This is best described with an example. Suppose we wanted to make the URL www.example.com/article/22
+ * work, we might use a routing rule like this:
+ *
+ * $config['article/([0-9]+)'] = 'news/show/$1';
+ *
+ * which would match URIs starting with “article/” followed by some numeric digits. If the URI takes this
+ * form, we will use the news controller and call it's show() method passing in the article number as the
+ * first argument. In the www.example.com/article/22 example, it is as if the URL www.example.com/news/show/22
+ * had been visited.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+
+
+/**
+ * Sets the default route to "welcome"
+ */
+$config['_default'] = 'welcome';
diff --git a/system/config/session.php b/system/config/session.php
new file mode 100644
index 0000000..a758305
--- /dev/null
+++ b/system/config/session.php
@@ -0,0 +1,56 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Session Config
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+
+/**
+ *
+ * Session driver name.
+ */
+$config['driver'] = 'cookie';
+
+/**
+ * Session storage parameter, used by drivers.
+ */
+$config['storage'] = '';
+
+/**
+ * Session name.
+ * It must contain only alphanumeric characters and underscores. At least one letter must be present.
+ */
+$config['name'] = 'kohanasession';
+
+/**
+ * Session parameters to validate: user_agent, ip_address, expiration.
+ */
+$config['validate'] = array('user_agent');
+
+/**
+ * Enable or disable session encryption.
+ * Note: this has no effect on the native session driver.
+ */
+$config['encryption'] = FALSE;
+
+/**
+ * Session lifetime. Number of seconds that each session will last.
+ * A value of 0 will keep the session active until the browser is closed (with a limit of 24h).
+ */
+$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'] = 0;
+
+/**
+ * Percentage probability that the gc (garbage collection) routine is started.
+ */
+$config['gc_probability'] = 2; \ No newline at end of file
diff --git a/system/config/sql_types.php b/system/config/sql_types.php
new file mode 100644
index 0000000..6a50329
--- /dev/null
+++ b/system/config/sql_types.php
@@ -0,0 +1,99 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * SQL data types. If there are missing values, please report them
+ * at the [issue tracker](http://dev.kohanaphp.com/projects/kohana2/issues)
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+
+/**
+ * Database sql types
+ */
+$config = array
+(
+ // 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'),
+
+ // 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'),
+
+ // SQL:2003
+ 'bigint' => array('type' => 'int', 'min' => -9223372036854775808, 'max' => 9223372036854775807),
+
+ // SQL:2008
+ 'binary' => array('type' => 'string', 'binary' => TRUE, 'exact' => TRUE),
+ 'binary varying' => array('type' => 'string', 'binary' => TRUE),
+
+ // 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'),
+);
+
+// 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'];
+
+// 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'];
+
+// SQL:2008
+$config['varbinary'] = $config['binary varying'];
+
+// 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/upload.php b/system/config/upload.php
new file mode 100644
index 0000000..833082c
--- /dev/null
+++ b/system/config/upload.php
@@ -0,0 +1,24 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Upload config
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+
+/**
+ * This path is relative to your index file. Absolute paths are also supported.
+ */
+$config['directory'] = DOCROOT.'upload';
+
+/**
+ * Enable or disable directory creation.
+ */
+$config['create_directories'] = FALSE;
+
+/**
+ * Remove spaces from uploaded filenames.
+ */
+$config['remove_spaces'] = TRUE; \ No newline at end of file
diff --git a/system/config/user_agents.php b/system/config/user_agents.php
new file mode 100644
index 0000000..2559e85
--- /dev/null
+++ b/system/config/user_agents.php
@@ -0,0 +1,120 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * This file contains four arrays of user agent data. It is used by the
+ * User Agent library to help identify browser, platform, robot, and
+ * mobile device data. The array keys are used to identify the device
+ * and the array values are used to set the actual name of the item.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+
+$config['platform'] = array
+(
+ 'windows nt 6.0' => 'Windows Vista',
+ 'windows nt 5.2' => 'Windows 2003',
+ 'windows nt 5.0' => 'Windows 2000',
+ 'windows nt 5.1' => 'Windows XP',
+ 'windows nt 4.0' => 'Windows NT',
+ 'winnt4.0' => 'Windows NT',
+ 'winnt 4.0' => 'Windows NT',
+ 'winnt' => 'Windows NT',
+ 'windows 98' => 'Windows 98',
+ 'win98' => 'Windows 98',
+ 'windows 95' => 'Windows 95',
+ 'win95' => 'Windows 95',
+ 'windows' => 'Unknown Windows OS',
+ 'os x' => 'Mac OS X',
+ 'intel mac' => 'Intel Mac',
+ 'ppc mac' => 'PowerPC Mac',
+ 'powerpc' => 'PowerPC',
+ 'ppc' => 'PowerPC',
+ 'cygwin' => 'Cygwin',
+ 'linux' => 'Linux',
+ 'debian' => 'Debian',
+ 'openvms' => 'OpenVMS',
+ 'sunos' => 'Sun Solaris',
+ 'amiga' => 'Amiga',
+ 'beos' => 'BeOS',
+ 'apachebench' => 'ApacheBench',
+ 'freebsd' => 'FreeBSD',
+ 'netbsd' => 'NetBSD',
+ 'bsdi' => 'BSDi',
+ 'openbsd' => 'OpenBSD',
+ 'os/2' => 'OS/2',
+ 'warp' => 'OS/2',
+ 'aix' => 'AIX',
+ 'irix' => 'Irix',
+ 'osf' => 'DEC OSF',
+ 'hp-ux' => 'HP-UX',
+ 'hurd' => 'GNU/Hurd',
+ 'unix' => 'Unknown Unix OS',
+);
+
+/**
+ * The order of this array should NOT be changed. Many browsers return
+ * multiple browser types so we want to identify the sub-type first.
+ */
+$config['browser'] = array
+(
+ 'Opera' => 'Opera',
+ 'MSIE' => 'Internet Explorer',
+ 'Internet Explorer' => 'Internet Explorer',
+ 'Shiira' => 'Shiira',
+ 'Firefox' => 'Firefox',
+ 'Chimera' => 'Chimera',
+ 'Phoenix' => 'Phoenix',
+ 'Firebird' => 'Firebird',
+ 'Camino' => 'Camino',
+ 'Netscape' => 'Netscape',
+ 'OmniWeb' => 'OmniWeb',
+ 'Chrome' => 'Chrome',
+ 'Safari' => 'Safari',
+ 'Konqueror' => 'Konqueror',
+ 'Epiphany' => 'Epiphany',
+ 'Galeon' => 'Galeon',
+ 'Mozilla' => 'Mozilla',
+ 'icab' => 'iCab',
+ 'lynx' => 'Lynx',
+ 'links' => 'Links',
+ 'hotjava' => 'HotJava',
+ 'amaya' => 'Amaya',
+ 'IBrowse' => 'IBrowse',
+);
+
+$config['mobile'] = array
+(
+ 'mobileexplorer' => 'Mobile Explorer',
+ 'openwave' => 'Open Wave',
+ 'opera mini' => 'Opera Mini',
+ 'operamini' => 'Opera Mini',
+ 'elaine' => 'Palm',
+ 'palmsource' => 'Palm',
+ 'digital paths' => 'Palm',
+ 'avantgo' => 'Avantgo',
+ 'xiino' => 'Xiino',
+ 'palmscape' => 'Palmscape',
+ 'nokia' => 'Nokia',
+ 'ericsson' => 'Ericsson',
+ 'blackBerry' => 'BlackBerry',
+ 'motorola' => 'Motorola',
+ 'iphone' => 'iPhone',
+ 'android' => 'Android',
+);
+
+/**
+ * There are hundreds of bots but these are the most common.
+ */
+$config['robot'] = array
+(
+ 'googlebot' => 'Googlebot',
+ 'msnbot' => 'MSNBot',
+ 'slurp' => 'Inktomi Slurp',
+ 'yahoo' => 'Yahoo',
+ 'askjeeves' => 'AskJeeves',
+ 'fastcrawler' => 'FastCrawler',
+ 'infoseek' => 'InfoSeek Robot 1.0',
+ 'lycos' => 'Lycos',
+); \ No newline at end of file
diff --git a/system/config/view.php b/system/config/view.php
new file mode 100644
index 0000000..b6a25b1
--- /dev/null
+++ b/system/config/view.php
@@ -0,0 +1,25 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * View Config
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+
+/**
+ * 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
+(
+ 'gif',
+ 'jpg', 'jpeg',
+ 'png',
+ 'tif', 'tiff',
+ 'swf',
+ 'htm', 'html',
+ 'css',
+ 'js'
+);
diff --git a/system/controllers/template.php b/system/controllers/template.php
new file mode 100644
index 0000000..a9b9934
--- /dev/null
+++ b/system/controllers/template.php
@@ -0,0 +1,54 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Allows a template to be automatically loaded and displayed. Display can be
+ * dynamically turned off in the controller methods, and the template file
+ * can be overloaded.
+ *
+ * To use it, declare your controller to extend this class:
+ * `class Your_Controller extends Template_Controller`
+ *
+ * $Id: template.php 4729 2009-12-29 20:35:19Z isaiah $
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+abstract class Template_Controller extends Controller {
+
+ // Template view name
+ public $template = 'template';
+
+ // Default to do auto-rendering
+ public $auto_render = TRUE;
+
+ /**
+ * Template loading and setup routine.
+ */
+ public function __construct()
+ {
+ parent::__construct();
+
+ // Load the template
+ $this->template = new View($this->template);
+
+ if ($this->auto_render == TRUE)
+ {
+ // Render the template immediately after the controller method
+ Event::add('system.post_controller', array($this, '_render'));
+ }
+ }
+
+ /**
+ * Render the loaded template.
+ */
+ public function _render()
+ {
+ if ($this->auto_render == TRUE)
+ {
+ // Render the template when the class is destroyed
+ $this->template->render(TRUE);
+ }
+ }
+
+} // End Template_Controller \ No newline at end of file
diff --git a/system/core/Benchmark.php b/system/core/Benchmark.php
new file mode 100644
index 0000000..79fadb6
--- /dev/null
+++ b/system/core/Benchmark.php
@@ -0,0 +1,126 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Simple benchmarking.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+final class Benchmark {
+
+ // Benchmark timestamps
+ private static $marks;
+
+ /**
+ * Set a benchmark start point.
+ *
+ * @param string benchmark name
+ * @return void
+ */
+ 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();
+ }
+
+ $mark = array
+ (
+ 'start' => microtime(TRUE),
+ 'stop' => FALSE,
+ 'memory_start' => self::memory_usage(),
+ 'memory_stop' => FALSE
+ );
+
+ array_unshift(self::$marks[$name], $mark);
+ }
+
+ /**
+ * Set a benchmark stop point.
+ *
+ * @param string benchmark name
+ * @return void
+ */
+ public static function stop($name)
+ {
+ if (isset(self::$marks[$name]) AND self::$marks[$name][0]['stop'] === FALSE)
+ {
+ self::$marks[$name][0]['stop'] = microtime(TRUE);
+ self::$marks[$name][0]['memory_stop'] = self::memory_usage();
+ }
+ }
+
+ /**
+ * Get the elapsed time between a start and stop.
+ *
+ * @param string benchmark name, TRUE for all
+ * @param integer number of decimal places to count to
+ * @return array
+ */
+ public static function get($name, $decimals = 4)
+ {
+ if ($name === TRUE)
+ {
+ $times = array();
+ $names = array_keys(self::$marks);
+
+ foreach ($names as $name)
+ {
+ // Get each mark recursively
+ $times[$name] = self::get($name, $decimals);
+ }
+
+ // Return the array
+ return $times;
+ }
+
+ if ( ! isset(self::$marks[$name]))
+ return FALSE;
+
+ if (self::$marks[$name][0]['stop'] === FALSE)
+ {
+ // Stop the benchmark to prevent mis-matched results
+ self::stop($name);
+ }
+
+ // Return a string version of the time between the start and stop points
+ // Properly reading a float requires using number_format or sprintf
+ $time = $memory = 0;
+ for ($i = 0; $i < count(self::$marks[$name]); $i++)
+ {
+ $time += self::$marks[$name][$i]['stop'] - self::$marks[$name][$i]['start'];
+ $memory += self::$marks[$name][$i]['memory_stop'] - self::$marks[$name][$i]['memory_start'];
+ }
+
+ return array
+ (
+ 'time' => number_format($time, $decimals),
+ 'memory' => $memory,
+ 'count' => count(self::$marks[$name])
+ );
+ }
+
+ /**
+ * Returns the current memory usage. This is only possible if the
+ * memory_get_usage function is supported in PHP.
+ *
+ * @return integer
+ */
+ private static function memory_usage()
+ {
+ static $func;
+
+ if ($func === NULL)
+ {
+ // Test if memory usage can be seen
+ $func = function_exists('memory_get_usage');
+ }
+
+ return $func ? memory_get_usage() : 0;
+ }
+
+} // End Benchmark
diff --git a/system/core/Event.php b/system/core/Event.php
new file mode 100644
index 0000000..ab839f4
--- /dev/null
+++ b/system/core/Event.php
@@ -0,0 +1,231 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Process queuing/execution class. Allows an unlimited number of callbacks
+ * 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.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ * @link http://docs.kohanaphp.com/general/events
+ */
+abstract class Event_Core {
+
+ // Event callbacks
+ protected static $events = array();
+
+ // Cache of events that have been run
+ protected static $has_run = array();
+
+ // Data that can be processed during events
+ public static $data;
+
+ /**
+ * Add a callback to an event queue.
+ *
+ * @param string event name
+ * @param array http://php.net/callback
+ * @param boolean prevent duplicates
+ * @return boolean
+ */
+ public static function add($name, $callback, $unique = FALSE)
+ {
+ if ( ! isset(Event::$events[$name]))
+ {
+ // Create an empty event if it is not yet defined
+ Event::$events[$name] = array();
+ }
+ elseif ($unique AND in_array($callback, Event::$events[$name], TRUE))
+ {
+ // The event already exists
+ return FALSE;
+ }
+
+ // Add the event
+ Event::$events[$name][] = $callback;
+
+ return TRUE;
+ }
+
+ /**
+ * Add a callback to an event queue, before a given event.
+ *
+ * @param string event name
+ * @param array existing event callback
+ * @param array event callback
+ * @return boolean
+ */
+ public static function add_before($name, $existing, $callback)
+ {
+ if (empty(Event::$events[$name]) OR ($key = array_search($existing, Event::$events[$name])) === FALSE)
+ {
+ // Just add the event if there are no events
+ return Event::add($name, $callback);
+ }
+ else
+ {
+ // Insert the event immediately before the existing event
+ return Event::insert_event($name, $key, $callback);
+ }
+ }
+
+ /**
+ * Add a callback to an event queue, after a given event.
+ *
+ * @param string event name
+ * @param array existing event callback
+ * @param array event callback
+ * @return boolean
+ */
+ public static function add_after($name, $existing, $callback)
+ {
+ if (empty(Event::$events[$name]) OR ($key = array_search($existing, Event::$events[$name])) === FALSE)
+ {
+ // Just add the event if there are no events
+ return Event::add($name, $callback);
+ }
+ else
+ {
+ // Insert the event immediately after the existing event
+ return Event::insert_event($name, $key + 1, $callback);
+ }
+ }
+
+ /**
+ * Inserts a new event at a specfic key location.
+ *
+ * @param string event name
+ * @param integer key to insert new event at
+ * @param array event callback
+ * @return void
+ */
+ private static function insert_event($name, $key, $callback)
+ {
+ if (in_array($callback, Event::$events[$name], TRUE))
+ return FALSE;
+
+ // Add the new event at the given key location
+ Event::$events[$name] = array_merge
+ (
+ // Events before the key
+ array_slice(Event::$events[$name], 0, $key),
+ // New event callback
+ array($callback),
+ // Events after the key
+ array_slice(Event::$events[$name], $key)
+ );
+
+ return TRUE;
+ }
+
+ /**
+ * Replaces an event with another event.
+ *
+ * @param string event name
+ * @param array event to replace
+ * @param array new callback
+ * @return boolean
+ */
+ public static function replace($name, $existing, $callback)
+ {
+ if (empty(Event::$events[$name]) OR ($key = array_search($existing, Event::$events[$name], TRUE)) === FALSE)
+ return FALSE;
+
+ if ( ! in_array($callback, Event::$events[$name], TRUE))
+ {
+ // Replace the exisiting event with the new event
+ Event::$events[$name][$key] = $callback;
+ }
+ else
+ {
+ // Remove the existing event from the queue
+ unset(Event::$events[$name][$key]);
+
+ // Reset the array so the keys are ordered properly
+ Event::$events[$name] = array_values(Event::$events[$name]);
+ }
+
+ return TRUE;
+ }
+
+ /**
+ * Get all callbacks for an event.
+ *
+ * @param string event name
+ * @return array
+ */
+ public static function get($name)
+ {
+ return empty(Event::$events[$name]) ? array() : Event::$events[$name];
+ }
+
+ /**
+ * Clear some or all callbacks from an event.
+ *
+ * @param string event name
+ * @param array specific callback to remove, FALSE for all callbacks
+ * @return void
+ */
+ public static function clear($name, $callback = FALSE)
+ {
+ if ($callback === FALSE)
+ {
+ Event::$events[$name] = array();
+ }
+ 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 (Event::$events[$name] as $i => $event_callback)
+ {
+ if ($callback === $event_callback)
+ {
+ unset(Event::$events[$name][$i]);
+ }
+ }
+ }
+ }
+
+ /**
+ * Execute all of the callbacks attached to an event.
+ *
+ * @param string event name
+ * @param array data can be processed as Event::$data by the callbacks
+ * @return void
+ */
+ public static function run($name, & $data = NULL)
+ {
+ if ( ! empty(Event::$events[$name]))
+ {
+ // So callbacks can access Event::$data
+ Event::$data =& $data;
+ $callbacks = Event::get($name);
+
+ foreach ($callbacks as $callback)
+ {
+ call_user_func_array($callback, array(&$data));
+ }
+
+ // Do this to prevent data from getting 'stuck'
+ $clear_data = '';
+ Event::$data =& $clear_data;
+ }
+
+ // The event has been run!
+ Event::$has_run[$name] = $name;
+ }
+
+ /**
+ * Check if a given event has been run.
+ *
+ * @param string event name
+ * @return boolean
+ */
+ public static function 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
new file mode 100644
index 0000000..96d969e
--- /dev/null
+++ b/system/core/Kohana.php
@@ -0,0 +1,1118 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Provides Kohana-specific helper functions. This is where the magic happens!
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+
+// 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
+ protected static $buffer_level;
+
+ // The final output that will displayed by Kohana
+ public static $output = '';
+
+ // The current locale
+ public static $locale;
+
+ // Include paths
+ protected static $include_paths;
+ protected static $include_paths_hash = '';
+
+ // Cache lifetime
+ protected static $cache_lifetime;
+
+ // Internal caches and write status
+ protected static $internal_cache = array();
+ protected static $write_cache;
+ protected static $internal_cache_path;
+ protected static $internal_cache_key;
+ protected static $internal_cache_encrypt;
+
+ // Server API that PHP is using. Allows testing of different APIs.
+ public static $server_api = PHP_SAPI;
+
+ /**
+ * Sets up the PHP environment. Adds error/exception handling, output
+ * buffering, and adds an auto-loading method for loading classes.
+ *
+ * This method is run immediately when this file is loaded, and is
+ * benchmarked as environment_setup.
+ *
+ * For security, this function also destroys the $_REQUEST global variable.
+ * Using the proper global (GET, POST, COOKIE, etc) is inherently more secure.
+ * The recommended way to fetch a global variable is using the Input library.
+ * @see http://www.php.net/globals
+ *
+ * @return void
+ */
+ public static function setup()
+ {
+ static $run;
+
+ // Only run this function once
+ if ($run === TRUE)
+ return;
+
+ $run = TRUE;
+
+ // Start the environment setup benchmark
+ Benchmark::start(SYSTEM_BENCHMARK.'_environment_setup');
+
+ // Define Kohana error constant
+ define('E_KOHANA', 42);
+
+ // Define 404 error constant
+ define('E_PAGE_NOT_FOUND', 43);
+
+ // Define database error constant
+ define('E_DATABASE_ERROR', 44);
+
+ // 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?
+ Kohana::$internal_cache_encrypt = Kohana::config('core.internal_cache_encrypt');
+
+ if(Kohana::$internal_cache_encrypt===TRUE)
+ {
+ Kohana::$internal_cache_key = Kohana::config('core.internal_cache_key');
+
+ // Be sure the key is of acceptable length for the mcrypt algorithm used
+ Kohana::$internal_cache_key = substr(Kohana::$internal_cache_key, 0, 24);
+ }
+
+ // Set the directory to be used for the internal cache
+ if ( ! Kohana::$internal_cache_path = Kohana::config('core.internal_cache_path'))
+ {
+ Kohana::$internal_cache_path = APPPATH.'cache/';
+ }
+
+ // Load cached configuration and language files
+ Kohana::$internal_cache['configuration'] = Kohana::cache('configuration', Kohana::$cache_lifetime);
+ Kohana::$internal_cache['language'] = Kohana::cache('language', Kohana::$cache_lifetime);
+
+ // Load cached file paths
+ Kohana::$internal_cache['find_file_paths'] = Kohana::cache('find_file_paths', Kohana::$cache_lifetime);
+
+ // Enable cache saving
+ Event::add('system.shutdown', array('Kohana', 'internal_cache_save'));
+ }
+
+ // Start output buffering
+ ob_start(array('Kohana', 'output_buffer'));
+
+ // Save buffering level
+ Kohana::$buffer_level = ob_get_level();
+
+ // Set autoloader
+ spl_autoload_register(array('Kohana', 'auto_load'));
+
+ // 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='.Kohana::CHARSET);
+
+ // Load i18n
+ new I18n;
+
+ // Enable exception handling
+ Kohana_Exception::enable();
+
+ // Enable error handling
+ Kohana_PHP_Exception::enable();
+
+ // Load locales
+ $locales = Kohana::config('locale.language');
+
+ // Make first locale the defined Kohana charset
+ $locales[0] .= '.'.Kohana::CHARSET;
+
+ // Set locale information
+ Kohana::$locale = setlocale(LC_ALL, $locales);
+
+ // Default to the default locale when none of the user defined ones where accepted
+ Kohana::$locale = ! Kohana::$locale ? Kohana::LOCALE.'.'.Kohana::CHARSET : Kohana::$locale;
+
+ // Set locale for the I18n system
+ I18n::set_locale(Kohana::$locale);
+
+ // Set and validate the timezone
+ date_default_timezone_set(Kohana::config('locale.timezone'));
+
+ // register_globals is enabled
+ if (ini_get('register_globals'))
+ {
+ if (isset($_REQUEST['GLOBALS']))
+ {
+ // Prevent GLOBALS override attacks
+ exit('Global variable overload attack.');
+ }
+
+ // Destroy the REQUEST global
+ $_REQUEST = array();
+
+ // These globals are standard and should not be removed
+ $preserve = array('GLOBALS', '_REQUEST', '_GET', '_POST', '_FILES', '_COOKIE', '_SERVER', '_ENV', '_SESSION');
+
+ // This loop has the same effect as disabling register_globals
+ foreach (array_diff(array_keys($GLOBALS), $preserve) as $key)
+ {
+ global $$key;
+ $$key = NULL;
+
+ // Unset the global variable
+ unset($GLOBALS[$key], $$key);
+ }
+
+ // Warn the developer about register globals
+ Kohana_Log::add('debug', 'Disable register_globals! It is evil and deprecated: http://php.net/register_globals');
+ }
+
+ // Enable Kohana routing
+ Event::add('system.routing', array('Router', 'find_uri'));
+ Event::add('system.routing', array('Router', 'setup'));
+
+ // Enable Kohana controller initialization
+ Event::add('system.execute', array('Kohana', 'instance'));
+
+ // Enable Kohana 404 pages
+ Event::add('system.404', array('Kohana_404_Exception', 'trigger'));
+
+ if (Kohana::config('core.enable_hooks') === TRUE)
+ {
+ // Find all the hook files
+ $hooks = Kohana::list_files('hooks', TRUE);
+
+ foreach ($hooks as $file)
+ {
+ // Load the hook
+ include $file;
+ }
+ }
+
+ // 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.
+ *
+ * This method is benchmarked as controller_setup and controller_execution.
+ *
+ * @return object instance of controller
+ */
+ public static function & instance()
+ {
+ if (Kohana::$instance === NULL)
+ {
+ Benchmark::start(SYSTEM_BENCHMARK.'_controller_setup');
+
+ // Include the Controller file
+ require_once Router::$controller_path;
+
+ try
+ {
+ // Start validation of the controller
+ $class = new ReflectionClass(ucfirst(Router::$controller).'_Controller');
+ }
+ catch (ReflectionException $e)
+ {
+ // Controller does not exist
+ Event::run('system.404');
+ }
+
+ if ($class->isAbstract() OR (IN_PRODUCTION AND $class->getConstant('ALLOW_PRODUCTION') == FALSE))
+ {
+ // Controller is not allowed to run in production
+ Event::run('system.404');
+ }
+
+ // Run system.pre_controller
+ Event::run('system.pre_controller');
+
+ // Create a new controller instance
+ $controller = $class->newInstance();
+
+ // Controller constructor has been executed
+ Event::run('system.post_controller_constructor');
+
+ try
+ {
+ // Load the controller method
+ $method = $class->getMethod(Router::$method);
+
+ // Method exists
+ if (Router::$method[0] === '_')
+ {
+ // Do not allow access to hidden methods
+ Event::run('system.404');
+ }
+
+ if ($method->isProtected() or $method->isPrivate())
+ {
+ // Do not attempt to invoke protected methods
+ throw new ReflectionException('protected controller method');
+ }
+
+ // Default arguments
+ $arguments = Router::$arguments;
+ }
+ catch (ReflectionException $e)
+ {
+ // Use __call instead
+ $method = $class->getMethod('__call');
+
+ // Use arguments in __call format
+ $arguments = array(Router::$method, Router::$arguments);
+ }
+
+ // Stop the controller setup benchmark
+ Benchmark::stop(SYSTEM_BENCHMARK.'_controller_setup');
+
+ // Start the controller execution benchmark
+ Benchmark::start(SYSTEM_BENCHMARK.'_controller_execution');
+
+ // Execute the controller method
+ $method->invokeArgs($controller, $arguments);
+
+ // Controller method has been executed
+ Event::run('system.post_controller');
+
+ // Stop the controller execution benchmark
+ Benchmark::stop(SYSTEM_BENCHMARK.'_controller_execution');
+ }
+
+ return Kohana::$instance;
+ }
+
+ /**
+ * Get all include paths. APPPATH is the first path, followed by module
+ * paths in the order they are configured, follow by the SYSPATH.
+ *
+ * @param boolean re-process the include paths
+ * @return array
+ */
+ public static function include_paths($process = FALSE)
+ {
+ if ($process === TRUE)
+ {
+ // Add APPPATH as the first path
+ Kohana::$include_paths = array(APPPATH);
+
+ foreach (Kohana::config('core.modules') as $path)
+ {
+ if ($path = str_replace('\\', '/', realpath($path)))
+ {
+ // Add a valid path
+ Kohana::$include_paths[] = $path.'/';
+ }
+ }
+
+ // Add SYSPATH as the last path
+ Kohana::$include_paths[] = SYSPATH;
+
+ Kohana::$include_paths_hash = md5(serialize(Kohana::$include_paths));
+ }
+
+ return Kohana::$include_paths;
+ }
+
+ /**
+ * Get a config item or group proxies Kohana_Config.
+ *
+ * @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 = FALSE)
+ {
+ return Kohana_Config::instance()->get($key,$slash,$required);
+ }
+
+ /**
+ * Load data from a simple cache file. This should only be used internally,
+ * and is NOT a replacement for the Cache library.
+ *
+ * @param string unique name of cache
+ * @param integer expiration in seconds
+ * @return mixed
+ */
+ public static function cache($name, $lifetime)
+ {
+ if ($lifetime > 0)
+ {
+ $path = Kohana::$internal_cache_path.'kohana_'.$name;
+
+ if (is_file($path))
+ {
+ // Check the file modification time
+ if ((time() - filemtime($path)) < $lifetime)
+ {
+ // Cache is valid! Now, do we need to decrypt it?
+ 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, 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);
+
+ // If cache is false (as above) return NULL, otherwise, return the cache
+ return ($cache ? $cache : NULL);
+ }
+ else
+ {
+ return unserialize(file_get_contents($path));
+ }
+ }
+ else
+ {
+ // Cache is invalid, delete it
+ unlink($path);
+ }
+ }
+ }
+
+ // No cache found
+ return NULL;
+ }
+
+ /**
+ * Save data to a simple cache file. This should only be used internally, and
+ * is NOT a replacement for the Cache library.
+ *
+ * @param string cache name
+ * @param mixed data to cache
+ * @param integer expiration in seconds
+ * @return boolean
+ */
+ public static function cache_save($name, $data, $lifetime)
+ {
+ if ($lifetime < 1)
+ return FALSE;
+
+ $path = Kohana::$internal_cache_path.'kohana_'.$name;
+
+ if ($data === NULL)
+ {
+ // Delete cache
+ return (is_file($path) and unlink($path));
+ }
+ else
+ {
+ // Using encryption? Encrypt the data when we write it
+ 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, Kohana::$internal_cache_key, serialize($data), MCRYPT_MODE_ECB, $iv);
+
+ return (bool) file_put_contents($path, $encrypted_text);
+ }
+ else
+ {
+ // Write data to cache file
+ return (bool) file_put_contents($path, serialize($data));
+ }
+ }
+ }
+
+ /**
+ * Kohana output handler. Called during ob_clean, ob_flush, and their variants.
+ *
+ * @param string current output buffer
+ * @return string
+ */
+ public static function output_buffer($output)
+ {
+ // Could be flushing, so send headers first
+ if ( ! Event::has_run('system.send_headers'))
+ {
+ // Run the send_headers event
+ Event::run('system.send_headers');
+ }
+
+ // Set final output
+ Kohana::$output = $output;
+
+ // Set and return the final output
+ return Kohana::$output;
+ }
+
+ /**
+ * 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
+ * @return void
+ */
+ public static function close_buffers($flush = TRUE)
+ {
+ if (ob_get_level() >= Kohana::$buffer_level)
+ {
+ // Set the close function
+ $close = ($flush === TRUE) ? 'ob_end_flush' : 'ob_end_clean';
+
+ while (ob_get_level() > Kohana::$buffer_level)
+ {
+ // Flush or clean the buffer
+ $close();
+ }
+
+ // Store the Kohana output buffer. Apparently there was a change in PHP
+ // 5.4 such that if you call this you wind up with a blank page.
+ // Disabling it for now. See ticket #1839
+ if (version_compare(PHP_VERSION, "5.4", "<")) {
+ ob_end_clean();
+ }
+ }
+ }
+
+ /**
+ * Triggers the shutdown of Kohana by closing the output buffer, runs the system.display event.
+ *
+ * @return void
+ */
+ 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
+ Kohana::close_buffers(TRUE);
+
+ // Run the output event
+ Event::run('system.display', Kohana::$output);
+
+ // Render the final output
+ Kohana::render(Kohana::$output);
+ }
+
+ /**
+ * Inserts global Kohana variables into the generated output and prints it.
+ *
+ * @param string final output that will displayed
+ * @return void
+ */
+ public static function render($output)
+ {
+ if (Kohana::config('core.render_stats') === TRUE)
+ {
+ // Fetch memory usage in MB
+ $memory = function_exists('memory_get_usage') ? (memory_get_usage() / 1024 / 1024) : 0;
+
+ // Fetch benchmark for page execution time
+ $benchmark = Benchmark::get(SYSTEM_BENCHMARK.'_total_execution');
+
+ // Replace the global template variables
+ $output = str_replace(
+ array
+ (
+ '{kohana_version}',
+ '{kohana_codename}',
+ '{execution_time}',
+ '{memory_usage}',
+ '{included_files}',
+ ),
+ array
+ (
+ KOHANA::VERSION,
+ KOHANA::CODENAME,
+ $benchmark['time'],
+ number_format($memory, 2).'MB',
+ count(get_included_files()),
+ ),
+ $output
+ );
+ }
+
+ if ($level = Kohana::config('core.output_compression') AND ini_get('output_handler') !== 'ob_gzhandler' AND (int) ini_get('zlib.output_compression') === 0)
+ {
+ if ($compress = request::preferred_encoding(array('gzip','deflate'), TRUE))
+ {
+ 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 ($compress === 'gzip')
+ {
+ // Compress output using gzip
+ $output = gzencode($output, $level);
+ }
+ elseif ($compress === 'deflate')
+ {
+ // Compress output using zlib (HTTP deflate)
+ $output = gzdeflate($output, $level);
+ }
+
+ // 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(Kohana::$server_api, 'cgi') === FALSE)
+ {
+ header('Content-Length: '.strlen($output));
+ }
+ }
+ }
+
+ echo $output;
+ }
+
+ /**
+ * Provides class auto-loading.
+ *
+ * @throws Kohana_Exception
+ * @param string name of class
+ * @return bool
+ */
+ public static function auto_load($class)
+ {
+ if (class_exists($class, FALSE) OR interface_exists($class, FALSE))
+ return TRUE;
+
+ if (($suffix = strrpos($class, '_')) > 0)
+ {
+ // Find the class suffix
+ $suffix = substr($class, $suffix + 1);
+ }
+ else
+ {
+ // No suffix
+ $suffix = FALSE;
+ }
+
+ if ($suffix === 'Core')
+ {
+ $type = 'libraries';
+ $file = substr($class, 0, -5);
+ }
+ elseif ($suffix === 'Controller')
+ {
+ $type = 'controllers';
+ // Lowercase filename
+ $file = strtolower(substr($class, 0, -11));
+ }
+ elseif ($suffix === 'Model')
+ {
+ $type = 'models';
+ // Lowercase filename
+ $file = strtolower(substr($class, 0, -6));
+ }
+ elseif ($suffix === 'Driver')
+ {
+ $type = 'libraries/drivers';
+ $file = str_replace('_', '/', substr($class, 0, -7));
+ }
+ else
+ {
+ // This could be either a library or a helper, but libraries must
+ // always be capitalized, so we check if the first character is
+ // uppercase. If it is, we are loading a library, not a helper.
+ $type = ($class[0] < 'a') ? 'libraries' : 'helpers';
+ $file = $class;
+ }
+
+ if ($filename = Kohana::find_file($type, $file))
+ {
+ // Load the class
+ require $filename;
+ }
+ else
+ {
+ // The class could not be found
+ return FALSE;
+ }
+
+ if ($filename = Kohana::find_file($type, Kohana::config('core.extension_prefix').$class))
+ {
+ // Load the class extension
+ require $filename;
+ }
+ elseif ($suffix !== 'Core' AND class_exists($class.'_Core', FALSE))
+ {
+ // Class extension to be evaluated
+ $extension = 'class '.$class.' extends '.$class.'_Core { }';
+
+ // Start class analysis
+ $core = new ReflectionClass($class.'_Core');
+
+ if ($core->isAbstract())
+ {
+ // Make the extension abstract
+ $extension = 'abstract '.$extension;
+ }
+
+ // Transparent class extensions are handled using eval. This is
+ // a disgusting hack, but it gets the job done.
+ eval($extension);
+ }
+
+ return TRUE;
+ }
+
+ /**
+ * Find a resource file in a given directory. Files will be located according
+ * to the order of the include paths. Configuration and i18n files will be
+ * returned in reverse order.
+ *
+ * @throws Kohana_Exception if file is required and not found
+ * @param string directory to search in
+ * @param string filename to look for (without extension)
+ * @param boolean file required
+ * @param string file extension
+ * @return array if the type is config, i18n or l10n
+ * @return string if the file is found
+ * @return FALSE if the file is not found
+ */
+ public static function find_file($directory, $filename, $required = FALSE, $ext = FALSE)
+ {
+ // NOTE: This test MUST be not be a strict comparison (===), or empty
+ // extensions will be allowed!
+ if ($ext == '')
+ {
+ // Use the default extension
+ $ext = EXT;
+ }
+ else
+ {
+ // Add a period before the extension
+ $ext = '.'.$ext;
+ }
+
+ // Search path
+ $search = $directory.'/'.$filename.$ext;
+
+ if (isset(Kohana::$internal_cache['find_file_paths'][Kohana::$include_paths_hash][$search]))
+ return Kohana::$internal_cache['find_file_paths'][Kohana::$include_paths_hash][$search];
+
+ // Load include paths
+ $paths = Kohana::$include_paths;
+
+ // Nothing found, yet
+ $found = NULL;
+
+ if ($directory === 'config' OR $directory === 'messages' OR $directory === 'i18n')
+ {
+ // Search in reverse, for merging
+ $paths = array_reverse($paths);
+
+ foreach ($paths as $path)
+ {
+ if (is_file($path.$search))
+ {
+ // A matching file has been found
+ $found[] = $path.$search;
+ }
+ }
+ }
+ else
+ {
+ foreach ($paths as $path)
+ {
+ if (is_file($path.$search))
+ {
+ // A matching file has been found
+ $found = $path.$search;
+
+ // Stop searching
+ break;
+ }
+ }
+ }
+
+ if ($found === NULL)
+ {
+ if ($required === TRUE)
+ {
+ // If the file is required, throw an exception
+ throw new Kohana_Exception('The requested :resource:, :file:, could not be found', array(':resource:' => __($directory), ':file:' =>$filename));
+ }
+ else
+ {
+ // Nothing was found, return FALSE
+ $found = FALSE;
+ }
+ }
+
+ if ( ! isset(Kohana::$write_cache['find_file_paths']))
+ {
+ // Write cache at shutdown
+ Kohana::$write_cache['find_file_paths'] = TRUE;
+ }
+
+ return Kohana::$internal_cache['find_file_paths'][Kohana::$include_paths_hash][$search] = $found;
+ }
+
+ /**
+ * Lists all files and directories in a resource path.
+ *
+ * @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, $ext = EXT, $path = FALSE)
+ {
+ $files = array();
+
+ if ($path === FALSE)
+ {
+ $paths = array_reverse(Kohana::include_paths());
+
+ foreach ($paths as $path)
+ {
+ // Recursively get and merge all files
+ $files = array_merge($files, Kohana::list_files($directory, $recursive, $ext, $path.$directory));
+ }
+ }
+ else
+ {
+ $path = rtrim($path, '/').'/';
+
+ if (is_readable($path) AND $items = glob($path.'*'))
+ {
+ $ext_pos = 0 - strlen($ext);
+
+ foreach ($items as $index => $item)
+ {
+ $item = str_replace('\\', '/', $item);
+
+ if (is_dir($item))
+ {
+ // Handle recursion
+ if ($recursive === TRUE)
+ {
+ // Filename should only be the basename
+ $item = pathinfo($item, PATHINFO_BASENAME);
+
+ // Append sub-directory search
+ $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;
+ }
+ }
+ }
+ }
+ }
+
+ return $files;
+ }
+
+
+ /**
+ * 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 message($key, $args = array())
+ {
+ // Extract the main group from the key
+ $group = explode('.', $key, 2);
+ $group = $group[0];
+
+ if ( ! isset(Kohana::$internal_cache['messages'][$group]))
+ {
+ // Messages for this group
+ $messages = array();
+
+ if ($file = Kohana::find_file('messages', $group))
+ {
+ include $file[0];
+ }
+
+ if ( ! isset(Kohana::$write_cache['messages']))
+ {
+ // Write language cache
+ Kohana::$write_cache['messages'] = TRUE;
+ }
+
+ Kohana::$internal_cache['messages'][$group] = $messages;
+ }
+
+ // Get the line from cache
+ $line = Kohana::key_string(Kohana::$internal_cache['messages'], $key);
+
+ if ($line === NULL)
+ {
+ Kohana_Log::add('error', 'Missing messages entry '.$key.' for message '.$group);
+
+ // Return the key string as fallback
+ return $key;
+ }
+
+ if (is_string($line) AND func_num_args() > 1)
+ {
+ $args = array_slice(func_get_args(), 1);
+
+ // Add the arguments into the line
+ $line = vsprintf($line, is_array($args[0]) ? $args[0] : $args);
+ }
+
+ return $line;
+ }
+
+ /**
+ * Returns the value of a key, defined by a 'dot-noted' string, from an array.
+ *
+ * @param array array to search
+ * @param string dot-noted string: foo.bar.baz
+ * @return string if the key is found
+ * @return void if the key is not found
+ */
+ public static function key_string($array, $keys)
+ {
+ if (empty($array))
+ return NULL;
+
+ // Prepare for loop
+ $keys = explode('.', $keys);
+
+ do
+ {
+ // Get the next key
+ $key = array_shift($keys);
+
+ if (isset($array[$key]))
+ {
+ if (is_array($array[$key]) AND ! empty($keys))
+ {
+ // Dig down to prepare the next loop
+ $array = $array[$key];
+ }
+ else
+ {
+ // Requested key was found
+ return $array[$key];
+ }
+ }
+ else
+ {
+ // Requested key is not set
+ break;
+ }
+ }
+ while ( ! empty($keys));
+
+ return NULL;
+ }
+
+ /**
+ * Sets values in an array by using a 'dot-noted' string.
+ *
+ * @param array array to set keys in (reference)
+ * @param string dot-noted string: foo.bar.baz
+ * @return mixed fill value for the key
+ * @return void
+ */
+ public static function key_string_set( & $array, $keys, $fill = NULL)
+ {
+ if (is_object($array) AND ($array instanceof ArrayObject))
+ {
+ // Copy the array
+ $array_copy = $array->getArrayCopy();
+
+ // Is an object
+ $array_object = TRUE;
+ }
+ else
+ {
+ if ( ! is_array($array))
+ {
+ // Must always be an array
+ $array = (array) $array;
+ }
+
+ // Copy is a reference to the array
+ $array_copy =& $array;
+ }
+
+ if (empty($keys))
+ return $array;
+
+ // Create keys
+ $keys = explode('.', $keys);
+
+ // Create reference to the array
+ $row =& $array_copy;
+
+ for ($i = 0, $end = count($keys) - 1; $i <= $end; $i++)
+ {
+ // Get the current key
+ $key = $keys[$i];
+
+ if ( ! isset($row[$key]))
+ {
+ if (isset($keys[$i + 1]))
+ {
+ // Make the value an array
+ $row[$key] = array();
+ }
+ else
+ {
+ // Add the fill key
+ $row[$key] = $fill;
+ }
+ }
+ elseif (isset($keys[$i + 1]))
+ {
+ // Make the value an array
+ $row[$key] = (array) $row[$key];
+ }
+
+ // Go down a level, creating a new row reference
+ $row =& $row[$key];
+ }
+
+ if (isset($array_object))
+ {
+ // Swap the array back in
+ $array->exchangeArray($array_copy);
+ }
+ }
+
+ /**
+ * Quick debugging of any variable. Any number of parameters can be set.
+ *
+ * @return string
+ */
+ public static function debug()
+ {
+ if (func_num_args() === 0)
+ return;
+
+ // Get params
+ $params = func_get_args();
+ $output = array();
+
+ foreach ($params as $var)
+ {
+ $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);
+ }
+
+ /**
+ * Saves the internal caches: configuration, include paths, etc.
+ *
+ * @return boolean
+ */
+ public static function internal_cache_save()
+ {
+ if ( ! is_array(Kohana::$write_cache))
+ return FALSE;
+
+ // Get internal cache names
+ $caches = array_keys(Kohana::$write_cache);
+
+ // Nothing written
+ $written = FALSE;
+
+ foreach ($caches as $cache)
+ {
+ if (isset(Kohana::$internal_cache[$cache]))
+ {
+ // Write the cache file
+ Kohana::cache_save($cache, Kohana::$internal_cache[$cache], Kohana::config('core.internal_cache'));
+
+ // A cache has been written
+ $written = TRUE;
+ }
+ }
+
+ return $written;
+ }
+
+} // End Kohana
diff --git a/system/core/Kohana_Config.php b/system/core/Kohana_Config.php
new file mode 100644
index 0000000..9abc5b6
--- /dev/null
+++ b/system/core/Kohana_Config.php
@@ -0,0 +1,329 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Provides a driver-based interface for setting and getting
+ * configuration options for the Kohana environment
+ *
+ * @package Kohana
+ * @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 0000000..bc0efd1
--- /dev/null
+++ b/system/core/Kohana_Exception.php
@@ -0,0 +1,622 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Kohana Exceptions
+ *
+ * @package Kohana
+ * @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('kohana/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('kohana/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('kohana/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 (Kohana::$server_api === '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
+ * @param integer maximum levels of recursion
+ * @return string
+ */
+ public static function dump($value, $length = 128, $max_level = 5)
+ {
+ return Kohana_Exception::_dump($value, $length, $max_level);
+ }
+
+ /**
+ * Helper for Kohana_Exception::dump(), handles recursion in arrays and objects.
+ *
+ * @param mixed variable to dump
+ * @param integer maximum length of strings
+ * @param integer maximum levels of recursion
+ * @param integer current recursion level (internal)
+ * @return string
+ */
+ private static function _dump( & $var, $length = 128, $max_level = 5, $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).'&nbsp;&hellip;';
+ }
+ 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 <= $max_level)
+ {
+ $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, $max_level, $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 <= $max_level)
+ {
+ $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, $max_level, $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)
+ {
+ // Normalize directory separator
+ $file = str_replace('\\', '/', $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/helpers/arr.php b/system/helpers/arr.php
new file mode 100644
index 0000000..19026fa
--- /dev/null
+++ b/system/helpers/arr.php
@@ -0,0 +1,273 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Array helper class.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class arr_Core {
+
+ /**
+ * Return a callback array from a string, eg: limit[10,20] would become
+ * array('limit', array('10', '20'))
+ *
+ * @param string callback string
+ * @return array
+ */
+ public static function callback_string($str)
+ {
+ // command[param,param]
+ if (preg_match('/([^\[]*+)\[(.+)\]/', (string) $str, $match))
+ {
+ // command
+ $command = $match[1];
+
+ // param,param
+ $params = preg_split('/(?<!\\\\),/', $match[2]);
+ $params = str_replace('\,', ',', $params);
+ }
+ else
+ {
+ // command
+ $command = $str;
+
+ // No params
+ $params = NULL;
+ }
+
+ return array($command, $params);
+ }
+
+ /**
+ * Rotates a 2D array clockwise.
+ * Example, turns a 2x3 array into a 3x2 array.
+ *
+ * @param array array to rotate
+ * @param boolean keep the keys in the final rotated array. the sub arrays of the source array need to have the same key values.
+ * if your subkeys might not match, you need to pass FALSE here!
+ * @return array
+ */
+ public static function rotate($source_array, $keep_keys = TRUE)
+ {
+ $new_array = array();
+ foreach ($source_array as $key => $value)
+ {
+ $value = ($keep_keys === TRUE) ? $value : array_values($value);
+ foreach ($value as $k => $v)
+ {
+ $new_array[$k][$key] = $v;
+ }
+ }
+
+ return $new_array;
+ }
+
+ /**
+ * Removes a key from an array and returns the value.
+ *
+ * @param string key to return
+ * @param array array to work on
+ * @return mixed value of the requested array key
+ */
+ public static function remove($key, & $array)
+ {
+ if ( ! array_key_exists($key, $array))
+ return NULL;
+
+ $val = $array[$key];
+ unset($array[$key]);
+
+ return $val;
+ }
+
+
+ /**
+ * Extract one or more keys from an array. Each key given after the first
+ * argument (the array) will be extracted. Keys that do not exist in the
+ * search array will be NULL in the extracted data.
+ *
+ * @param array array to search
+ * @param string key name
+ * @return array
+ */
+ public static function extract(array $search, $keys)
+ {
+ // Get the keys, removing the $search array
+ $keys = array_slice(func_get_args(), 1);
+
+ $found = array();
+ foreach ($keys as $key)
+ {
+ $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
+ * @param string key to unshift
+ * @param mixed value to unshift
+ * @return array
+ */
+ public static function unshift_assoc( array & $array, $key, $val)
+ {
+ $array = array_reverse($array, TRUE);
+ $array[$key] = $val;
+ $array = array_reverse($array, TRUE);
+
+ return $array;
+ }
+
+ /**
+ * Because PHP does not have this function, and array_walk_recursive creates
+ * references in arrays and is not truly recursive.
+ *
+ * @param mixed callback to apply to each member of the array
+ * @param array array to map to
+ * @return array
+ */
+ public static function map_recursive($callback, array $array)
+ {
+ foreach ($array as $key => $val)
+ {
+ // Map the callback to the key
+ $array[$key] = is_array($val) ? arr::map_recursive($callback, $val) : call_user_func($callback, $val);
+ }
+
+ return $array;
+ }
+
+ /**
+ * Emulates array_merge_recursive, but appends numeric keys and replaces
+ * associative keys, instead of appending all keys.
+ *
+ * @param array any number of arrays
+ * @return array
+ */
+ public static function merge()
+ {
+ $total = func_num_args();
+
+ $result = array();
+ for ($i = 0; $i < $total; $i++)
+ {
+ foreach (func_get_arg($i) as $key => $val)
+ {
+ if (isset($result[$key]))
+ {
+ if (is_array($val))
+ {
+ // Arrays are merged recursively
+ $result[$key] = arr::merge($result[$key], $val);
+ }
+ elseif (is_int($key))
+ {
+ // Indexed arrays are appended
+ array_push($result, $val);
+ }
+ else
+ {
+ // Associative arrays are replaced
+ $result[$key] = $val;
+ }
+ }
+ else
+ {
+ // New values are added
+ $result[$key] = $val;
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Overwrites an array with values from input array(s).
+ * Non-existing keys will not be appended!
+ *
+ * @param array key array
+ * @param array input array(s) that will overwrite key array values
+ * @return array
+ */
+ public static function overwrite($array1, $array2)
+ {
+ foreach (array_intersect_key($array2, $array1) as $key => $value)
+ {
+ $array1[$key] = $value;
+ }
+
+ if (func_num_args() > 2)
+ {
+ foreach (array_slice(func_get_args(), 2) as $array2)
+ {
+ foreach (array_intersect_key($array2, $array1) as $key => $value)
+ {
+ $array1[$key] = $value;
+ }
+ }
+ }
+
+ return $array1;
+ }
+
+ /**
+ * Recursively convert an array to an object.
+ *
+ * @param array array to convert
+ * @return object
+ */
+ public static function to_object(array $array, $class = 'stdClass')
+ {
+ $object = new $class;
+
+ foreach ($array as $key => $value)
+ {
+ if (is_array($value))
+ {
+ // Convert the array to an object
+ $value = arr::to_object($value, $class);
+ }
+
+ // Add the value to the object
+ $object->{$key} = $value;
+ }
+
+ 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
new file mode 100644
index 0000000..3680ae3
--- /dev/null
+++ b/system/helpers/cookie.php
@@ -0,0 +1,149 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Cookie helper class.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class cookie_Core {
+
+ /**
+ * Sets a cookie with the given parameters.
+ *
+ * @param string cookie name or array of config options
+ * @param string cookie value
+ * @param integer number of seconds before the cookie expires
+ * @param string URL path to allow
+ * @param string URL domain to allow
+ * @param boolean HTTPS only
+ * @param boolean HTTP only (requires PHP 5.2 or higher)
+ * @return boolean
+ */
+ public static function set($name, $value = NULL, $expire = NULL, $path = NULL, $domain = NULL, $secure = NULL, $httponly = NULL)
+ {
+ if (headers_sent())
+ return FALSE;
+
+ // If the name param is an array, we import it
+ is_array($name) and extract($name, EXTR_OVERWRITE);
+
+ // Fetch default options
+ $config = Kohana::config('cookie');
+
+ foreach (array('value', 'expire', 'domain', 'path', 'secure', 'httponly') as $item)
+ {
+ if ($$item === NULL AND isset($config[$item]))
+ {
+ $$item = $config[$item];
+ }
+ }
+
+ 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);
+ }
+
+ /**
+ * Fetch a cookie value, using the Input library.
+ *
+ * @param string cookie name
+ * @param mixed default value
+ * @param boolean use XSS cleaning on the value
+ * @return string
+ */
+ public static function get($name = NULL, $default = NULL, $xss_clean = FALSE)
+ {
+ // 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;
+ }
+
+ /**
+ * Nullify and unset a cookie.
+ *
+ * @param string cookie name
+ * @param string URL path
+ * @param string URL domain
+ * @return boolean
+ */
+ public static function delete($name, $path = NULL, $domain = NULL)
+ {
+ // Delete the cookie from globals
+ unset($_COOKIE[$name]);
+
+ // Sets the cookie value to an empty string, and the expiration to 24 hours ago
+ 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
new file mode 100644
index 0000000..0b29d28
--- /dev/null
+++ b/system/helpers/date.php
@@ -0,0 +1,395 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Date helper class.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class date_Core {
+
+ /**
+ * Converts a UNIX timestamp to DOS format.
+ *
+ * @param integer UNIX timestamp
+ * @return integer
+ */
+ public static function unix2dos($timestamp = FALSE)
+ {
+ $timestamp = ($timestamp === FALSE) ? getdate() : getdate($timestamp);
+
+ if ($timestamp['year'] < 1980)
+ {
+ return (1 << 21 | 1 << 16);
+ }
+
+ $timestamp['year'] -= 1980;
+
+ // What voodoo is this? I have no idea... Geert can explain it though,
+ // and that's good enough for me.
+ return ($timestamp['year'] << 25 | $timestamp['mon'] << 21 |
+ $timestamp['mday'] << 16 | $timestamp['hours'] << 11 |
+ $timestamp['minutes'] << 5 | $timestamp['seconds'] >> 1);
+ }
+
+ /**
+ * Converts a DOS timestamp to UNIX format.
+ *
+ * @param integer DOS timestamp
+ * @return integer
+ */
+ public static function dos2unix($timestamp = FALSE)
+ {
+ $sec = 2 * ($timestamp & 0x1f);
+ $min = ($timestamp >> 5) & 0x3f;
+ $hrs = ($timestamp >> 11) & 0x1f;
+ $day = ($timestamp >> 16) & 0x1f;
+ $mon = ($timestamp >> 21) & 0x0f;
+ $year = ($timestamp >> 25) & 0x7f;
+
+ return mktime($hrs, $min, $sec, $mon, $day, $year + 1980);
+ }
+
+ /**
+ * Returns the offset (in seconds) between two time zones.
+ * @see http://php.net/timezones
+ *
+ * @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, $when = 'now')
+ {
+ if ($local === TRUE)
+ {
+ $local = date_default_timezone_get();
+ }
+
+ // Create timezone objects
+ $remote = new DateTimeZone($remote);
+ $local = new DateTimeZone($local);
+
+ // Create date objects from timezones
+ $time_there = new DateTime($when, $remote);
+ $time_here = new DateTime($when, $local);
+
+ // Find the offset
+ return $remote->getOffset($time_there) - $local->getOffset($time_here);
+ }
+
+ /**
+ * Number of seconds in a minute, incrementing by a step.
+ *
+ * @param integer amount to increment each step by, 1 to 30
+ * @param integer start value
+ * @param integer end value
+ * @return array A mirrored (foo => foo) array from 1-60.
+ */
+ public static function seconds($step = 1, $start = 0, $end = 60)
+ {
+ // Always integer
+ $step = (int) $step;
+
+ $seconds = array();
+
+ for ($i = $start; $i < $end; $i += $step)
+ {
+ $seconds[$i] = ($i < 10) ? '0'.$i : $i;
+ }
+
+ return $seconds;
+ }
+
+ /**
+ * Number of minutes in an hour, incrementing by a step.
+ *
+ * @param integer amount to increment each step by, 1 to 30
+ * @return array A mirrored (foo => foo) array from 1-60.
+ */
+ public static function minutes($step = 5)
+ {
+ // Because there are the same number of minutes as seconds in this set,
+ // we choose to re-use seconds(), rather than creating an entirely new
+ // function. Shhhh, it's cheating! ;) There are several more of these
+ // in the following methods.
+ return date::seconds($step);
+ }
+
+ /**
+ * Number of hours in a day.
+ *
+ * @param integer amount to increment each step by
+ * @param boolean use 24-hour time
+ * @param integer the hour to start at
+ * @return array A mirrored (foo => foo) array from start-12 or start-23.
+ */
+ public static function hours($step = 1, $long = FALSE, $start = NULL)
+ {
+ // Default values
+ $step = (int) $step;
+ $long = (bool) $long;
+ $hours = array();
+
+ // Set the default start if none was specified.
+ if ($start === NULL)
+ {
+ $start = ($long === FALSE) ? 1 : 0;
+ }
+
+ $hours = array();
+
+ // 24-hour time has 24 hours, instead of 12
+ $size = ($long === TRUE) ? 23 : 12;
+
+ for ($i = $start; $i <= $size; $i += $step)
+ {
+ $hours[$i] = $i;
+ }
+
+ return $hours;
+ }
+
+ /**
+ * Returns AM or PM, based on a given hour.
+ *
+ * @param integer number of the hour
+ * @return string
+ */
+ public static function ampm($hour)
+ {
+ // Always integer
+ $hour = (int) $hour;
+
+ return ($hour > 11) ? 'PM' : 'AM';
+ }
+
+ /**
+ * Adjusts a non-24-hour number into a 24-hour number.
+ *
+ * @param integer hour to adjust
+ * @param string AM or PM
+ * @return string
+ */
+ public static function adjust($hour, $ampm)
+ {
+ $hour = (int) $hour;
+ $ampm = strtolower($ampm);
+
+ switch ($ampm)
+ {
+ case 'am':
+ if ($hour == 12)
+ $hour = 0;
+ break;
+ case 'pm':
+ if ($hour < 12)
+ $hour += 12;
+ break;
+ }
+
+ return sprintf('%02s', $hour);
+ }
+
+ /**
+ * Number of days in month.
+ *
+ * @param integer number of month
+ * @param integer number of year to check month, defaults to the current year
+ * @return array A mirrored (foo => foo) array of the days.
+ */
+ public static function days($month, $year = FALSE)
+ {
+ static $months;
+
+ // Always integers
+ $month = (int) $month;
+ $year = (int) $year;
+
+ // Use the current year by default
+ $year = ($year == FALSE) ? date('Y') : $year;
+
+ // We use caching for months, because time functions are used
+ if (empty($months[$year][$month]))
+ {
+ $months[$year][$month] = array();
+
+ // Use date to find the number of days in the given month
+ $total = date('t', mktime(1, 0, 0, $month, 1, $year)) + 1;
+
+ for ($i = 1; $i < $total; $i++)
+ {
+ $months[$year][$month][$i] = $i;
+ }
+ }
+
+ return $months[$year][$month];
+ }
+
+ /**
+ * Number of months in a year
+ *
+ * @return array A mirrored (foo => foo) array from 1-12.
+ */
+ public static function months()
+ {
+ return date::hours();
+ }
+
+ /**
+ * Returns an array of years between a starting and ending year.
+ * Uses the current year +/- 5 as the max/min.
+ *
+ * @param integer starting year
+ * @param integer ending year
+ * @return array
+ */
+ public static function years($start = FALSE, $end = FALSE)
+ {
+ // Default values
+ $start = ($start === FALSE) ? date('Y') - 5 : (int) $start;
+ $end = ($end === FALSE) ? date('Y') + 5 : (int) $end;
+
+ $years = array();
+
+ // Add one, so that "less than" works
+ $end += 1;
+
+ for ($i = $start; $i < $end; $i++)
+ {
+ $years[$i] = $i;
+ }
+
+ return $years;
+ }
+
+ /**
+ * Returns time difference between two timestamps, in human readable format.
+ *
+ * @param integer timestamp
+ * @param integer timestamp, defaults to the current time
+ * @param string formatting string
+ * @return string|array
+ */
+ public static function timespan($time1, $time2 = NULL, $output = 'years,months,weeks,days,hours,minutes,seconds')
+ {
+ // Array with the output formats
+ $output = preg_split('/[^a-z]+/', strtolower((string) $output));
+
+ // Invalid output
+ if (empty($output))
+ return FALSE;
+
+ // Make the output values into keys
+ extract(array_flip($output), EXTR_SKIP);
+
+ // Default values
+ $time1 = max(0, (int) $time1);
+ $time2 = empty($time2) ? time() : max(0, (int) $time2);
+
+ // Calculate timespan (seconds)
+ $timespan = abs($time1 - $time2);
+
+ // All values found using Google Calculator.
+ // Years and months do not match the formula exactly, due to leap years.
+
+ // Years ago, 60 * 60 * 24 * 365
+ isset($years) and $timespan -= 31556926 * ($years = (int) floor($timespan / 31556926));
+
+ // Months ago, 60 * 60 * 24 * 30
+ isset($months) and $timespan -= 2629744 * ($months = (int) floor($timespan / 2629743.83));
+
+ // Weeks ago, 60 * 60 * 24 * 7
+ isset($weeks) and $timespan -= 604800 * ($weeks = (int) floor($timespan / 604800));
+
+ // Days ago, 60 * 60 * 24
+ isset($days) and $timespan -= 86400 * ($days = (int) floor($timespan / 86400));
+
+ // Hours ago, 60 * 60
+ isset($hours) and $timespan -= 3600 * ($hours = (int) floor($timespan / 3600));
+
+ // Minutes ago, 60
+ isset($minutes) and $timespan -= 60 * ($minutes = (int) floor($timespan / 60));
+
+ // Seconds ago, 1
+ isset($seconds) and $seconds = $timespan;
+
+ // Remove the variables that cannot be accessed
+ unset($timespan, $time1, $time2);
+
+ // Deny access to these variables
+ $deny = array_flip(array('deny', 'key', 'difference', 'output'));
+
+ // Return the difference
+ $difference = array();
+ foreach ($output as $key)
+ {
+ if (isset($$key) AND ! isset($deny[$key]))
+ {
+ // Add requested key to the output
+ $difference[$key] = $$key;
+ }
+ }
+
+ // Invalid output formats string
+ if (empty($difference))
+ return FALSE;
+
+ // If only one output format was asked, don't put it in an array
+ if (count($difference) === 1)
+ return current($difference);
+
+ // Return array
+ return $difference;
+ }
+
+ /**
+ * Returns time difference between two timestamps, in the format:
+ * N year, N months, N weeks, N days, N hours, N minutes, and N seconds ago
+ *
+ * @param integer timestamp
+ * @param integer timestamp, defaults to the current time
+ * @param string formatting string
+ * @return string
+ */
+ public static function timespan_string($time1, $time2 = NULL, $output = 'years,months,weeks,days,hours,minutes,seconds')
+ {
+ if ($difference = date::timespan($time1, $time2, $output) AND is_array($difference))
+ {
+ // Determine the key of the last item in the array
+ $last = end($difference);
+ $last = key($difference);
+
+ $span = array();
+ foreach ($difference as $name => $amount)
+ {
+ if ($amount === 0)
+ {
+ // Skip empty amounts
+ continue;
+ }
+
+ // Add the amount to the span
+ $span[] = ($name === $last ? ' and ' : ', ').$amount.' '.($amount === 1 ? inflector::singular($name) : $name);
+ }
+
+ // If the difference is less than 60 seconds, remove the preceding and.
+ if (count($span) === 1)
+ {
+ $span[0] = ltrim($span[0], 'and ');
+ }
+
+ // Replace difference by making the span into a string
+ $difference = trim(implode('', $span), ',');
+ }
+ elseif (is_int($difference))
+ {
+ // Single-value return
+ $difference = $difference.' '.($difference === 1 ? inflector::singular($output) : $output);
+ }
+
+ return $difference;
+ }
+
+} // End date \ No newline at end of file
diff --git a/system/helpers/db.php b/system/helpers/db.php
new file mode 100644
index 0000000..941bb2e
--- /dev/null
+++ b/system/helpers/db.php
@@ -0,0 +1,47 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Database helper class.
+ *
+ * @package Kohana
+ * @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
new file mode 100644
index 0000000..0bdb04d
--- /dev/null
+++ b/system/helpers/download.php
@@ -0,0 +1,135 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Download helper class.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class download_Core {
+
+ /**
+ * 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 string optional data to send
+ * @return void
+ */
+ public static function send($filename, $data = NULL)
+ {
+ if ($data === NULL)
+ {
+ $filepath = realpath($filename);
+
+ $filename = basename($filepath);
+ $filesize = filesize($filepath);
+ }
+ else
+ {
+ $filename = basename($filename);
+ $filesize = strlen($data);
+ }
+
+ // Retrieve MIME type by extension
+ $mime = Kohana::config('mimes.'.strtolower(substr(strrchr($filename, '.'), 1)));
+ $mime = empty($mime) ? 'application/octet-stream' : $mime[0];
+
+ // Close output buffers
+ Kohana::close_buffers(FALSE);
+
+ // Clear any output
+ Event::add('system.display', create_function('', 'Kohana::$output = "";'));
+
+ // Send headers
+ header("Content-Type: $mime");
+ header('Content-Length: '.sprintf('%d', $filesize));
+ header('Content-Transfer-Encoding: binary');
+
+ // Send data
+ if ($data === NULL)
+ {
+ $handle = fopen($filepath, 'rb');
+
+ fpassthru($handle);
+ fclose($handle);
+ }
+ else
+ {
+ echo $data;
+ }
+
+ 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);
+
+ // Prevent caching
+ header('Expires: Thu, 01 Jan 1970 00:00:00 GMT');
+
+ if (request::user_agent('browser') === 'Internet Explorer' AND request::user_agent('version') <= '6.0')
+ {
+ // HTTP 1.0
+ header('Pragma:');
+
+ // HTTP 1.1 with IE extensions
+ header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
+ }
+ else
+ {
+ // HTTP 1.0
+ header('Pragma: no-cache');
+
+ // HTTP 1.1
+ header('Cache-Control: no-cache, max-age=0');
+ }
+
+ if (is_file($filename))
+ {
+ download::send($filename);
+ }
+ else
+ {
+ download::send($filename, $data);
+ }
+ }
+
+} // End download
diff --git a/system/helpers/expires.php b/system/helpers/expires.php
new file mode 100644
index 0000000..a3e01b8
--- /dev/null
+++ b/system/helpers/expires.php
@@ -0,0 +1,129 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Controls headers that effect client caching of pages
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class expires_Core {
+
+ /**
+ * Sets the amount of time before content expires
+ *
+ * @param integer Seconds before the content expires
+ * @param integer Last modified timestamp in seconds(optional)
+ * @return integer Timestamp when the content expires
+ */
+ public static function set($seconds = 60, $last_modified=null)
+ {
+ $now = time();
+ $expires = $now + $seconds;
+ if (empty($last_modified))
+ {
+ $last_modified = $now;
+ }
+
+ 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));
+
+ // HTTP 1.1
+ header('Cache-Control: public,max-age='.$seconds);
+
+ return $expires;
+ }
+
+ /**
+ * Parses the If-Modified-Since header
+ *
+ * @return integer|boolean Timestamp or FALSE when header is lacking or malformed
+ */
+ public static function get()
+ {
+ if ( ! empty($_SERVER['HTTP_IF_MODIFIED_SINCE']))
+ {
+ // Some versions of IE6 append "; length=####"
+ if (($strpos = strpos($_SERVER['HTTP_IF_MODIFIED_SINCE'], ';')) !== FALSE)
+ {
+ $mod_time = substr($_SERVER['HTTP_IF_MODIFIED_SINCE'], 0, $strpos);
+ }
+ else
+ {
+ $mod_time = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
+ }
+
+ return strtotime($mod_time);
+ }
+
+ return FALSE;
+ }
+
+ /**
+ * 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
+ * @param integer Last modified timestamp in seconds(optional)
+ * @return integer|boolean Timestamp of the If-Modified-Since header or FALSE when header is lacking or malformed
+ */
+ public static function check($seconds = 60, $modified=null)
+ {
+ if ($last_modified = expires::get())
+ {
+ $now = time();
+
+ if (empty($last_modified))
+ {
+ $last_modified = $now;
+ }
+
+ if ($modified <= $last_modified)
+ {
+ // 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));
+
+ $expires = $now + $seconds;
+ // HTTP 1.0
+ header('Expires: '.gmdate('D, d M Y H:i:s T', $expires));
+
+ // HTTP 1.1
+ header('Cache-Control: public,max-age='.$seconds);
+
+ // Clear any output
+ Event::add('system.display', create_function('', 'Kohana::$output = "";'));
+
+ exit;
+ }
+ }
+
+ return $last_modified;
+ }
+
+ /**
+ * Check if expiration headers are already set
+ *
+ * @return boolean
+ */
+ public static function headers_set()
+ {
+ foreach (headers_list() as $header)
+ {
+ if (strncasecmp($header, 'Expires:', 8) === 0
+ OR strncasecmp($header, 'Cache-Control:', 14) === 0
+ OR strncasecmp($header, 'Last-Modified:', 14) === 0)
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+ }
+
+} // End expires
diff --git a/system/helpers/feed.php b/system/helpers/feed.php
new file mode 100644
index 0000000..9831812
--- /dev/null
+++ b/system/helpers/feed.php
@@ -0,0 +1,120 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Feed helper class.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class feed_Core {
+
+ /**
+ * Parses a remote feed into an array.
+ *
+ * @param string remote feed URL
+ * @param integer item limit to fetch
+ * @return array
+ */
+ public static function parse($feed, $limit = 0)
+ {
+ // Check if SimpleXML is installed
+ if( ! function_exists('simplexml_load_file'))
+ throw new Kohana_User_Exception('Feed Error', 'SimpleXML must be installed!');
+
+ // Make limit an integer
+ $limit = (int) $limit;
+
+ // Disable error reporting while opening the feed
+ $ER = error_reporting(0);
+
+ // Allow loading by filename or raw XML string
+ $load = (is_file($feed) OR valid::url($feed)) ? 'simplexml_load_file' : 'simplexml_load_string';
+
+ // Load the feed
+ $feed = $load($feed, 'SimpleXMLElement', LIBXML_NOCDATA);
+
+ // Restore error reporting
+ error_reporting($ER);
+
+ // Feed could not be loaded
+ if ($feed === FALSE)
+ return array();
+
+ // Detect the feed type. RSS 1.0/2.0 and Atom 1.0 are supported.
+ $feed = isset($feed->channel) ? $feed->xpath('//item') : $feed->entry;
+
+ $i = 0;
+ $items = array();
+
+ foreach ($feed as $item)
+ {
+ if ($limit > 0 AND $i++ === $limit)
+ break;
+
+ $items[] = (array) $item;
+ }
+
+ return $items;
+ }
+
+ /**
+ * Creates a feed from the given parameters.
+ *
+ * @param array feed information
+ * @param array items to add to the feed
+ * @param string define which format to use
+ * @param string define which encoding to use
+ * @return string
+ */
+ public static function create($info, $items, $format = 'rss2', $encoding = 'UTF-8')
+ {
+ $info += array('title' => 'Generated Feed', 'link' => '', 'generator' => 'KohanaPHP');
+
+ $feed = '<?xml version="1.0" encoding="'.$encoding.'"?><rss version="2.0"><channel></channel></rss>';
+ $feed = simplexml_load_string($feed);
+
+ foreach ($info as $name => $value)
+ {
+ if (($name === 'pubDate' OR $name === 'lastBuildDate') AND (is_int($value) OR ctype_digit($value)))
+ {
+ // Convert timestamps to RFC 822 formatted dates
+ $value = date(DATE_RFC822, $value);
+ }
+ elseif (($name === 'link' OR $name === 'docs') AND strpos($value, '://') === FALSE)
+ {
+ // Convert URIs to URLs
+ $value = url::site($value, 'http');
+ }
+
+ // Add the info to the channel
+ $feed->channel->addChild($name, $value);
+ }
+
+ foreach ($items as $item)
+ {
+ // Add the item to the channel
+ $row = $feed->channel->addChild('item');
+
+ foreach ($item as $name => $value)
+ {
+ if ($name === 'pubDate' AND (is_int($value) OR ctype_digit($value)))
+ {
+ // Convert timestamps to RFC 822 formatted dates
+ $value = date(DATE_RFC822, $value);
+ }
+ elseif (($name === 'link' OR $name === 'guid') AND strpos($value, '://') === FALSE)
+ {
+ // Convert URIs to URLs
+ $value = url::site($value, 'http');
+ }
+
+ // Add the info to the row
+ $row->addChild($name, $value);
+ }
+ }
+
+ return $feed->asXML();
+ }
+
+} // End feed \ No newline at end of file
diff --git a/system/helpers/file.php b/system/helpers/file.php
new file mode 100644
index 0000000..48fbfc1
--- /dev/null
+++ b/system/helpers/file.php
@@ -0,0 +1,184 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * File helper class.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class file_Core {
+
+ /**
+ * Attempt to get the mime type from a file. This method is horribly
+ * unreliable, due to PHP being horribly unreliable when it comes to
+ * determining the mime-type of a file.
+ *
+ * @param string filename
+ * @return string mime-type, if found
+ * @return boolean FALSE, if not found
+ */
+ public static function mime($filename)
+ {
+ // Make sure the file is readable
+ if ( ! (is_file($filename) AND is_readable($filename)))
+ return FALSE;
+
+ // Get the extension from the filename
+ $extension = strtolower(substr(strrchr($filename, '.'), 1));
+
+ if (preg_match('/^(?:jpe?g|png|[gt]if|bmp|swf)$/', $extension))
+ {
+ // Disable error reporting
+ $ER = error_reporting(0);
+
+ // Use getimagesize() to find the mime type on images
+ $mime = getimagesize($filename);
+
+ // Turn error reporting back on
+ error_reporting($ER);
+
+ // Return the mime type
+ if (isset($mime['mime']))
+ return $mime['mime'];
+ }
+
+ if (function_exists('finfo_open'))
+ {
+ // Use the fileinfo extension
+ $finfo = finfo_open(FILEINFO_MIME);
+ $mime = finfo_file($finfo, $filename);
+ finfo_close($finfo);
+
+ // Return the mime type
+ return $mime;
+ }
+
+ if (ini_get('mime_magic.magicfile') AND function_exists('mime_content_type'))
+ {
+ // Return the mime type using mime_content_type
+ return mime_content_type($filename);
+ }
+
+ if ( ! KOHANA_IS_WIN)
+ {
+ // Attempt to locate use the file command, checking the return value
+ if ($command = trim(exec('which file', $output, $return)) AND $return === 0)
+ {
+ return trim(exec($command.' -bi '.escapeshellarg($filename)));
+ }
+ }
+
+ if ( ! empty($extension) AND is_array($mime = Kohana::config('mimes.'.$extension)))
+ {
+ // Return the mime-type guess, based on the extension
+ return $mime[0];
+ }
+
+ // Unable to find the mime-type
+ return FALSE;
+ }
+
+ /**
+ * Split a file into pieces matching a specific size.
+ *
+ * @param string file to be split
+ * @param string directory to output to, defaults to the same directory as the file
+ * @param integer size, in MB, for each chunk to be
+ * @return integer The number of pieces that were created.
+ */
+ public static function split($filename, $output_dir = FALSE, $piece_size = 10)
+ {
+ // Find output dir
+ $output_dir = ($output_dir == FALSE) ? pathinfo(str_replace('\\', '/', realpath($filename)), PATHINFO_DIRNAME) : str_replace('\\', '/', realpath($output_dir));
+ $output_dir = rtrim($output_dir, '/').'/';
+
+ // Open files for writing
+ $input_file = fopen($filename, 'rb');
+
+ // Change the piece size to bytes
+ $piece_size = 1024 * 1024 * (int) $piece_size; // Size in bytes
+
+ // Set up reading variables
+ $read = 0; // Number of bytes read
+ $piece = 1; // Current piece
+ $chunk = 1024 * 8; // Chunk size to read
+
+ // Split the file
+ while ( ! feof($input_file))
+ {
+ // Open a new piece
+ $piece_name = $filename.'.'.str_pad($piece, 3, '0', STR_PAD_LEFT);
+ $piece_open = @fopen($piece_name, 'wb+') or die('Could not write piece '.$piece_name);
+
+ // Fill the current piece
+ while ($read < $piece_size AND $data = fread($input_file, $chunk))
+ {
+ fwrite($piece_open, $data) or die('Could not write to open piece '.$piece_name);
+ $read += $chunk;
+ }
+
+ // Close the current piece
+ fclose($piece_open);
+
+ // Prepare to open a new piece
+ $read = 0;
+ $piece++;
+
+ // Make sure that piece is valid
+ ($piece < 999) or die('Maximum of 999 pieces exceeded, try a larger piece size');
+ }
+
+ // Close input file
+ fclose($input_file);
+
+ // Returns the number of pieces that were created
+ return ($piece - 1);
+ }
+
+ /**
+ * Join a split file into a whole file.
+ *
+ * @param string split filename, without .000 extension
+ * @param string output filename, if different then an the filename
+ * @return integer The number of pieces that were joined.
+ */
+ public static function join($filename, $output = FALSE)
+ {
+ if ($output == FALSE)
+ $output = $filename;
+
+ // Set up reading variables
+ $piece = 1; // Current piece
+ $chunk = 1024 * 8; // Chunk size to read
+
+ // Open output file
+ $output_file = @fopen($output, 'wb+') or die('Could not open output file '.$output);
+
+ // Read each piece
+ while ($piece_open = @fopen(($piece_name = $filename.'.'.str_pad($piece, 3, '0', STR_PAD_LEFT)), 'rb'))
+ {
+ // Write the piece into the output file
+ while ( ! feof($piece_open))
+ {
+ fwrite($output_file, fread($piece_open, $chunk));
+ }
+
+ // Close the current piece
+ fclose($piece_open);
+
+ // Prepare for a new piece
+ $piece++;
+
+ // Make sure piece is valid
+ ($piece < 999) or die('Maximum of 999 pieces exceeded');
+ }
+
+ // Close the output file
+ fclose($output_file);
+
+ // Return the number of pieces joined
+ return ($piece - 1);
+ }
+
+} // End file \ No newline at end of file
diff --git a/system/helpers/form.php b/system/helpers/form.php
new file mode 100644
index 0000000..3d4f8ad
--- /dev/null
+++ b/system/helpers/form.php
@@ -0,0 +1,466 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Form helper class.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class form_Core {
+
+ /**
+ * Generates an opening HTML form tag.
+ *
+ * @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, $protocol = NULL)
+ {
+ // Make sure that the method is always set
+ empty($attr['method']) and $attr['method'] = 'post';
+
+ if ($attr['method'] !== 'post' AND $attr['method'] !== 'get')
+ {
+ // If the method is invalid, use post
+ $attr['method'] = 'post';
+ }
+
+ if ($action === NULL)
+ {
+ // Use the current URL as the default action
+ $action = url::site(Router::$complete_uri, $protocol);
+ }
+ elseif (strpos($action, '://') === FALSE)
+ {
+ // Make the action URI into a URL
+ $action = url::site($action, $protocol);
+ }
+
+ // Set action
+ $attr['action'] = $action;
+
+ // Form opening tag
+ $form = '<form'.form::attributes($attr).'>'."\n";
+
+ // Add hidden fields immediate after opening tag
+ empty($hidden) or $form .= form::hidden($hidden);
+
+ return $form;
+ }
+
+ /**
+ * Generates an opening HTML form tag that can be used for uploading files.
+ *
+ * @param string form action attribute
+ * @param array extra attributes
+ * @param array hidden fields to be created immediately after the form tag
+ * @return string
+ */
+ public static function open_multipart($action = NULL, $attr = array(), $hidden = array())
+ {
+ // Set multi-part form type
+ $attr['enctype'] = 'multipart/form-data';
+
+ return form::open($action, $attr, $hidden);
+ }
+
+ /**
+ * Creates a HTML form hidden input tag.
+ *
+ * @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 = '', $extra = '')
+ {
+ if ( ! is_array($data))
+ {
+ $data = array('name' => $data);
+ }
+
+ $data['type'] = 'hidden';
+
+ return form::input($data, $value, $extra);
+ }
+
+ /**
+ * Creates an HTML form input tag. Defaults to a text type.
+ *
+ * @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 input($data, $value = '', $extra = '')
+ {
+ if ( ! is_array($data))
+ {
+ $data = array('name' => $data);
+ }
+
+ // Type and value are required attributes
+ $data += array
+ (
+ 'type' => 'text',
+ 'value' => $value
+ );
+
+ return '<input'.form::attributes($data).' '.$extra.' />';
+ }
+
+ /**
+ * Creates a HTML form password input tag.
+ *
+ * @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 password($data, $value = '', $extra = '')
+ {
+ if ( ! is_array($data))
+ {
+ $data = array('name' => $data);
+ }
+
+ $data['type'] = 'password';
+
+ return form::input($data, $value, $extra);
+ }
+
+ /**
+ * Creates an HTML form upload input tag.
+ *
+ * @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 upload($data, $value = '', $extra = '')
+ {
+ if ( ! is_array($data))
+ {
+ $data = array('name' => $data);
+ }
+
+ $data['type'] = 'file';
+
+ return form::input($data, $value, $extra);
+ }
+
+ /**
+ * Creates an HTML form textarea tag.
+ *
+ * @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
+ * @param boolean encode existing entities
+ * @return string
+ */
+ public static function textarea($data, $value = '', $extra = '', $double_encode = TRUE)
+ {
+ if ( ! is_array($data))
+ {
+ $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.'>'.htmlspecialchars($value, ENT_QUOTES, Kohana::CHARSET, $double_encode).'</textarea>';
+ }
+
+ /**
+ * Creates an HTML form select tag, or "dropdown menu".
+ *
+ * @param string|array input name or an array of HTML attributes
+ * @param array select options, when using a name
+ * @param string|array option key(s) that should be selected by default
+ * @param string a string to be attached to the end of the attributes
+ * @return string
+ */
+ public static function dropdown($data, $options = NULL, $selected = NULL, $extra = '')
+ {
+ if ( ! is_array($data))
+ {
+ $data = array('name' => $data);
+ }
+ else
+ {
+ if (isset($data['options']))
+ {
+ // Use data options
+ $options = $data['options'];
+ }
+
+ if (isset($data['selected']))
+ {
+ // Use data selected
+ $selected = $data['selected'];
+ }
+ }
+
+ if (is_array($selected))
+ {
+ // Multi-select box
+ $data['multiple'] = 'multiple';
+ }
+ else
+ {
+ // Single selection (but converted to an array)
+ $selected = array($selected);
+ }
+
+ $input = '<select'.form::attributes($data, 'select').' '.$extra.'>'."\n";
+ foreach ((array) $options as $key => $val)
+ {
+ // Key should always be a string
+ $key = (string) $key;
+
+ if (is_array($val))
+ {
+ $input .= '<optgroup label="'.$key.'">'."\n";
+ foreach ($val as $inner_key => $inner_val)
+ {
+ // Inner key should always be a string
+ $inner_key = (string) $inner_key;
+
+ $sel = in_array($inner_key, $selected) ? ' selected="selected"' : '';
+ $input .= '<option value="'.$inner_key.'"'.$sel.'>'.htmlspecialchars($inner_val, ENT_QUOTES, Kohana::CHARSET, FALSE).'</option>'."\n";
+ }
+ $input .= '</optgroup>'."\n";
+ }
+ else
+ {
+ $sel = in_array($key, $selected) ? ' selected="selected"' : '';
+ $input .= '<option value="'.$key.'"'.$sel.'>'.htmlspecialchars($val, ENT_QUOTES, Kohana::CHARSET, FALSE).'</option>'."\n";
+ }
+ }
+ $input .= '</select>';
+
+ return $input;
+ }
+
+ /**
+ * Creates an HTML form checkbox input tag.
+ *
+ * @param string|array input name or an array of HTML attributes
+ * @param string input value, when using a name
+ * @param boolean make the checkbox checked by default
+ * @param string a string to be attached to the end of the attributes
+ * @return string
+ */
+ public static function checkbox($data, $value = '', $checked = FALSE, $extra = '')
+ {
+ if ( ! is_array($data))
+ {
+ $data = array('name' => $data);
+ }
+
+ $data['type'] = 'checkbox';
+
+ if ($checked == TRUE OR (isset($data['checked']) AND $data['checked'] == TRUE))
+ {
+ $data['checked'] = 'checked';
+ }
+ else
+ {
+ unset($data['checked']);
+ }
+
+ return form::input($data, $value, $extra);
+ }
+
+ /**
+ * Creates an HTML form radio input tag.
+ *
+ * @param string|array input name or an array of HTML attributes
+ * @param string input value, when using a name
+ * @param boolean make the radio selected by default
+ * @param string a string to be attached to the end of the attributes
+ * @return string
+ */
+ public static function radio($data = '', $value = '', $checked = FALSE, $extra = '')
+ {
+ if ( ! is_array($data))
+ {
+ $data = array('name' => $data);
+ }
+
+ $data['type'] = 'radio';
+
+ if ($checked == TRUE OR (isset($data['checked']) AND $data['checked'] == TRUE))
+ {
+ $data['checked'] = 'checked';
+ }
+ else
+ {
+ unset($data['checked']);
+ }
+
+ return form::input($data, $value, $extra);
+ }
+
+ /**
+ * Creates an HTML form submit input tag.
+ *
+ * @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 submit($data = '', $value = '', $extra = '')
+ {
+ if ( ! is_array($data))
+ {
+ $data = array('name' => $data);
+ }
+
+ if (empty($data['name']))
+ {
+ // Remove the name if it is empty
+ unset($data['name']);
+ }
+
+ $data['type'] = 'submit';
+
+ return form::input($data, $value, $extra);
+ }
+
+ /**
+ * Creates an HTML form button input tag.
+ *
+ * @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 button($data = '', $value = '', $extra = '')
+ {
+ if ( ! is_array($data))
+ {
+ $data = array('name' => $data);
+ }
+
+ if (empty($data['name']))
+ {
+ // Remove the name if it is empty
+ unset($data['name']);
+ }
+
+ if (isset($data['value']) AND empty($value))
+ {
+ $value = arr::remove('value', $data);
+ }
+
+ return '<button'.form::attributes($data, 'button').' '.$extra.'>'.$value.'</button>';
+ }
+
+ /**
+ * Creates an HTML form label tag.
+ *
+ * @param string|array label "for" name or an array of HTML attributes
+ * @param string label text or HTML
+ * @param string a string to be attached to the end of the attributes
+ * @return string
+ */
+ public static function label($data = '', $text = NULL, $extra = '')
+ {
+ if ( ! is_array($data))
+ {
+ if (is_string($data))
+ {
+ // Specify the input this label is for
+ $data = array('for' => $data);
+ }
+ else
+ {
+ // No input specified
+ $data = array();
+ }
+ }
+
+ if ($text === NULL AND isset($data['for']))
+ {
+ // Make the text the human-readable input name
+ $text = ucwords(inflector::humanize($data['for']));
+ }
+
+ return '<label'.form::attributes($data).' '.$extra.'>'.$text.'</label>';
+ }
+
+ /**
+ * Sorts a key/value array of HTML attributes, putting form attributes first,
+ * and returns an attribute string.
+ *
+ * @param array HTML attributes array
+ * @return string
+ */
+ public static function attributes($attr, $type = NULL)
+ {
+ if (empty($attr))
+ return '';
+
+ $order = array
+ (
+ 'action',
+ 'method',
+ 'type',
+ 'id',
+ 'name',
+ 'value',
+ 'src',
+ 'size',
+ 'maxlength',
+ 'rows',
+ 'cols',
+ 'accept',
+ 'tabindex',
+ 'accesskey',
+ 'align',
+ 'alt',
+ 'title',
+ 'class',
+ 'style',
+ 'selected',
+ 'checked',
+ 'readonly',
+ 'disabled'
+ );
+
+ $sorted = array();
+ foreach ($order as $key)
+ {
+ if (isset($attr[$key]))
+ {
+ // Move the attribute to the sorted array
+ $sorted[$key] = $attr[$key];
+
+ // Remove the attribute from unsorted array
+ unset($attr[$key]);
+ }
+ }
+
+ // Combine the sorted and unsorted attributes and create an HTML string
+ return html::attributes(array_merge($sorted, $attr));
+ }
+
+} // End form
diff --git a/system/helpers/format.php b/system/helpers/format.php
new file mode 100644
index 0000000..7494046
--- /dev/null
+++ b/system/helpers/format.php
@@ -0,0 +1,112 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Format helper class.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @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
+ * @param string format string
+ * @return string
+ */
+ public static function phone($number, $format = '3-3-4')
+ {
+ // Get rid of all non-digit characters in number string
+ $number_clean = preg_replace('/\D+/', '', (string) $number);
+
+ // Array of digits we need for a valid format
+ $format_parts = preg_split('/[^1-9][^0-9]*/', $format, -1, PREG_SPLIT_NO_EMPTY);
+
+ // Number must match digit count of a valid format
+ if (strlen($number_clean) !== array_sum($format_parts))
+ return $number;
+
+ // Build regex
+ $regex = '(\d{'.implode('})(\d{', $format_parts).'})';
+
+ // Build replace string
+ for ($i = 1, $c = count($format_parts); $i <= $c; $i++)
+ {
+ $format = preg_replace('/(?<!\$)[1-9][0-9]*/', '\$'.$i, $format, 1);
+ }
+
+ // Hocus pocus!
+ return preg_replace('/^'.$regex.'$/', $format, $number_clean);
+ }
+
+ /**
+ * Formats a URL to contain a protocol at the beginning.
+ *
+ * @param string possibly incomplete URL
+ * @return string
+ */
+ public static function url($str = '')
+ {
+ // Clear protocol-only strings like "http://"
+ if ($str === '' OR substr($str, -3) === '://')
+ return '';
+
+ // If no protocol given, prepend "http://" by default
+ if (strpos($str, '://') === FALSE)
+ return 'http://'.$str;
+
+ // Return the original URL
+ 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
new file mode 100644
index 0000000..c16031d
--- /dev/null
+++ b/system/helpers/html.php
@@ -0,0 +1,364 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * HTML helper class.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class html_Core {
+
+ // Enable or disable automatic setting of target="_blank"
+ public static $windowed_urls = FALSE;
+
+ /**
+ * Convert special characters to HTML entities
+ *
+ * @param string string to convert
+ * @param boolean encode existing entities
+ * @return string
+ */
+ public static function chars($str, $double_encode = TRUE)
+ {
+ // Return HTML entities using the Kohana charset
+ return htmlspecialchars($str, ENT_QUOTES, Kohana::CHARSET, $double_encode);
+ }
+
+ /**
+ * Create HTML link anchors.
+ *
+ * @param string URL or URI string
+ * @param string link text
+ * @param array HTML anchor attributes
+ * @param string non-default protocol, eg: https
+ * @param boolean option to escape the title that is output
+ * @return string
+ */
+ public static function anchor($uri, $title = NULL, $attributes = NULL, $protocol = NULL, $escape_title = FALSE)
+ {
+ if ($uri === '')
+ {
+ $site_url = url::base(FALSE);
+ }
+ elseif (strpos($uri, '#') === 0)
+ {
+ // This is an id target link, not a URL
+ $site_url = $uri;
+ }
+ elseif (strpos($uri, '://') === FALSE)
+ {
+ $site_url = url::site($uri, $protocol);
+ }
+ else
+ {
+ if (html::$windowed_urls === TRUE AND empty($attributes['target']))
+ {
+ $attributes['target'] = '_blank';
+ }
+
+ $site_url = $uri;
+ }
+
+ return
+ // Parsed URL
+ '<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 ? htmlspecialchars((($title === NULL) ? $site_url : $title), ENT_QUOTES, Kohana::CHARSET, FALSE) : (($title === NULL) ? $site_url : $title)).'</a>';
+ }
+
+ /**
+ * Creates an HTML anchor to a file.
+ *
+ * @param string name of file to link to
+ * @param string link text
+ * @param array HTML anchor attributes
+ * @param string non-default protocol, eg: ftp
+ * @return string
+ */
+ public static function file_anchor($file, $title = NULL, $attributes = NULL, $protocol = NULL)
+ {
+ return
+ // Base URL + URI = full URL
+ '<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
+ .(($title === NULL) ? end(explode('/', $file)) : $title) .'</a>';
+ }
+
+ /**
+ * Generates an obfuscated version of an email address.
+ *
+ * @param string email address
+ * @return string
+ */
+ public static function email($email)
+ {
+ $safe = '';
+ foreach (str_split($email) as $letter)
+ {
+ switch (($letter === '@') ? rand(1, 2) : rand(1, 3))
+ {
+ // HTML entity code
+ case 1: $safe .= '&#'.ord($letter).';'; break;
+ // Hex character code
+ case 2: $safe .= '&#x'.dechex(ord($letter)).';'; break;
+ // Raw (no) encoding
+ case 3: $safe .= $letter;
+ }
+ }
+
+ return $safe;
+ }
+
+ /**
+ * Creates an email anchor.
+ *
+ * @param string email address to send to
+ * @param string link text
+ * @param array HTML anchor attributes
+ * @return string
+ */
+ public static function mailto($email, $title = NULL, $attributes = NULL)
+ {
+ if (empty($email))
+ return $title;
+
+ // Remove the subject or other parameters that do not need to be encoded
+ if (strpos($email, '?') !== FALSE)
+ {
+ // Extract the parameters from the email address
+ list ($email, $params) = explode('?', $email, 2);
+
+ // Make the params into a query string, replacing spaces
+ $params = '?'.str_replace(' ', '%20', $params);
+ }
+ else
+ {
+ // No parameters
+ $params = '';
+ }
+
+ // Obfuscate email address
+ $safe = html::email($email);
+
+ // Title defaults to the encoded email address
+ empty($title) and $title = $safe;
+
+ // Parse attributes
+ empty($attributes) or $attributes = html::attributes($attributes);
+
+ // Encoded start of the href="" is a static encoded version of 'mailto:'
+ return '<a href="&#109;&#097;&#105;&#108;&#116;&#111;&#058;'.$safe.$params.'"'.$attributes.'>'.$title.'</a>';
+ }
+
+ /**
+ * Generate a "breadcrumb" list of anchors representing the URI.
+ *
+ * @param array segments to use as breadcrumbs, defaults to using Router::$segments
+ * @return string
+ */
+ public static function breadcrumb($segments = NULL)
+ {
+ empty($segments) and $segments = Router::$segments;
+
+ $array = array();
+ while ($segment = array_pop($segments))
+ {
+ $array[] = html::anchor
+ (
+ // Complete URI for the URL
+ implode('/', $segments).'/'.$segment,
+ // Title for the current segment
+ ucwords(inflector::humanize($segment))
+ );
+ }
+
+ // Retrun the array of all the segments
+ return array_reverse($array);
+ }
+
+ /**
+ * Creates a meta tag.
+ *
+ * @param string|array tag name, or an array of tags
+ * @param string tag "content" value
+ * @return string
+ */
+ public static function meta($tag, $value = NULL)
+ {
+ if (is_array($tag))
+ {
+ $tags = array();
+ foreach ($tag as $t => $v)
+ {
+ // Build each tag and add it to the array
+ $tags[] = html::meta($t, $v);
+ }
+
+ // Return all of the tags as a string
+ return implode("\n", $tags);
+ }
+
+ // Set the meta attribute value
+ $attr = in_array(strtolower($tag), Kohana::config('http.meta_equiv')) ? 'http-equiv' : 'name';
+
+ return '<meta '.$attr.'="'.$tag.'" content="'.$value.'" />';
+ }
+
+ /**
+ * Creates a stylesheet link.
+ *
+ * @param string|array filename, or array of filenames to match to array of medias
+ * @param string|array media type of stylesheet, or array to match filenames
+ * @param boolean include the index_page in the link
+ * @return string
+ */
+ public static function stylesheet($style, $media = FALSE, $index = FALSE)
+ {
+ return html::link($style, 'stylesheet', 'text/css', $media, $index);
+ }
+
+ /**
+ * Creates a link tag.
+ *
+ * @param string|array filename
+ * @param string|array relationship
+ * @param string|array mimetype
+ * @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, $media = FALSE, $index = FALSE)
+ {
+ $compiled = '';
+
+ if (is_array($href))
+ {
+ foreach ($href as $_href)
+ {
+ $_rel = is_array($rel) ? array_shift($rel) : $rel;
+ $_type = is_array($type) ? array_shift($type) : $type;
+ $_media = is_array($media) ? array_shift($media) : $media;
+
+ $compiled .= html::link($_href, $_rel, $_type, $_media, $index);
+ }
+ }
+ else
+ {
+ if (strpos($href, '://') === FALSE)
+ {
+ // Make the URL absolute
+ $href = url::base($index).$href;
+ }
+
+ $attr = array
+ (
+ 'rel' => $rel,
+ 'type' => $type,
+ 'href' => $href,
+ );
+
+ if ( ! empty($media))
+ {
+ // Add the media type to the attributes
+ $attr['media'] = $media;
+ }
+
+ $compiled = '<link'.html::attributes($attr).' />';
+ }
+
+ return $compiled."\n";
+ }
+
+ /**
+ * Creates a script link.
+ *
+ * @param string|array filename
+ * @param boolean include the index_page in the link
+ * @return string
+ */
+ public static function script($script, $index = FALSE)
+ {
+ $compiled = '';
+
+ if (is_array($script))
+ {
+ foreach ($script as $name)
+ {
+ $compiled .= html::script($name, $index);
+ }
+ }
+ else
+ {
+ if (strpos($script, '://') === FALSE)
+ {
+ // Add the suffix only when it's not already present
+ $script = url::base((bool) $index).$script;
+ }
+
+ $compiled = '<script type="text/javascript" src="'.$script.'"></script>';
+ }
+
+ return $compiled."\n";
+ }
+
+ /**
+ * Creates a image link.
+ *
+ * @param string image source, or an array of attributes
+ * @param string|array image alt attribute, or an array of attributes
+ * @param boolean include the index_page in the link
+ * @return string
+ */
+ public static function image($src = NULL, $alt = NULL, $index = FALSE)
+ {
+ // Create attribute list
+ $attributes = is_array($src) ? $src : array('src' => $src);
+
+ if (is_array($alt))
+ {
+ $attributes += $alt;
+ }
+ elseif ( ! empty($alt))
+ {
+ // Add alt to attributes
+ $attributes['alt'] = $alt;
+ }
+
+ if (strpos($attributes['src'], '://') === FALSE)
+ {
+ // Make the src attribute into an absolute URL
+ $attributes['src'] = url::base($index).$attributes['src'];
+ }
+
+ return '<img'.html::attributes($attributes).' />';
+ }
+
+ /**
+ * Compiles an array of HTML attributes into an attribute string.
+ *
+ * @param string|array array of attributes
+ * @return string
+ */
+ public static function attributes($attrs)
+ {
+ if (empty($attrs))
+ return '';
+
+ if (is_string($attrs))
+ return ' '.$attrs;
+
+ $compiled = '';
+ foreach ($attrs as $key => $val)
+ {
+ $compiled .= ' '.$key.'="'.htmlspecialchars($val, ENT_QUOTES, Kohana::CHARSET).'"';
+ }
+
+ return $compiled;
+ }
+
+} // End html
diff --git a/system/helpers/inflector.php b/system/helpers/inflector.php
new file mode 100644
index 0000000..e723707
--- /dev/null
+++ b/system/helpers/inflector.php
@@ -0,0 +1,252 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Inflector helper class.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class inflector_Core {
+
+ // Cached inflections
+ protected static $cache = array();
+
+ // Uncountable and irregular words
+ protected static $uncountable;
+ protected static $irregular;
+
+ /**
+ * Checks if a word is defined as uncountable.
+ *
+ * @param string word to check
+ * @return boolean
+ */
+ public static function uncountable($str)
+ {
+ if (inflector::$uncountable === NULL)
+ {
+ // Cache uncountables
+ inflector::$uncountable = Kohana::config('inflector.uncountable');
+
+ // Make uncountables mirroed
+ inflector::$uncountable = array_combine(inflector::$uncountable, inflector::$uncountable);
+ }
+
+ return isset(inflector::$uncountable[strtolower($str)]);
+ }
+
+ /**
+ * Makes a plural word singular.
+ *
+ * @param string word to singularize
+ * @param integer number of things
+ * @return string
+ */
+ 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));
+
+ if (is_string($count))
+ {
+ // Convert to integer when using a digit string
+ $count = (int) $count;
+ }
+
+ // Do nothing with a single count
+ if ($count === 0 OR $count > 1)
+ return $str;
+
+ // Cache key name
+ $key = 'singular_'.$str.$count;
+
+ if (isset(inflector::$cache[$key]))
+ return inflector::$cache[$key];
+
+ if (inflector::uncountable($str))
+ return inflector::$cache[$key] = $str;
+
+ if (empty(inflector::$irregular))
+ {
+ // Cache irregular words
+ inflector::$irregular = Kohana::config('inflector.irregular');
+ }
+
+ if ($irregular = array_search($str, inflector::$irregular))
+ {
+ $str = $irregular;
+ }
+ elseif (preg_match('/[sxz]es$/', $str) OR preg_match('/[^aeioudgkprt]hes$/', $str))
+ {
+ // Remove "es"
+ $str = substr($str, 0, -2);
+ }
+ elseif (preg_match('/[^aeiou]ies$/', $str))
+ {
+ $str = substr($str, 0, -3).'y';
+ }
+ elseif (substr($str, -1) === 's' AND substr($str, -2) !== 'ss')
+ {
+ $str = substr($str, 0, -1);
+ }
+
+ return inflector::$cache[$key] = $str;
+ }
+
+ /**
+ * Makes a singular word plural.
+ *
+ * @param string word to pluralize
+ * @return string
+ */
+ 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));
+
+ if (is_string($count))
+ {
+ // Convert to integer when using a digit string
+ $count = (int) $count;
+ }
+
+ // Do nothing with singular
+ if ($count === 1)
+ return $str;
+
+ // Cache key name
+ $key = 'plural_'.$str.$count;
+
+ if (isset(inflector::$cache[$key]))
+ return inflector::$cache[$key];
+
+ if (inflector::uncountable($str))
+ return inflector::$cache[$key] = $str;
+
+ if (empty(inflector::$irregular))
+ {
+ // Cache irregular words
+ inflector::$irregular = Kohana::config('inflector.irregular');
+ }
+
+ if (isset(inflector::$irregular[$str]))
+ {
+ $str = inflector::$irregular[$str];
+ }
+ elseif (preg_match('/[sxz]$/', $str) OR preg_match('/[^aeioudgkprt]h$/', $str))
+ {
+ $str .= 'es';
+ }
+ elseif (preg_match('/[^aeiou]y$/', $str))
+ {
+ // Change "y" to "ies"
+ $str = substr_replace($str, 'ies', -1);
+ }
+ else
+ {
+ $str .= 's';
+ }
+
+ // Set the cache and return
+ return inflector::$cache[$key] = $str;
+ }
+
+ /**
+ * 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
+ * @return string
+ */
+ public static function camelize($str)
+ {
+ $str = 'x'.strtolower(trim($str));
+ $str = ucwords(preg_replace('/[\s_]+/', ' ', $str));
+
+ return substr(str_replace(' ', '', $str), 1);
+ }
+
+ /**
+ * Makes a phrase underscored instead of spaced.
+ *
+ * @param string phrase to underscore
+ * @return string
+ */
+ public static function underscore($str)
+ {
+ return trim(preg_replace('/[\s_]+/', '_', $str), '_');
+ }
+
+ /**
+ * Makes an underscored or dashed phrase human-readable.
+ *
+ * @param string phrase to make human-readable
+ * @return string
+ */
+ public static function humanize($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
new file mode 100644
index 0000000..c9e9843
--- /dev/null
+++ b/system/helpers/num.php
@@ -0,0 +1,24 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Number helper class.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class num_Core {
+
+ /**
+ * Round a number to the nearest nth
+ *
+ * @param integer number to round
+ * @param integer number to round to
+ * @return integer
+ */
+ public static function round($number, $nearest = 5)
+ {
+ return round($number / $nearest) * $nearest;
+ }
+
+} // End num \ No newline at end of file
diff --git a/system/helpers/remote.php b/system/helpers/remote.php
new file mode 100644
index 0000000..e6edb9c
--- /dev/null
+++ b/system/helpers/remote.php
@@ -0,0 +1,64 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Remote url/file helper.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class remote_Core {
+
+ public static function status($url)
+ {
+ if ( ! valid::url($url))
+ return FALSE;
+
+ // Get the hostname and path
+ $url = parse_url($url);
+
+ if (empty($url['path']))
+ {
+ // Request the root document
+ $url['path'] = '/';
+ }
+
+ // Open a remote connection
+ $remote = fsockopen($url['host'], 80, $errno, $errstr, 5);
+
+ if ( ! is_resource($remote))
+ return FALSE;
+
+ // Set CRLF
+ $CRLF = "\r\n";
+
+ // Send request
+ 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);
+
+ // Send one more CRLF to terminate the headers
+ fwrite($remote, $CRLF);
+
+ while ( ! feof($remote))
+ {
+ // Get the line
+ $line = trim(fgets($remote, 512));
+
+ if ($line !== '' AND preg_match('#^HTTP/1\.[01] (\d{3})#', $line, $matches))
+ {
+ // Response code found
+ $response = (int) $matches[1];
+
+ break;
+ }
+ }
+
+ // Close the connection
+ fclose($remote);
+
+ return isset($response) ? $response : FALSE;
+ }
+
+} // End remote
diff --git a/system/helpers/request.php b/system/helpers/request.php
new file mode 100644
index 0000000..c04a2fa
--- /dev/null
+++ b/system/helpers/request.php
@@ -0,0 +1,618 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Request helper class.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @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');
+
+ // 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, $remove_base = FALSE)
+ {
+ if ( ! empty($_SERVER['HTTP_REFERER']))
+ {
+ // Set referrer
+ $ref = $_SERVER['HTTP_REFERER'];
+
+ 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)));
+ }
+ }
+
+ return isset($ref) ? $ref : $default;
+ }
+
+ /**
+ * Returns the current request protocol, based on $_SERVER['https']. In CLI
+ * mode, NULL will be returned.
+ *
+ * @return string
+ */
+ public static function protocol()
+ {
+ if (Kohana::$server_api === 'cli')
+ {
+ return NULL;
+ }
+ elseif ( ! empty($_SERVER['HTTPS']) AND $_SERVER['HTTPS'] === 'on')
+ {
+ return 'https';
+ }
+ else
+ {
+ return 'http';
+ }
+ }
+
+ /**
+ * Tests if the current request is an AJAX request by checking the X-Requested-With HTTP
+ * request header that most popular JS frameworks now set for AJAX calls.
+ *
+ * @return boolean
+ */
+ public static function is_ajax()
+ {
+ return (isset($_SERVER['HTTP_X_REQUESTED_WITH']) AND strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest');
+ }
+
+ /**
+ * Returns current request method.
+ *
+ * @throws Kohana_Exception in case of an unknown request method
+ * @return string
+ */
+ public static function method()
+ {
+ $method = strtolower($_SERVER['REQUEST_METHOD']);
+
+ if ( ! in_array($method, request::$http_methods))
+ 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 = array();
+ 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.
+ *
+ * @param string content type
+ * @param boolean set to TRUE to disable wildcard checking
+ * @return boolean
+ */
+ public static function accepts($type = NULL, $explicit_check = FALSE)
+ {
+ request::parse_accept_content_header();
+
+ if ($type === NULL)
+ return request::$accept_types;
+
+ return (request::accepts_at_quality($type, $explicit_check) > 0);
+ }
+
+ /**
+ * 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.
+ *
+ * @param array content types
+ * @param boolean set to TRUE to disable wildcard checking
+ * @return mixed string mime type with highest q value, FALSE if none of the given types are accepted
+ */
+ public static function preferred_accept($types, $explicit_check = FALSE)
+ {
+ $max_q = 0;
+ $preferred = FALSE;
+
+ 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)
+ {
+ $q = request::accepts_encoding_at_quality($encoding, $explicit_check);
+
+ if ($q > $max_q)
+ {
+ $max_q = $q;
+ $preferred = $encoding;
+ }
+ }
+
+ 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 = $tag;
+ }
+ }
+
+ return $preferred;
+ }
+
+ /**
+ * 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, $explicit_check = FALSE)
+ {
+ request::parse_accept_content_header();
+
+ // Normalize type
+ $type = strtolower($type);
+
+ // General content type (e.g. "jpg")
+ if (strpos($type, '/') === FALSE)
+ {
+ // Don't accept anything by default
+ $q = 0;
+
+ // Look up relevant mime types
+ foreach ((array) Kohana::config('mimes.'.$type) as $type)
+ {
+ $q2 = request::accepts_at_quality($type, $explicit_check);
+ $q = ($q2 > $q) ? $q2 : $q;
+ }
+
+ return $q;
+ }
+
+ // Content type with subtype given (e.g. "image/jpg")
+ $type = explode('/', $type, 2);
+
+ // Exact match
+ if (isset(request::$accept_types[$type[0]][$type[1]]))
+ return request::$accept_types[$type[0]][$type[1]];
+
+ if ($explicit_check === FALSE)
+ {
+ // Wildcard match
+ if (isset(request::$accept_types[$type[0]]['*']))
+ return request::$accept_types[$type[0]]['*'];
+
+ // Catch-all wildcard match
+ if (isset(request::$accept_types['*']['*']))
+ return request::$accept_types['*']['*'];
+ }
+
+ // Content type not accepted
+ return 0;
+ }
+
+ /**
+ * 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
+ *
+ * @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_charset_header()
+ {
+ // Run this function just once
+ if (request::$accept_charsets !== NULL)
+ return;
+
+ // 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;
+ }
+ else
+ {
+ 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;
+
+ // 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']);
+ }
+ }
+
+ /**
+ * 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;
+
+ // No HTTP Accept-Language header found
+ if (empty($_SERVER['HTTP_ACCEPT_LANGUAGE']))
+ {
+ // Accept everything
+ request::$accept_languages['*'] = 1;
+ }
+ else
+ {
+ request::$accept_languages = array();
+
+ foreach (request::parse_accept_header($_SERVER['HTTP_ACCEPT_LANGUAGE']) as $tag => $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
diff --git a/system/helpers/security.php b/system/helpers/security.php
new file mode 100644
index 0000000..7103bd1
--- /dev/null
+++ b/system/helpers/security.php
@@ -0,0 +1,35 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Security helper class.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+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, $tool = NULL)
+ {
+ return Input::instance()->xss_clean($str, $tool);
+ }
+
+ /**
+ * Remove image tags from a string.
+ *
+ * @param string string to sanitize
+ * @return string
+ */
+ public static function strip_image_tags($str)
+ {
+ return preg_replace('#<img\s.*?(?:src\s*=\s*["\']?([^"\'<>\s]*)["\']?[^>]*)?>#is', '$1', $str);
+ }
+
+} // End security \ No newline at end of file
diff --git a/system/helpers/text.php b/system/helpers/text.php
new file mode 100644
index 0000000..f7f040c
--- /dev/null
+++ b/system/helpers/text.php
@@ -0,0 +1,596 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Text helper class.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class text_Core {
+
+ /**
+ * Limits a phrase to a given number of words.
+ *
+ * @param string phrase to limit words of
+ * @param integer number of words to limit to
+ * @param string end character or entity
+ * @return string
+ */
+ public static function limit_words($str, $limit = 100, $end_char = NULL)
+ {
+ $limit = (int) $limit;
+ $end_char = ($end_char === NULL) ? '…' : $end_char;
+
+ if (trim($str) === '')
+ return $str;
+
+ if ($limit <= 0)
+ return $end_char;
+
+ preg_match('/^\s*+(?:\S++\s*+){1,'.$limit.'}/u', $str, $matches);
+
+ // Only attach the end character if the matched string is shorter
+ // than the starting string.
+ return rtrim($matches[0]).(strlen($matches[0]) === strlen($str) ? '' : $end_char);
+ }
+
+ /**
+ * Limits a phrase to a given number of characters.
+ *
+ * @param string phrase to limit characters of
+ * @param integer number of characters to limit to
+ * @param string end character or entity
+ * @param boolean enable or disable the preservation of words while limiting
+ * @return string
+ */
+ public static function limit_chars($str, $limit = 100, $end_char = NULL, $preserve_words = FALSE)
+ {
+ $end_char = ($end_char === NULL) ? '…' : $end_char;
+
+ $limit = (int) $limit;
+
+ if (trim($str) === '' OR mb_strlen($str) <= $limit)
+ return $str;
+
+ if ($limit <= 0)
+ return $end_char;
+
+ if ($preserve_words == FALSE)
+ {
+ return rtrim(mb_substr($str, 0, $limit)).$end_char;
+ }
+
+ preg_match('/^.{'.($limit - 1).'}\S*/us', $str, $matches);
+
+ return rtrim($matches[0]).(strlen($matches[0]) == strlen($str) ? '' : $end_char);
+ }
+
+ /**
+ * Alternates between two or more strings.
+ *
+ * @param string strings to alternate between
+ * @return string
+ */
+ public static function alternate()
+ {
+ static $i;
+
+ if (func_num_args() === 0)
+ {
+ $i = 0;
+ return '';
+ }
+
+ $args = func_get_args();
+ return $args[($i++ % count($args))];
+ }
+
+ /**
+ * Generates a random string of a given type and length.
+ *
+ * @param string a type of pool, or a string of characters to use as the pool
+ * @param integer length of string to return
+ * @return string
+ *
+ * @tutorial alnum alpha-numeric characters
+ * @tutorial alpha alphabetical characters
+ * @tutorial hexdec hexadecimal characters, 0-9 plus a-f
+ * @tutorial numeric digit characters, 0-9
+ * @tutorial nozero digit characters, 1-9
+ * @tutorial distinct clearly distinct alpha-numeric characters
+ */
+ public static function random($type = 'alnum', $length = 8)
+ {
+ $utf8 = FALSE;
+
+ switch ($type)
+ {
+ case 'alnum':
+ $pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+ break;
+ case 'alpha':
+ $pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+ break;
+ case 'hexdec':
+ $pool = '0123456789abcdef';
+ break;
+ case 'numeric':
+ $pool = '0123456789';
+ break;
+ case 'nozero':
+ $pool = '123456789';
+ break;
+ case 'distinct':
+ $pool = '2345679ACDEFHJKLMNPRSTUVWXYZ';
+ break;
+ default:
+ $pool = (string) $type;
+ $utf8 = ! text::is_ascii($pool);
+ break;
+ }
+
+ // Split the pool into an array of characters
+ $pool = ($utf8 === TRUE) ? utf8::str_split($pool, 1) : str_split($pool, 1);
+
+ // Largest pool key
+ $max = count($pool) - 1;
+
+ $str = '';
+ for ($i = 0; $i < $length; $i++)
+ {
+ // Select a random character from the pool and add it to the string
+ $str .= $pool[mt_rand(0, $max)];
+ }
+
+ // Make sure alnum strings contain at least one letter and one digit
+ if ($type === 'alnum' AND $length > 1)
+ {
+ if (ctype_alpha($str))
+ {
+ // Add a random digit
+ $str[mt_rand(0, $length - 1)] = chr(mt_rand(48, 57));
+ }
+ elseif (ctype_digit($str))
+ {
+ // Add a random letter
+ $str[mt_rand(0, $length - 1)] = chr(mt_rand(65, 90));
+ }
+ }
+
+ return $str;
+ }
+
+ /**
+ * Reduces multiple slashes in a string to single slashes.
+ *
+ * @param string string to reduce slashes of
+ * @return string
+ */
+ public static function reduce_slashes($str)
+ {
+ return preg_replace('#(?<!:)//+#', '/', $str);
+ }
+
+ /**
+ * Replaces the given words with a string.
+ *
+ * @param string phrase to replace words in
+ * @param array words to replace
+ * @param string replacement string
+ * @param boolean replace words across word boundries (space, period, etc)
+ * @return string
+ */
+ public static function censor($str, $badwords, $replacement = '#', $replace_partial_words = TRUE)
+ {
+ foreach ((array) $badwords as $key => $badword)
+ {
+ $badwords[$key] = str_replace('\*', '\S*?', preg_quote((string) $badword));
+ }
+
+ $regex = '('.implode('|', $badwords).')';
+
+ 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|$)';
+ }
+
+ $regex = '!'.$regex.'!ui';
+
+ if (mb_strlen($replacement) == 1)
+ {
+ $regex .= 'e';
+ return preg_replace($regex, 'str_repeat($replacement, mb_strlen(\'$1\'))', $str);
+ }
+
+ return preg_replace($regex, $replacement, $str);
+ }
+
+ /**
+ * Finds the text that is similar between a set of words.
+ *
+ * @param array words to find similar text of
+ * @return string
+ */
+ public static function similar(array $words)
+ {
+ // First word is the word to match against
+ $word = current($words);
+
+ for ($i = 0, $max = strlen($word); $i < $max; ++$i)
+ {
+ foreach ($words as $w)
+ {
+ // Once a difference is found, break out of the loops
+ if ( ! isset($w[$i]) OR $w[$i] !== $word[$i])
+ break 2;
+ }
+ }
+
+ // Return the similar text
+ return substr($word, 0, $i);
+ }
+
+ /**
+ * 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
+ *
+ * @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 distance($string1, $string2)
+ {
+ $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];
+ }
+
+ /**
+ * Converts text anchors into links.
+ *
+ * @param string text to auto link
+ * @return string
+ */
+ public static function auto_link_urls($text)
+ {
+
+ $regex = '~\\b'
+ .'((?:ht|f)tps?://)?' // protocol
+ .'(?:[-a-zA-Z0-9]{1,63}\.)+' // host name
+ .'(?:[0-9]{1,3}|aero|asia|biz|cat|com|coop|edu|gov|info|int|jobs|mil|mobi|museum|name|net|org|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw)' // tlds
+ .'(?:/[!$-/0-9:;=@_\':;!a-zA-Z\x7f-\xff]*?)?' // path
+ .'(?:\?[!$-/0-9:;=@_\':;!a-zA-Z\x7f-\xff]+?)?' // query
+ .'(?:#[!$-/0-9:;=@_\':;!a-zA-Z\x7f-\xff]+?)?' // fragment
+ .'(?=[?.!,;:"]?(?:\s|$))~'; // punctuation and url end
+
+ $result = "";
+ $position = 0;
+
+ while (preg_match($regex, $text, $match, PREG_OFFSET_CAPTURE, $position))
+ {
+ list($url, $url_pos) = $match[0];
+
+ // Add the text before the url
+ $result .= substr($text, $position, $url_pos - $position);
+
+ // Default to http://
+ $full_url = empty($match[1][0]) ? 'http://'.$url : $url;
+
+ // Add the hyperlink.
+ $result .= html::anchor($full_url, $url);
+
+ // New position to start parsing
+ $position = $url_pos + strlen($url);
+ }
+
+ return $result.substr($text, $position);
+ }
+
+ /**
+ * Converts text email addresses into links.
+ *
+ * @param string text to auto link
+ * @return string
+ */
+ public static function auto_link_emails($text)
+ {
+ // Finds all email addresses that are not part of an existing html mailto anchor
+ // Note: The "58;" negative lookbehind prevents matching of existing encoded html mailto anchors
+ // The html entity for a colon (:) is &#58; or &#058; or &#0058; etc.
+ if (preg_match_all('~\b(?<!href="mailto:|">|58;)(?!\.)[-+_a-z0-9.]++(?<!\.)@(?![-.])[-a-z0-9.]+(?<!\.)\.[a-z]{2,6}\b~i', $text, $matches))
+ {
+ foreach ($matches[0] as $match)
+ {
+ // Replace each email with an encoded mailto
+ $text = str_replace($match, html::mailto($match), $text);
+ }
+ }
+
+ return $text;
+ }
+
+ /**
+ * 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, $br = TRUE)
+ {
+ // Trim whitespace
+ if (($str = trim($str)) === '')
+ return '';
+
+ // Standardize newlines
+ $str = str_replace(array("\r\n", "\r"), "\n", $str);
+
+ // Trim whitespace on each line
+ $str = preg_replace('~^[ \t]+~m', '', $str);
+ $str = preg_replace('~[ \t]+$~m', '', $str);
+
+ // The following regexes only need to be executed if the string contains html
+ if ($html_found = (strpos($str, '<') !== FALSE))
+ {
+ // Elements that should not be surrounded by p tags
+ $no_p = '(?:p|div|h[1-6r]|ul|ol|li|blockquote|d[dlt]|pre|t[dhr]|t(?:able|body|foot|head)|c(?:aption|olgroup)|form|s(?:elect|tyle)|a(?:ddress|rea)|ma(?:p|th))';
+
+ // Put at least two linebreaks before and after $no_p elements
+ $str = preg_replace('~^<'.$no_p.'[^>]*+>~im', "\n$0", $str);
+ $str = preg_replace('~</'.$no_p.'\s*+>$~im', "$0\n", $str);
+ }
+
+ // Do the <p> magic!
+ $str = '<p>'.trim($str).'</p>';
+ $str = preg_replace('~\n{2,}~', "</p>\n\n<p>", $str);
+
+ // The following regexes only need to be executed if the string contains html
+ if ($html_found !== FALSE)
+ {
+ // Remove p tags around $no_p elements
+ $str = preg_replace('~<p>(?=</?'.$no_p.'[^>]*+>)~i', '', $str);
+ $str = preg_replace('~(</?'.$no_p.'[^>]*+>)</p>~i', '$1', $str);
+ }
+
+ // Convert single linebreaks to <br />
+ if ($br === TRUE)
+ {
+ $str = preg_replace('~(?<!\n)\n(?!\n)~', "<br />\n", $str);
+ }
+
+ return $str;
+ }
+
+ /**
+ * Returns human readable sizes.
+ * @see Based on original functions written by:
+ * @see Aidan Lister: http://aidanlister.com/repos/v/function.size_readable.php
+ * @see Quentin Zervaas: http://www.phpriot.com/d/code/strings/filesize-format/
+ *
+ * @param integer size in bytes
+ * @param string a definitive unit
+ * @param string the return string format
+ * @param boolean whether to use SI prefixes or IEC
+ * @return string
+ */
+ public static function bytes($bytes, $force_unit = NULL, $format = NULL, $si = TRUE)
+ {
+ // Format string
+ $format = ($format === NULL) ? '%01.2f %s' : (string) $format;
+
+ // IEC prefixes (binary)
+ if ($si == FALSE OR strpos($force_unit, 'i') !== FALSE)
+ {
+ $units = array(__('B'), __('KiB'), __('MiB'), __('GiB'), __('TiB'), __('PiB'));
+ $mod = 1024;
+ }
+ // SI prefixes (decimal)
+ else
+ {
+ $units = array(__('B'), __('kB'), __('MB'), __('GB'), __('TB'), __('PB'));
+ $mod = 1000;
+ }
+
+ // Determine unit to use
+ if (($power = array_search((string) $force_unit, $units)) === FALSE)
+ {
+ $power = ($bytes > 0) ? floor(log($bytes, $mod)) : 0;
+ }
+
+ return sprintf($format, $bytes / pow($mod, $power), $units[$power]);
+ }
+
+ /**
+ * Prevents widow words by inserting a non-breaking space between the last two words.
+ * @see http://www.shauninman.com/archive/2006/08/22/widont_wordpress_plugin
+ *
+ * @param string string to remove widows from
+ * @return string
+ */
+ public static function widont($str)
+ {
+ $str = rtrim($str);
+ $space = strrpos($str, ' ');
+
+ if ($space !== FALSE)
+ {
+ $str = substr($str, 0, $space).'&nbsp;'.substr($str, $space + 1);
+ }
+
+ 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
new file mode 100644
index 0000000..62de674
--- /dev/null
+++ b/system/helpers/upload.php
@@ -0,0 +1,157 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Upload helper class for working with the global $_FILES
+ * array and Validation library.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class upload_Core {
+
+ /**
+ * Save an uploaded file to a new location.
+ *
+ * @param mixed name of $_FILE input or array of upload data
+ * @param string new filename
+ * @param string new directory
+ * @param integer chmod mask
+ * @return string full path to new file
+ */
+ public static function save($file, $filename = NULL, $directory = NULL, $chmod = 0644)
+ {
+ // Load file data from FILES if not passed as array
+ $file = is_array($file) ? $file : $_FILES[$file];
+
+ if ($filename === NULL)
+ {
+ // Use the default filename, with a timestamp pre-pended
+ $filename = time().$file['name'];
+ }
+
+ if (Kohana::config('upload.remove_spaces') === TRUE)
+ {
+ // Remove spaces from the filename
+ $filename = preg_replace('/\s+/', '_', $filename);
+ }
+
+ if ($directory === NULL)
+ {
+ // Use the pre-configured upload directory
+ $directory = Kohana::config('upload.directory', TRUE);
+ }
+
+ // Make sure the directory ends with a slash
+ $directory = rtrim($directory, '/').'/';
+
+ if ( ! is_dir($directory) AND Kohana::config('upload.create_directories') === TRUE)
+ {
+ // Create the upload directory
+ mkdir($directory, 0777, TRUE);
+ }
+
+ if ( ! is_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))
+ {
+ if ($chmod !== FALSE)
+ {
+ // Set permissions on filename
+ chmod($filename, $chmod);
+ }
+
+ // Return new file path
+ return $filename;
+ }
+
+ return FALSE;
+ }
+
+ /* Validation Rules */
+
+ /**
+ * Tests if input data is valid file type, even if no upload is present.
+ *
+ * @param array $_FILES item
+ * @return bool
+ */
+ public static function valid($file)
+ {
+ return (is_array($file)
+ AND isset($file['error'])
+ AND isset($file['name'])
+ AND isset($file['type'])
+ AND isset($file['tmp_name'])
+ AND isset($file['size']));
+ }
+
+ /**
+ * Tests if input data has valid upload data.
+ *
+ * @param array $_FILES item
+ * @return bool
+ */
+ public static function required(array $file)
+ {
+ return (isset($file['tmp_name'])
+ AND isset($file['error'])
+ AND is_uploaded_file($file['tmp_name'])
+ AND (int) $file['error'] === UPLOAD_ERR_OK);
+ }
+
+ /**
+ * Validation rule to test if an uploaded file is allowed by extension.
+ *
+ * @param array $_FILES item
+ * @param array allowed file extensions
+ * @return bool
+ */
+ public static function type(array $file, array $allowed_types)
+ {
+ if ((int) $file['error'] !== UPLOAD_ERR_OK)
+ return TRUE;
+
+ // Get the default extension of the file
+ $extension = strtolower(substr(strrchr($file['name'], '.'), 1));
+
+ // Make sure there is an extension and that the extension is allowed
+ return ( ! empty($extension) AND in_array($extension, $allowed_types));
+ }
+
+ /**
+ * Validation rule to test if an uploaded file is allowed by file size.
+ * File sizes are defined as: SB, where S is the size (1, 15, 300, etc) and
+ * B is the byte modifier: (B)ytes, (K)ilobytes, (M)egabytes, (G)igabytes.
+ * Eg: to limit the size to 1MB or less, you would use "1M".
+ *
+ * @param array $_FILES item
+ * @param array maximum file size
+ * @return bool
+ */
+ public static function size(array $file, array $size)
+ {
+ if ((int) $file['error'] !== UPLOAD_ERR_OK)
+ return TRUE;
+
+ // Only one size is allowed
+ $size = strtoupper($size[0]);
+
+ if ( ! preg_match('/[0-9]++[BKMG]/', $size))
+ return FALSE;
+
+ // Make the size into a power of 1024
+ switch (substr($size, -1))
+ {
+ case 'G': $size = intval($size) * pow(1024, 3); break;
+ case 'M': $size = intval($size) * pow(1024, 2); break;
+ case 'K': $size = intval($size) * pow(1024, 1); break;
+ default: $size = intval($size); break;
+ }
+
+ // Test that the file is under or equal to the max size
+ return ($file['size'] <= $size);
+ }
+
+} // End upload \ No newline at end of file
diff --git a/system/helpers/url.php b/system/helpers/url.php
new file mode 100644
index 0000000..02956fc
--- /dev/null
+++ b/system/helpers/url.php
@@ -0,0 +1,264 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * URL helper class.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class url_Core {
+
+ /**
+ * Fetches the current URI.
+ *
+ * @param boolean include the query string
+ * @param boolean include the suffix
+ * @return string
+ */
+ public static function current($qs = FALSE, $suffix = FALSE)
+ {
+ $uri = ($qs === TRUE) ? Router::$complete_uri : Router::$current_uri;
+
+ return ($suffix === TRUE) ? $uri.Kohana::config('core.url_suffix') : $uri;
+ }
+
+ /**
+ * Base URL, with or without the index page.
+ *
+ * If protocol (and core.site_protocol) and core.site_domain are both empty,
+ * then
+ *
+ * @param boolean include the index page
+ * @param boolean non-default protocol
+ * @return string
+ */
+ public static function base($index = FALSE, $protocol = FALSE)
+ {
+ if ($protocol == FALSE)
+ {
+ // Use the default configured protocol
+ $protocol = Kohana::config('core.site_protocol');
+ }
+
+ // Load the site domain
+ $site_domain = (string) Kohana::config('core.site_domain', TRUE);
+
+ if ($protocol == FALSE)
+ {
+ if ($site_domain === '' OR $site_domain[0] === '/')
+ {
+ // Use the configured site domain
+ $base_url = $site_domain;
+ }
+ else
+ {
+ // Guess the protocol to provide full http://domain/path URL
+ $base_url = ((empty($_SERVER['HTTPS']) OR $_SERVER['HTTPS'] === 'off') ? 'http' : 'https').'://'.$site_domain;
+ }
+ }
+ else
+ {
+ if ($site_domain === '' OR $site_domain[0] === '/')
+ {
+ // Guess the server name if the domain starts with slash
+ $port = $_SERVER['SERVER_PORT'];
+ $port = ((($port == 80) && ($protocol == 'http')) || (($port == 443) && ($protocol == 'https')) || !$port) ? '' : ":$port";
+ $base_url = $protocol.'://'.($_SERVER['SERVER_NAME']?($_SERVER['SERVER_NAME'].$port):$_SERVER['HTTP_HOST']).$site_domain;
+ }
+ else
+ {
+ // Use the configured site domain
+ $base_url = $protocol.'://'.$site_domain;
+ }
+ }
+
+ if ($index === TRUE AND $index = Kohana::config('core.index_page'))
+ {
+ // Append the index page
+ $base_url = $base_url.$index;
+ }
+
+ // Force a slash on the end of the URL
+ return rtrim($base_url, '/').'/';
+ }
+
+ /**
+ * Fetches an absolute site URL based on a URI segment.
+ *
+ * @param string site URI to convert
+ * @param string non-default protocol
+ * @return string
+ */
+ public static function site($uri = '', $protocol = FALSE)
+ {
+ if ($path = trim(parse_url($uri, PHP_URL_PATH), '/'))
+ {
+ // Add path suffix
+ $path .= Kohana::config('core.url_suffix');
+ }
+
+ if ($query = parse_url($uri, PHP_URL_QUERY))
+ {
+ // ?query=string
+ $query = '?'.$query;
+ }
+
+ if ($fragment = parse_url($uri, PHP_URL_FRAGMENT))
+ {
+ // #fragment
+ $fragment = '#'.$fragment;
+ }
+
+ // Concat the URL
+ return url::base(TRUE, $protocol).$path.$query.$fragment;
+ }
+
+ /**
+ * Return the URL to a file. Absolute filenames and relative filenames
+ * are allowed.
+ *
+ * @param string filename
+ * @param boolean include the index page
+ * @return string
+ */
+ public static function file($file, $index = FALSE)
+ {
+ if (strpos($file, '://') === FALSE)
+ {
+ // Add the base URL to the filename
+ $file = url::base($index).$file;
+ }
+
+ return $file;
+ }
+
+ /**
+ * Merges an array of arguments with the current URI and query string to
+ * overload, instead of replace, the current query string.
+ *
+ * @param array associative array of arguments
+ * @return string
+ */
+ public static function merge(array $arguments)
+ {
+ if ($_GET === $arguments)
+ {
+ $query = Router::$query_string;
+ }
+ elseif ($query = http_build_query(array_merge($_GET, $arguments)))
+ {
+ $query = '?'.$query;
+ }
+
+ // Return the current URI with the arguments merged into the query string
+ return Router::$current_uri.$query;
+ }
+
+ /**
+ * Convert a phrase to a URL-safe title.
+ *
+ * @param string phrase to convert
+ * @param string word separator (- or _)
+ * @param boolean transliterate to ASCII
+ * @return string
+ */
+ public static function title($title, $separator = '-', $ascii_only = FALSE)
+ {
+ $separator = ($separator === '-') ? '-' : '_';
+
+ if ($ascii_only === TRUE)
+ {
+ // Replace accented characters by their unaccented equivalents
+ $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));
+ }
+ else
+ {
+ // Remove all characters that are not the separator, letters, numbers, or whitespace
+ $title = preg_replace('/[^'.$separator.'\pL\pN\s]+/u', '', mb_strtolower($title));
+ }
+
+ // Replace all separator characters and whitespace by a single separator
+ $title = preg_replace('/['.$separator.'\s]+/', $separator, $title);
+
+ // Trim separators from the beginning and end
+ return trim($title, $separator);
+ }
+
+ /**
+ * Sends a page redirect header and runs the system.redirect Event.
+ *
+ * @param mixed string site URI or URL to redirect to, or array of strings if method is 300
+ * @param string HTTP method of redirect
+ * @return void
+ */
+ public static function redirect($uri = '', $method = '302')
+ {
+ if (Event::has_run('system.send_headers'))
+ {
+ return FALSE;
+ }
+
+ $codes = array
+ (
+ 'refresh' => 'Refresh',
+ '300' => 'Multiple Choices',
+ '301' => 'Moved Permanently',
+ '302' => 'Found',
+ '303' => 'See Other',
+ '304' => 'Not Modified',
+ '305' => 'Use Proxy',
+ '307' => 'Temporary Redirect'
+ );
+
+ // Validate the method and default to 302
+ $method = isset($codes[$method]) ? (string) $method : '302';
+
+ if ($method === '300')
+ {
+ $uri = (array) $uri;
+
+ $output = '<ul>';
+ foreach ($uri as $link)
+ {
+ $output .= '<li>'.html::anchor($link).'</li>';
+ }
+ $output .= '</ul>';
+
+ // The first URI will be used for the Location header
+ $uri = $uri[0];
+ }
+ else
+ {
+ $output = '<p>'.html::anchor($uri).'</p>';
+ }
+
+ // Run the redirect event
+ Event::run('system.redirect', $uri);
+
+ if (strpos($uri, '://') === FALSE)
+ {
+ // HTTP headers expect absolute URLs
+ $uri = url::site($uri, request::protocol());
+ }
+
+ if ($method === 'refresh')
+ {
+ header('Refresh: 0; url='.$uri);
+ }
+ else
+ {
+ header('HTTP/1.1 '.$method.' '.$codes[$method]);
+ header('Location: '.$uri);
+ }
+
+ // We are about to exit, so run the send_headers event
+ Event::run('system.send_headers');
+
+ exit('<h1>'.$method.' - '.$codes[$method].'</h1>'.$output);
+ }
+
+} // End url \ No newline at end of file
diff --git a/system/helpers/utf8.php b/system/helpers/utf8.php
new file mode 100644
index 0000000..0123cfb
--- /dev/null
+++ b/system/helpers/utf8.php
@@ -0,0 +1,744 @@
+<?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
+ *
+ * @package Kohana
+ * @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_Core {
+
+ /**
+ * 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
new file mode 100644
index 0000000..1f7be22
--- /dev/null
+++ b/system/helpers/valid.php
@@ -0,0 +1,453 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * The Valid Helper provides functions to help validate data. They can be used as standalone static functions or
+ * as rules for the Validation Library.
+ *
+ * ###### Validation Library Example:
+ * $data = new Validation($_POST);
+ * $data->add_rules('phone', 'required', 'valid::phone[7, 10, 11, 14]')
+ *
+ * if ($data->validate())
+ * {
+ * echo 'The phone number is valid';
+ * }
+ * else
+ * {
+ * echo Kohana::debug($data->errors());
+ * }
+ *
+ * [!!] The *valid::* part of the rule is optional, but is recommended to avoid conflicts with php functions.
+ *
+ * For more informaiton see the [Validation] Library.
+ *
+ * ###### Standalone Example:
+ * if (valid::phone($_POST['phone'], array(7, 10, 11, 14))
+ * {
+ * echo 'The phone number is valid';
+ * }
+ * else
+ * {
+ * echo 'Not valid';
+ * }
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class valid_Core {
+
+ /**
+ * Validate an email address. This method is more strict than valid::email_rfc();
+ *
+ * ###### Example:
+ * $email = 'bill@gates.com';
+ * if (valid::email($email))
+ * {
+ * echo "Valid email";
+ * }
+ * else
+ * {
+ * echo "Invalid email";
+ * }
+ *
+ * @param string A email address
+ * @return boolean
+ */
+ public static function email($email)
+ {
+ return (bool) preg_match('/^[-_a-z0-9\'+*$^&%=~!?{}]++(?:\.[-_a-z0-9\'+*$^&%=~!?{}]+)*+@(?:(?![-.])[-a-z0-9.]+(?<![-.])\.[a-z]{2,6}|\d{1,3}(?:\.\d{1,3}){3})(?::\d++)?$/iD', (string) $email);
+ }
+
+ /**
+ * Validate the domain of an email address by checking if the domain has a
+ * valid MX record.
+ *
+ * [!!] This function will always return `TRUE` if the checkdnsrr() function isn't avaliable (All Windows platforms before php 5.3)
+ *
+ * @param string email address
+ * @return boolean
+ */
+ public static function email_domain($email)
+ {
+ // If we can't prove the domain is invalid, consider it valid
+ // Note: checkdnsrr() is not implemented on Windows platforms
+ if ( ! function_exists('checkdnsrr'))
+ return TRUE;
+
+ // Check if the email domain has a valid MX record
+ return (bool) checkdnsrr(preg_replace('/^[^@]+@/', '', $email), 'MX');
+ }
+
+ /**
+ * RFC compliant email validation. This function is __LESS__ strict than [valid::email]. Choose carefully.
+ *
+ * @see Originally by Cal Henderson, modified to fit Kohana syntax standards:
+ * @see http://www.iamcal.com/publish/articles/php/parsing_email/
+ * @see http://www.w3.org/Protocols/rfc822/
+ *
+ * @param string email address
+ * @return boolean
+ */
+ public static function email_rfc($email)
+ {
+ $qtext = '[^\\x0d\\x22\\x5c\\x80-\\xff]';
+ $dtext = '[^\\x0d\\x5b-\\x5d\\x80-\\xff]';
+ $atom = '[^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-\\x3c\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+';
+ $pair = '\\x5c[\\x00-\\x7f]';
+
+ $domain_literal = "\\x5b($dtext|$pair)*\\x5d";
+ $quoted_string = "\\x22($qtext|$pair)*\\x22";
+ $sub_domain = "($atom|$domain_literal)";
+ $word = "($atom|$quoted_string)";
+ $domain = "$sub_domain(\\x2e$sub_domain)*";
+ $local_part = "$word(\\x2e$word)*";
+ $addr_spec = "$local_part\\x40$domain";
+
+ return (bool) preg_match('/^'.$addr_spec.'$/D', (string) $email);
+ }
+
+ /**
+ * Basic URL validation.
+ *
+ * ###### Example:
+ * $url = 'http://www.kohanaphp.com';
+ * if (valid::url($url))
+ * {
+ * echo "Valid url";
+ * }
+ * else
+ * {
+ * echo "Invalid url";
+ * }
+ *
+ * @param string URL
+ * @return boolean
+ */
+ public static function url($url)
+ {
+ return (bool) filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_HOST_REQUIRED);
+ }
+
+ /**
+ * Validates an IP Address. This only tests to see if the ip address is valid,
+ * it doesn't check to see if the ip address is actually in use. Has optional support for
+ * IPv6, and private ip address ranges.
+ *
+ * @param string IP address
+ * @param boolean allow IPv6 addresses
+ * @param boolean allow private IP networks
+ * @return boolean
+ */
+ public static function ip($ip, $ipv6 = FALSE, $allow_private = TRUE)
+ {
+ // By default do not allow private and reserved range IPs
+ $flags = FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE;
+ if ($allow_private === TRUE)
+ $flags = FILTER_FLAG_NO_RES_RANGE;
+
+ if ($ipv6 === TRUE)
+ return (bool) filter_var($ip, FILTER_VALIDATE_IP, $flags);
+
+ return (bool) filter_var($ip, FILTER_VALIDATE_IP, $flags | FILTER_FLAG_IPV4);
+ }
+
+ /**
+ * Validates a credit card number using the [Luhn (mod10)](http://en.wikipedia.org/wiki/Luhn_algorithm)
+ * formula.
+ *
+ * ###### Example:
+ * $cc_number = '4111111111111111';
+ * if (valid::credit_card($cc_number, array('visa', 'mastercard')))
+ * {
+ * echo "Valid number";
+ * }
+ * else
+ * {
+ * echo "Invalid number";
+ * }
+ *
+ * @param integer credit card number
+ * @param string|array card type, or an array of card types
+ * @return boolean
+ */
+ public static function credit_card($number, $type = NULL)
+ {
+ // Remove all non-digit characters from the number
+ if (($number = preg_replace('/\D+/', '', $number)) === '')
+ return FALSE;
+
+ if ($type == NULL)
+ {
+ // Use the default type
+ $type = 'default';
+ }
+ elseif (is_array($type))
+ {
+ foreach ($type as $t)
+ {
+ // Test each type for validity
+ if (valid::credit_card($number, $t))
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+
+ $cards = Kohana::config('credit_cards');
+
+ // Check card type
+ $type = strtolower($type);
+
+ if ( ! isset($cards[$type]))
+ return FALSE;
+
+ // Check card number length
+ $length = strlen($number);
+
+ // Validate the card length by the card type
+ if ( ! in_array($length, preg_split('/\D+/', $cards[$type]['length'])))
+ return FALSE;
+
+ // Check card number prefix
+ if ( ! preg_match('/^'.$cards[$type]['prefix'].'/', $number))
+ return FALSE;
+
+ // No Luhn check required
+ if ($cards[$type]['luhn'] == FALSE)
+ return TRUE;
+
+ // Checksum of the card number
+ $checksum = 0;
+
+ for ($i = $length - 1; $i >= 0; $i -= 2)
+ {
+ // Add up every 2nd digit, starting from the right
+ $checksum += substr($number, $i, 1);
+ }
+
+ for ($i = $length - 2; $i >= 0; $i -= 2)
+ {
+ // Add up every 2nd digit doubled, starting from the right
+ $double = substr($number, $i, 1) * 2;
+
+ // Subtract 9 from the double where value is greater than 10
+ $checksum += ($double >= 10) ? $double - 9 : $double;
+ }
+
+ // If the checksum is a multiple of 10, the number is valid
+ return ($checksum % 10 === 0);
+ }
+
+ /**
+ * Checks if a phone number is valid. This function will strip all non-digit
+ * characters from the phone number for testing.
+ *
+ * ###### Example:
+ * $phone_number = '(201) 664-0274';
+ * if (valid::phone($phone_number))
+ * {
+ * echo "Valid phone number";
+ * }
+ * else
+ * {
+ * echo "Invalid phone number";
+ * }
+ *
+ * @param string phone number to check
+ * @return boolean
+ */
+ public static function phone($number, $lengths = NULL)
+ {
+ if ( ! is_array($lengths))
+ {
+ $lengths = array(7,10,11);
+ }
+
+ // Remove all non-digit characters from the number
+ $number = preg_replace('/\D+/', '', $number);
+
+ // Check if the number is within range
+ return in_array(strlen($number), $lengths);
+ }
+
+ /**
+ * Tests if a string is a valid date using the php
+ * [strtotime()](http://php.net/strtotime) function
+ *
+ * @param string date to check
+ * @return boolean
+ */
+ public static function date($str)
+ {
+ return (strtotime($str) !== FALSE);
+ }
+
+ /**
+ * Checks whether a string consists of alphabetical characters only.
+ *
+ * @param string input string
+ * @param boolean trigger UTF-8 compatibility
+ * @return boolean
+ */
+ public static function alpha($str, $utf8 = FALSE)
+ {
+ return ($utf8 === TRUE)
+ ? (bool) preg_match('/^\pL++$/uD', (string) $str)
+ : ctype_alpha((string) $str);
+ }
+
+ /**
+ * Checks whether a string consists of alphabetical characters and numbers only.
+ *
+ * @param string input string
+ * @param boolean trigger UTF-8 compatibility
+ * @return boolean
+ */
+ public static function alpha_numeric($str, $utf8 = FALSE)
+ {
+ return ($utf8 === TRUE)
+ ? (bool) preg_match('/^[\pL\pN]++$/uD', (string) $str)
+ : ctype_alnum((string) $str);
+ }
+
+ /**
+ * Checks whether a string consists of alphabetical characters, numbers, underscores and dashes only.
+ *
+ * @param string input string
+ * @param boolean trigger UTF-8 compatibility
+ * @return boolean
+ */
+ public static function alpha_dash($str, $utf8 = FALSE)
+ {
+ return ($utf8 === TRUE)
+ ? (bool) preg_match('/^[-\pL\pN_]++$/uD', (string) $str)
+ : (bool) preg_match('/^[-a-z0-9_]++$/iD', (string) $str);
+ }
+
+ /**
+ * Checks whether a string consists of digits only (no dots or dashes).
+ *
+ * @param string input string
+ * @param boolean trigger UTF-8 compatibility
+ * @return boolean
+ */
+ public static function digit($str, $utf8 = FALSE)
+ {
+ return ($utf8 === TRUE)
+ ? (bool) preg_match('/^\pN++$/uD', (string) $str)
+ : ctype_digit((string) $str);
+ }
+
+ /**
+ * Checks whether a string is a valid number (negative and decimal numbers allowed).
+ * This function uses [localeconv()](http://www.php.net/manual/en/function.localeconv.php)
+ * to support international number formats.
+ *
+ * @param string input string
+ * @return boolean
+ */
+ public static function numeric($str)
+ {
+ // Use localeconv to set the decimal_point value: Usually a comma or period.
+ $locale = localeconv();
+ return (bool) preg_match('/^-?[0-9'.$locale['decimal_point'].']++$/D', (string) $str);
+ }
+
+ /**
+ * Tests if an integer is within a range.
+ *
+ * @param integer number to check
+ * @param array valid range of input
+ * @return boolean
+ */
+ public static function range($number, array $range)
+ {
+ // 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;
+ }
+
+ /**
+ * Checks if a string is a proper decimal format. The format array can be
+ * used to specify a decimal length, or a number and decimal length, eg:
+ * array(2) would force the number to have 2 decimal places, array(4,2)
+ * would force the number to have 4 digits and 2 decimal places.
+ *
+ * ###### Example:
+ * $decimal = '4.5';
+ * if (valid::decimal($decimal, array(2,1)))
+ * {
+ * echo "Valid decimal";
+ * }
+ * else
+ * {
+ * echo "Invalid decimal";
+ * }
+ *
+ * Output: Invalid decimal
+ *
+ * @param string input string
+ * @param array decimal format: y or x,y
+ * @return boolean
+ */
+ public static function decimal($str, $format = NULL)
+ {
+ // Create the pattern
+ $pattern = '/^[0-9]%s\.[0-9]%s$/';
+
+ if ( ! empty($format))
+ {
+ if (count($format) > 1)
+ {
+ // Use the format for number and decimal length
+ $pattern = sprintf($pattern, '{'.$format[0].'}', '{'.$format[1].'}');
+ }
+ elseif (count($format) > 0)
+ {
+ // Use the format as decimal length
+ $pattern = sprintf($pattern, '+', '{'.$format[0].'}');
+ }
+ }
+ else
+ {
+ // No format
+ $pattern = sprintf($pattern, '+', '+');
+ }
+
+ 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/libraries/Cache.php b/system/libraries/Cache.php
new file mode 100644
index 0000000..c7954d3
--- /dev/null
+++ b/system/libraries/Cache.php
@@ -0,0 +1,248 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Provides a driver-based interface for finding, creating, and deleting cached
+ * resources. Caches are identified by a unique string. Tagging of caches is
+ * also supported, and caches can be found and deleted by id or tag.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Cache_Core {
+
+ protected static $instances = array();
+
+ // Configuration
+ protected $config;
+
+ // Driver object
+ protected $driver;
+
+ /**
+ * Returns a singleton instance of Cache.
+ *
+ * @param string configuration
+ * @return Cache_Core
+ */
+ public static function & instance($config = FALSE)
+ {
+ if ( ! isset(Cache::$instances[$config]))
+ {
+ // Create a new instance
+ Cache::$instances[$config] = new Cache($config);
+ }
+
+ return Cache::$instances[$config];
+ }
+
+ /**
+ * Loads the configured driver and validates it.
+ *
+ * @param array|string custom configuration or config group name
+ * @return void
+ */
+ public function __construct($config = FALSE)
+ {
+ if (is_string($config))
+ {
+ $name = $config;
+
+ // Test the config group name
+ if (($config = Kohana::config('cache.'.$config)) === NULL)
+ throw new Cache_Exception('The :group: group is not defined in your configuration.', array(':group:' => $name));
+ }
+
+ if (is_array($config))
+ {
+ // Append the default configuration options
+ $config += Kohana::config('cache.default');
+ }
+ else
+ {
+ // Load the default group
+ $config = Kohana::config('cache.default');
+ }
+
+ // Cache the config in the object
+ $this->config = $config;
+
+ // Set driver name
+ $driver = 'Cache_'.ucfirst($this->config['driver']).'_Driver';
+
+ // Load the driver
+ if ( ! Kohana::auto_load($driver))
+ throw new Cache_Exception('The :driver: driver for the :class: library could not be found',
+ array(':driver:' => $this->config['driver'], ':class:' => get_class($this)));
+
+ // Initialize the driver
+ $this->driver = new $driver($this->config['params']);
+
+ // Validate the driver
+ if ( ! ($this->driver instanceof Cache_Driver))
+ throw new Cache_Exception('The :driver: driver for the :library: library must implement the :interface: interface',
+ array(':driver:' => $this->config['driver'], ':library:' => get_class($this), ':interface:' => 'Cache_Driver'));
+
+ Kohana_Log::add('debug', 'Cache Library initialized');
+ }
+
+ /**
+ * Set cache items
+ */
+ public function set($key, $value = NULL, $tags = NULL, $lifetime = NULL)
+ {
+ if ($lifetime === NULL)
+ {
+ $lifetime = $this->config['lifetime'];
+ }
+
+ if ( ! is_array($key))
+ {
+ $key = array($key => $value);
+ }
+
+ if ($this->config['prefix'] !== NULL)
+ {
+ $key = $this->add_prefix($key);
+
+ if ($tags !== NULL)
+ {
+ $tags = $this->add_prefix($tags, FALSE);
+ }
+ }
+
+ return $this->driver->set($key, $tags, $lifetime);
+ }
+
+ /**
+ * Get a cache items by key
+ */
+ public function get($keys)
+ {
+ $single = FALSE;
+
+ if ( ! is_array($keys))
+ {
+ $keys = array($keys);
+ $single = TRUE;
+ }
+
+ if ($this->config['prefix'] !== NULL)
+ {
+ $keys = $this->add_prefix($keys, FALSE);
+
+ if ( ! $single)
+ {
+ return $this->strip_prefix($this->driver->get($keys, $single));
+ }
+
+ }
+
+ return $this->driver->get($keys, $single);
+ }
+
+ /**
+ * Get cache items by tags
+ */
+ public function get_tag($tags)
+ {
+ if ( ! is_array($tags))
+ {
+ $tags = array($tags);
+ }
+
+ if ($this->config['prefix'] !== NULL)
+ {
+ $tags = $this->add_prefix($tags, FALSE);
+ return $this->strip_prefix($this->driver->get_tag($tags));
+ }
+ else
+ {
+ return $this->driver->get_tag($tags);
+ }
+ }
+
+ /**
+ * Delete cache item by key
+ */
+ public function delete($keys)
+ {
+ if ( ! is_array($keys))
+ {
+ $keys = array($keys);
+ }
+
+ if ($this->config['prefix'] !== NULL)
+ {
+ $keys = $this->add_prefix($keys, FALSE);
+ }
+
+ return $this->driver->delete($keys);
+ }
+
+ /**
+ * Delete cache items by tag
+ */
+ public function delete_tag($tags)
+ {
+ if ( ! is_array($tags))
+ {
+ $tags = array($tags);
+ }
+
+ if ($this->config['prefix'] !== NULL)
+ {
+ $tags = $this->add_prefix($tags, FALSE);
+ }
+
+ return $this->driver->delete_tag($tags);
+ }
+
+ /**
+ * Empty the cache
+ */
+ public function delete_all()
+ {
+ return $this->driver->delete_all();
+ }
+
+ /**
+ * Add a prefix to keys or tags
+ */
+ protected function add_prefix($array, $to_key = TRUE)
+ {
+ $out = array();
+
+ foreach($array as $key => $value)
+ {
+ if ($to_key)
+ {
+ $out[$this->config['prefix'].$key] = $value;
+ }
+ else
+ {
+ $out[$key] = $this->config['prefix'].$value;
+ }
+ }
+
+ return $out;
+ }
+
+ /**
+ * Strip a prefix to keys or tags
+ */
+ protected function strip_prefix($array)
+ {
+ $out = array();
+
+ $start = strlen($this->config['prefix']);
+
+ foreach($array as $key => $value)
+ {
+ $out[substr($key, $start)] = $value;
+ }
+
+ return $out;
+ }
+
+} // End Cache Library \ No newline at end of file
diff --git a/system/libraries/Cache_Exception.php b/system/libraries/Cache_Exception.php
new file mode 100644
index 0000000..706dc09
--- /dev/null
+++ b/system/libraries/Cache_Exception.php
@@ -0,0 +1,11 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+
+class Cache_Exception_Core extends Kohana_Exception {}
+// End Kohana User Exception
diff --git a/system/libraries/Controller.php b/system/libraries/Controller.php
new file mode 100644
index 0000000..a1139f6
--- /dev/null
+++ b/system/libraries/Controller.php
@@ -0,0 +1,44 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Kohana Controller class. The controller class must be extended to work
+ * properly, so this class is defined as abstract.
+ *
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+abstract class Controller_Core {
+
+ // Allow all controllers to run in production by default
+ const ALLOW_PRODUCTION = TRUE;
+
+ /**
+ * Loads URI, and Input into this controller.
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ if (Kohana::$instance == NULL)
+ {
+ // Set the instance to the first controller loaded
+ Kohana::$instance = $this;
+ }
+ }
+
+ /**
+ * Handles methods that do not exist.
+ *
+ * @param string method name
+ * @param array arguments
+ * @return void
+ */
+ public function __call($method, $args)
+ {
+ // Default to showing a 404 page
+ Event::run('system.404');
+ }
+
+} // End Controller Class \ No newline at end of file
diff --git a/system/libraries/Database.php b/system/libraries/Database.php
new file mode 100644
index 0000000..253bb15
--- /dev/null
+++ b/system/libraries/Database.php
@@ -0,0 +1,648 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Database wrapper.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2008-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+abstract class Database_Core {
+
+ const SELECT = 1;
+ const INSERT = 2;
+ const UPDATE = 3;
+ const DELETE = 4;
+ const CROSS_REQUEST = 5;
+ const PER_REQUEST = 6;
+
+ protected static $instances = array();
+
+ // Global benchmarks
+ public static $benchmarks = array();
+
+ // Last execute query
+ protected $last_query;
+
+ // Configuration array
+ protected $config;
+
+ // Required configuration keys
+ protected $config_required = array();
+
+ // Raw server connection
+ protected $connection;
+
+ // Cache (Cache object for cross-request, array for per-request)
+ protected $cache;
+
+ // Quote character to use for identifiers (tables/columns/aliases)
+ protected $quote = '"';
+
+ /**
+ * Returns a singleton instance of Database.
+ *
+ * @param string Database name
+ * @return Database_Core
+ */
+ public static function instance($name = 'default')
+ {
+ if ( ! isset(Database::$instances[$name]))
+ {
+ // Load the configuration for this database group
+ $config = Kohana::config('database.'.$name);
+
+ if (is_string($config['connection']))
+ {
+ // Parse the DSN into connection array
+ $config['connection'] = Database::parse_dsn($config['connection']);
+ }
+
+ // Set the driver class name
+ $driver = 'Database_'.ucfirst($config['connection']['type']);
+
+ // Create the database connection instance
+ Database::$instances[$name] = new $driver($config);
+ }
+
+ return Database::$instances[$name];
+ }
+
+ /**
+ * Constructs a new Database object
+ *
+ * @param array Database config array
+ * @return Database_Core
+ */
+ protected function __construct(array $config)
+ {
+ // Store the config locally
+ $this->config = $config;
+
+ if ($this->config['cache'] !== FALSE)
+ {
+ if (is_string($this->config['cache']))
+ {
+ // Use Cache library
+ $this->cache = new Cache($this->config['cache']);
+ }
+ elseif ($this->config['cache'] === TRUE)
+ {
+ // Use array
+ $this->cache = array();
+ }
+ }
+ }
+
+ public function __destruct()
+ {
+ $this->disconnect();
+ }
+
+ /**
+ * Connects to the database
+ *
+ * @return void
+ */
+ abstract public function connect();
+
+ /**
+ * Disconnects from the database
+ *
+ * @return void
+ */
+ abstract public function disconnect();
+
+ /**
+ * Sets the character set
+ *
+ * @return void
+ */
+ abstract public function set_charset($charset);
+
+ /**
+ * Executes the query
+ *
+ * @param string SQL
+ * @return Database_Result
+ */
+ abstract public function query_execute($sql);
+
+ /**
+ * Escapes the given value
+ *
+ * @param mixed Value
+ * @return mixed Escaped value
+ */
+ abstract public function escape($value);
+
+ /**
+ * List constraints for the given table
+ *
+ * @param string Table name
+ * @return array
+ */
+ abstract public function list_constraints($table);
+
+ /**
+ * List fields for the given table
+ *
+ * @param string Table name
+ * @return array
+ */
+ abstract public function list_fields($table);
+
+ /**
+ * List tables for the given connection (checks for prefix)
+ *
+ * @return array
+ */
+ abstract public function list_tables();
+
+ /**
+ * Converts the given DSN string to an array of database connection components
+ *
+ * @param string DSN string
+ * @return array
+ */
+ public static function parse_dsn($dsn)
+ {
+ $db = array
+ (
+ 'type' => FALSE,
+ 'user' => FALSE,
+ 'pass' => FALSE,
+ 'host' => FALSE,
+ 'port' => FALSE,
+ 'socket' => FALSE,
+ 'database' => FALSE
+ );
+
+ // Get the protocol and arguments
+ list ($db['type'], $connection) = explode('://', $dsn, 2);
+
+ if ($connection[0] === '/')
+ {
+ // Strip leading slash
+ $db['database'] = substr($connection, 1);
+ }
+ else
+ {
+ $connection = parse_url('http://'.$connection);
+
+ if (isset($connection['user']))
+ {
+ $db['user'] = $connection['user'];
+ }
+
+ if (isset($connection['pass']))
+ {
+ $db['pass'] = $connection['pass'];
+ }
+
+ if (isset($connection['port']))
+ {
+ $db['port'] = $connection['port'];
+ }
+
+ if (isset($connection['host']))
+ {
+ if ($connection['host'] === 'unix(')
+ {
+ list($db['socket'], $connection['path']) = explode(')', $connection['path'], 2);
+ }
+ else
+ {
+ $db['host'] = $connection['host'];
+ }
+ }
+
+ if (isset($connection['path']) AND $connection['path'])
+ {
+ // Strip leading slash
+ $db['database'] = substr($connection['path'], 1);
+ }
+ }
+
+ return $db;
+ }
+
+ /**
+ * Returns the last executed query for this database
+ *
+ * @return string
+ */
+ public function last_query()
+ {
+ return $this->last_query;
+ }
+
+ /**
+ * Executes the given query, returning the cached version if enabled
+ *
+ * @param string SQL query
+ * @return Database_Result
+ */
+ public function query($sql)
+ {
+ // Start the benchmark
+ $start = microtime(TRUE);
+
+ if (is_array($this->cache))
+ {
+ $hash = $this->query_hash($sql);
+
+ if (isset($this->cache[$hash]))
+ {
+ // Use cached result
+ $result = $this->cache[$hash];
+
+ // It's from cache
+ $sql .= ' [CACHE]';
+ }
+ else
+ {
+ // No cache, execute query and store in cache
+ $result = $this->cache[$hash] = $this->query_execute($sql);
+ }
+ }
+ else
+ {
+ // Execute the query, cache is off
+ $result = $this->query_execute($sql);
+ }
+
+ // Stop the benchmark
+ $stop = microtime(TRUE);
+
+ if ($this->config['benchmark'] === TRUE)
+ {
+ // Benchmark the query
+ Database::$benchmarks[] = array('query' => $sql, 'time' => $stop - $start, 'rows' => count($result));
+ }
+
+ return $result;
+ }
+
+ /**
+ * Performs the query on the cache (and caches it if it's not found)
+ *
+ * @param string query
+ * @param int time-to-live (NULL for Cache default)
+ * @return Database_Cache_Result
+ */
+ public function query_cache($sql, $ttl)
+ {
+ if ( ! $this->cache instanceof Cache)
+ {
+ throw new Database_Exception('Database :name has not been configured to use the Cache library.');
+ }
+
+ // Start the benchmark
+ $start = microtime(TRUE);
+
+ $hash = $this->query_hash($sql);
+
+ if (($data = $this->cache->get($hash)) !== NULL)
+ {
+ // Found in cache, create result
+ $result = new Database_Cache_Result($data, $sql, $this->config['object']);
+
+ // It's from the cache
+ $sql .= ' [CACHE]';
+ }
+ else
+ {
+ // Run the query and return the full array of rows
+ $data = $this->query_execute($sql)->as_array(TRUE);
+
+ // Set the Cache
+ $this->cache->set($hash, $data, NULL, $ttl);
+
+ // Create result
+ $result = new Database_Cache_Result($data, $sql, $this->config['object']);
+ }
+
+ // Stop the benchmark
+ $stop = microtime(TRUE);
+
+ if ($this->config['benchmark'] === TRUE)
+ {
+ // Benchmark the query
+ Database::$benchmarks[] = array('query' => $sql, 'time' => $stop - $start, 'rows' => count($result));
+ }
+
+ return $result;
+ }
+
+ /**
+ * Generates a hash for the given query
+ *
+ * @param string SQL query string
+ * @return string
+ */
+ protected function query_hash($sql)
+ {
+ return sha1(str_replace("\n", ' ', trim($sql)));
+ }
+
+ /**
+ * Clears the internal query cache.
+ *
+ * @param mixed clear cache by SQL statement, NULL for all, or TRUE for last query
+ * @param integer Type of cache to clear, Database::CROSS_REQUEST or Database::PER_REQUEST
+ * @return Database
+ */
+ public function clear_cache($sql = NULL, $type = NULL)
+ {
+ if ($this->cache instanceof Cache AND ($type == NULL OR $type == Database::CROSS_REQUEST))
+ {
+ // Using cross-request Cache library
+ if ($sql === TRUE)
+ {
+ $this->cache->delete($this->query_hash($this->last_query));
+ }
+ elseif (is_string($sql))
+ {
+ $this->cache->delete($this->query_hash($sql));
+ }
+ else
+ {
+ $this->cache->delete_all();
+ }
+ }
+ elseif (is_array($this->cache) AND ($type == NULL OR $type == Database::PER_REQUEST))
+ {
+ // Using per-request memory cache
+ if ($sql === TRUE)
+ {
+ unset($this->cache[$this->query_hash($this->last_query)]);
+ }
+ elseif (is_string($sql))
+ {
+ unset($this->cache[$this->query_hash($sql)]);
+ }
+ else
+ {
+ $this->cache = array();
+ }
+ }
+ }
+
+ /**
+ * Quotes the given value
+ *
+ * @param mixed value
+ * @return mixed
+ */
+ public function quote($value)
+ {
+ if ( ! $this->config['escape'])
+ return $value;
+
+ if ($value === NULL)
+ {
+ return 'NULL';
+ }
+ elseif ($value === TRUE)
+ {
+ return 'TRUE';
+ }
+ elseif ($value === FALSE)
+ {
+ return 'FALSE';
+ }
+ elseif (is_int($value))
+ {
+ return (int) $value;
+ }
+ elseif ($value instanceof Database_Expression)
+ {
+ return (string) $value;
+ }
+ elseif (is_float($value))
+ {
+ // Convert to non-locale aware float to prevent possible commas
+ return sprintf('%F', $value);
+ }
+
+ return '\''.$this->escape($value).'\'';
+ }
+
+ /**
+ * Quotes a table, adding the table prefix
+ * Reserved characters not allowed in table names for the builder are [ .*] (space, dot, asterisk)
+ *
+ * @param string|array table name or array - 'users u' or array('u' => 'users') both valid
+ * @param string table alias
+ * @return string
+ */
+ public function quote_table($table, $alias = NULL)
+ {
+ if (is_array($table))
+ {
+ // Using array('u' => 'user')
+ list($alias, $table) = each($table);
+ }
+ elseif (strpos(' ', $table) !== FALSE)
+ {
+ // Using format 'user u'
+ list($table, $alias) = explode(' ', $table);
+ }
+
+ if ($table instanceof Database_Expression)
+ {
+ if ($alias)
+ {
+ if ($this->config['escape'])
+ {
+ $alias = $this->quote.$alias.$this->quote;
+ }
+
+ return $table.' AS '.$alias;
+ }
+
+ return (string) $table;
+ }
+
+ if ($this->config['table_prefix'])
+ {
+ $table = $this->config['table_prefix'].$table;
+ }
+
+ if ($alias)
+ {
+ if ($this->config['escape'])
+ {
+ $table = $this->quote.$table.$this->quote;
+ $alias = $this->quote.$alias.$this->quote;
+ }
+
+ return $table.' AS '.$alias;
+ }
+
+ if ($this->config['escape'])
+ {
+ $table = $this->quote.$table.$this->quote;
+ }
+
+ return $table;
+ }
+
+ /**
+ * Quotes column or table.column, adding the table prefix if necessary
+ * Reserved characters not allowed in table names for the builder are [ .*] (space, dot, asterisk)
+ * Complex column names must have table/columns in double quotes, e.g. array('mycount' => 'COUNT("users.id")')
+ *
+ * @param string|array column name or array('u' => 'COUNT("*")')
+ * @param string column alias
+ * @return string
+ */
+ public function quote_column($column, $alias = NULL)
+ {
+ if ($column === '*')
+ return $column;
+
+ if (is_array($column))
+ {
+ list($alias, $column) = each($column);
+ }
+
+ if ($column instanceof Database_Expression)
+ {
+ if ($alias)
+ {
+ if ($this->config['escape'])
+ {
+ $alias = $this->quote.$alias.$this->quote;
+ }
+
+ return $column.' AS '.$alias;
+ }
+
+ return (string) $column;
+ }
+
+ if ($this->config['table_prefix'] AND strpos($column, '.') !== FALSE)
+ {
+ if (strpos($column, '"') !== FALSE)
+ {
+ // Find "table.column" and replace them with "[prefix]table.column"
+ $column = preg_replace('/"([^.]++)\.([^"]++)"/', '"'.$this->config['table_prefix'].'$1.$2"', $column);
+ }
+ else
+ {
+ // Attach table prefix if table.column format
+ $column = $this->config['table_prefix'].$column;
+ }
+ }
+
+ if ($this->config['escape'])
+ {
+ if (strpos($column, '"') === FALSE)
+ {
+ // Quote the column
+ $column = $this->quote.$column.$this->quote;
+ }
+ elseif ($this->quote !== '"')
+ {
+ // Replace double quotes
+ $column = str_replace('"', $this->quote, $column);
+ }
+
+ // Replace . with "."
+ $column = str_replace('.', $this->quote.'.'.$this->quote, $column);
+
+ // Unescape any asterisks
+ $column = str_replace($this->quote.'*'.$this->quote, '*', $column);
+
+ if ($alias)
+ {
+ // Quote the alias
+ return $column.' AS '.$this->quote.$alias.$this->quote;
+ }
+
+ return $column;
+ }
+
+ // Strip double quotes
+ $column = str_replace('"', '', $column);
+
+ if ($alias)
+ return $column.' AS '.$alias;
+
+ return $column;
+ }
+
+ /**
+ * Get the table prefix
+ *
+ * @param string Optional new table prefix to set
+ * @return string
+ */
+ public function table_prefix($new_prefix = NULL)
+ {
+ $prefix = $this->config['table_prefix'];
+
+ if ($new_prefix !== NULL)
+ {
+ // Set a new prefix
+ $this->config['table_prefix'] = $new_prefix;
+ }
+
+ return $prefix;
+ }
+
+ /**
+ * Fetches SQL type information about a field, in a generic format.
+ *
+ * @param string field datatype
+ * @return array
+ */
+ protected function sql_type($str)
+ {
+ static $sql_types;
+
+ if ($sql_types === NULL)
+ {
+ // Load SQL data types
+ $sql_types = Kohana::config('sql_types');
+ }
+
+ $str = trim($str);
+
+ if (($open = strpos($str, '(')) !== FALSE)
+ {
+ // Closing bracket
+ $close = strpos($str, ')', $open);
+
+ // Length without brackets
+ $length = substr($str, $open + 1, $close - 1 - $open);
+
+ // Type without the length
+ $type = substr($str, 0, $open).substr($str, $close + 1);
+ }
+ else
+ {
+ // No length
+ $type = $str;
+ }
+
+ if (empty($sql_types[$type]))
+ throw new Database_Exception('Undefined field type :type', array(':type' => $str));
+
+ // Fetch the field definition
+ $field = $sql_types[$type];
+
+ $field['sql_type'] = $type;
+
+ if (isset($length))
+ {
+ // Add the length to the field info
+ $field['length'] = $length;
+ }
+
+ return $field;
+ }
+
+} // End Database
diff --git a/system/libraries/Database_Builder.php b/system/libraries/Database_Builder.php
new file mode 100644
index 0000000..e86ce37
--- /dev/null
+++ b/system/libraries/Database_Builder.php
@@ -0,0 +1,1241 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * The Database Query Builder provides methods for creating database agnostic queries and
+ * data manipulation.
+ *
+ * ##### A basic select query
+ *
+ * $builder = new Database_Builder;
+ * $kohana = $builder
+ * ->select()
+ * ->where('name', '=', 'Kohana')
+ * ->from('frameworks')
+ * ->execute();
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2008-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Database_Builder_Core {
+
+ // Valid ORDER BY directions
+ protected $order_directions = array('ASC', 'DESC', 'RAND()');
+
+ // Database object
+ protected $db;
+
+ // Builder members
+ protected $select = array();
+ protected $from = array();
+ protected $join = array();
+ protected $where = array();
+ protected $group_by = array();
+ protected $having = array();
+ protected $order_by = array();
+ protected $limit = NULL;
+ protected $offset = NULL;
+ protected $set = array();
+ protected $columns = array();
+ protected $values = array();
+ protected $type;
+ protected $distinct = FALSE;
+ protected $reset = TRUE;
+
+ // TTL for caching (using Cache library)
+ protected $ttl = FALSE;
+
+ public function __construct($db = 'default')
+ {
+ $this->db = $db;
+ }
+
+ /**
+ * Compiles the builder object into a SQL query. Useful for debugging
+ *
+ * ##### Example
+ *
+ * echo $builder->select()->from('products');
+ * // Output: SELECT * FROM `products`
+ *
+ * @return string Compiled query
+ */
+ public function __toString()
+ {
+ return $this->compile();
+ }
+
+ /**
+ * Creates a `SELECT` query with support for column aliases, database functions,
+ * subqueries or a [Database_Expression]
+ *
+ * ##### Examples
+ *
+ * // Simple select
+ * echo $builder->select()->from('products');
+ *
+ * // Select with database function
+ * echo $builder->select(array('records_found' => 'COUNT("*")'))->from('products');
+ *
+ * // Select with sub query
+ * echo $builder->select(array('field', 'test' => db::select('test')->from('table')))->from('products');
+ *
+ * @chainable
+ * @param string|array column name or array(alias => column)
+ * @return Database_Builder
+ */
+ public function select($columns = NULL)
+ {
+ $this->type = Database::SELECT;
+
+ if ($columns === NULL)
+ {
+ $columns = array('*');
+ }
+ elseif ( ! is_array($columns))
+ {
+ $columns = func_get_args();
+ }
+
+ $this->select = array_merge($this->select, $columns);
+
+ return $this;
+ }
+
+ /**
+ * Creates a `DISTINCT SELECT` query. For more information see see [Database_Builder::select].
+ *
+ * @chainable
+ * @param string|array column name or array(alias => column)
+ * @return Database_Builder
+ */
+ public function select_distinct($columns = NULL)
+ {
+ $this->select($columns);
+ $this->distinct = TRUE;
+ return $this;
+ }
+
+ /**
+ * Add tables to the FROM portion of the builder
+ *
+ * ##### Example
+ *
+ * $builder->select()->from('products')
+ * ->from(array('other' => 'other_table'));
+ * // Output: SELECT * FROM `products`, `other_table` AS `other`
+ *
+ * @chainable
+ * @param string|array table name or array(alias => table)
+ * @return Database_Builder
+ */
+ public function from($tables)
+ {
+ if ( ! is_array($tables))
+ {
+ $tables = func_get_args();
+ }
+
+ $this->from = array_merge($this->from, $tables);
+
+ return $this;
+ }
+
+ /**
+ * Add conditions to the `WHERE` clause. Alias for [Database_Builder::and_where].
+ *
+ * @chainable
+ * @param mixed Column name or array of columns => vals
+ * @param string Operation to perform
+ * @param mixed Value
+ * @return Database_Builder
+ */
+ public function where($columns, $op = '=', $value = NULL)
+ {
+ return $this->and_where($columns, $op, $value);
+ }
+
+ /**
+ * Add conditions to the `WHERE` clause separating multiple conditions with `AND`.
+ * This function supports all `WHERE` operators including `LIKE` and `IN`. It can
+ * also be used with a [Database_Expression] or subquery.
+ *
+ * ##### Examples
+ *
+ * // Basic where condition
+ * $builder->where('field', '=', 'value');
+ *
+ * // Multiple conditions with an array (you can also chain where() function calls)
+ * $builder->where(array(array('field', '=', 'value'), array(...)));
+ *
+ * // With a database expression
+ * $builder->where('field', '=', db::expr('field + 1'));
+ * // or a function
+ * $builder->where('field', '=', db::expr('UNIX_TIMESTAMP()'));
+ *
+ * // With a subquery
+ * $builder->where('field', 'IN', db::select('id')->from('table'));
+ *
+ * [!!] You must manually escape all data you pass into a database expression!
+ *
+ * @chainable
+ * @param mixed Column name or array of triplets
+ * @param string Operation to perform
+ * @param mixed Value
+ * @return Database_Builder
+ */
+ public function and_where($columns, $op = '=', $value = NULL)
+ {
+ if (is_array($columns))
+ {
+ foreach ($columns as $column)
+ {
+ $this->where[] = array('AND' => $column);
+ }
+ }
+ else
+ {
+ $this->where[] = array('AND' => array($columns, $op, $value));
+ }
+ return $this;
+ }
+
+ /**
+ * Add conditions to the `WHERE` clause separating multiple conditions with `OR`.
+ * For more information about building a `WHERE` clause see [Database_Builder::and_where]
+ *
+ * @chainable
+ * @param mixed Column name or array of triplets
+ * @param string Operation to perform
+ * @param mixed Value
+ * @return Database_Builder
+ */
+ public function or_where($columns, $op = '=', $value = NULL)
+ {
+ if (is_array($columns))
+ {
+ foreach ($columns as $column)
+ {
+ $this->where[] = array('OR' => $column);
+ }
+ }
+ else
+ {
+ $this->where[] = array('OR' => array($columns, $op, $value));
+ }
+ return $this;
+ }
+
+ /**
+ * Join tables to the builder
+ *
+ * ##### Example
+ *
+ * // Basic join
+ * db::select()->from('products')
+ * ->join('reviews', 'reviews.product_id', 'products.id');
+ *
+ * // Advanced joins
+ * echo db::select()->from('products')
+ * ->join('reviews', 'field', db::expr('advanced condition here'), 'RIGHT');
+ *
+ * @chainable
+ * @param mixed Table name
+ * @param mixed Key, or an array of key => value pair, for join condition (can be a Database_Expression)
+ * @param mixed Value if $keys is not an array or Database_Expression
+ * @param string Join type (LEFT, RIGHT, INNER, etc.)
+ * @return Database_Builder
+ */
+ public function join($table, $keys, $value = NULL, $type = NULL)
+ {
+ if (is_string($keys))
+ {
+ $keys = array($keys => $value);
+ }
+
+ if ($type !== NULL)
+ {
+ $type = strtoupper($type);
+ }
+
+ $this->join[] = array($table, $keys, $type);
+
+ return $this;
+ }
+
+ /**
+ * This function is an alias for [Database_Builder::join]
+ * with the join type set to `LEFT`.
+ *
+ * @chainable
+ * @param mixed Table name
+ * @param mixed Key, or an array of key => value pair, for join condition (can be a Database_Expression)
+ * @param mixed Value if $keys is not an array or Database_Expression
+ * @return Database_Builder
+ */
+ public function left_join($table, $keys, $value = NULL)
+ {
+ return $this->join($table, $keys, $value, 'LEFT');
+ }
+
+ /**
+ * This function is an alias for [Database_Builder::join]
+ * with the join type set to `RIGHT`.
+ *
+ * @chainable
+ * @param mixed Table name
+ * @param mixed Key, or an array of key => value pair, for join condition (can be a Database_Expression)
+ * @param mixed Value if $keys is not an array or Database_Expression
+ * @return Database_Builder
+ */
+ public function right_join($table, $keys, $value = NULL)
+ {
+ return $this->join($table, $keys, $value, 'RIGHT');
+ }
+
+ /**
+ * This function is an alias for [Database_Builder::join]
+ * with the join type set to `INNER`.
+ *
+ * @chainable
+ * @param mixed Table name
+ * @param mixed Key, or an array of key => value pair, for join condition (can be a Database_Expression)
+ * @param mixed Value if $keys is not an array or Database_Expression
+ * @return Database_Builder
+ */
+ public function inner_join($table, $keys, $value = NULL)
+ {
+ return $this->join($table, $keys, $value, 'INNER');
+ }
+
+ /**
+ * This function is an alias for [Database_Builder::join]
+ * with the join type set to `OUTER`.
+ *
+ * @chainable
+ * @param mixed Table name
+ * @param mixed Key, or an array of key => value pair, for join condition (can be a Database_Expression)
+ * @param mixed Value if $keys is not an array or Database_Expression
+ * @return Database_Builder
+ */
+ public function outer_join($table, $keys, $value = NULL)
+ {
+ return $this->join($table, $keys, $value, 'OUTER');
+ }
+
+ /**
+ * This function is an alias for [Database_Builder::join]
+ * with the join type set to `FULL`.
+ *
+ * @chainable
+ * @param mixed Table name
+ * @param mixed Key, or an array of key => value pair, for join condition (can be a Database_Expression)
+ * @param mixed Value if $keys is not an array or Database_Expression
+ * @return Database_Builder
+ */
+ public function full_join($table, $keys, $value = NULL)
+ {
+ return $this->join($table, $keys, $value, 'FULL');
+ }
+
+ /**
+ * This function is an alias for [Database_Builder::join]
+ * with the join type set to `LEFT INNER`.
+ *
+ * @chainable
+ * @param mixed Table name
+ * @param mixed Key, or an array of key => value pair, for join condition (can be a Database_Expression)
+ * @param mixed Value if $keys is not an array or Database_Expression
+ * @return Database_Builder
+ */
+ public function left_inner_join($table, $keys, $value = NULL)
+ {
+ return $this->join($table, $keys, $value, 'LEFT INNER');
+ }
+
+ /**
+ * This function is an alias for [Database_Builder::join]
+ * with the join type set to `RIGHT INNER`.
+ *
+ * @chainable
+ * @param mixed Table name
+ * @param mixed Key, or an array of key => value pair, for join condition (can be a Database_Expression)
+ * @param mixed Value if $keys is not an array or Database_Expression
+ * @return Database_Builder
+ */
+ public function right_inner_join($table, $keys, $value = NULL)
+ {
+ return $this->join($table, $keys, $value, 'RIGHT INNER');
+ }
+
+ /**
+ * Add fields to the GROUP BY portion
+ *
+ * ##### Example
+ *
+ * db::select()->from('products')
+ * ->group_by(array('name', 'cat_id'));
+ * // Output: SELECT * FROM `products` GROUP BY `name`, `cat_id`
+ *
+ * @chainable
+ * @param mixed Field names or an array of fields
+ * @return Database_Builder
+ */
+ public function group_by($columns)
+ {
+ if ( ! is_array($columns))
+ {
+ $columns = func_get_args();
+ }
+
+ $this->group_by = array_merge($this->group_by, $columns);
+
+ return $this;
+ }
+
+ /**
+ * Add conditions to the HAVING clause (AND)
+ *
+ * @chainable
+ * @param mixed Column name or array of columns => vals
+ * @param string Operation to perform
+ * @param mixed Value
+ * @return Database_Builder
+ */
+ public function having($columns, $op = '=', $value = NULL)
+ {
+ return $this->and_having($columns, $op, $value);
+ }
+
+ /**
+ * Add conditions to the HAVING clause (AND)
+ *
+ * @chainable
+ * @param mixed Column name or array of triplets
+ * @param string Operation to perform
+ * @param mixed Value
+ * @return Database_Builder
+ */
+ public function and_having($columns, $op = '=', $value = NULL)
+ {
+ if (is_array($columns))
+ {
+ foreach ($columns as $column)
+ {
+ $this->having[] = array('AND' => $column);
+ }
+ }
+ else
+ {
+ $this->having[] = array('AND' => array($columns, $op, $value));
+ }
+ return $this;
+ }
+
+ /**
+ * Add conditions to the HAVING clause (OR)
+ *
+ * @chainable
+ * @param mixed Column name or array of triplets
+ * @param string Operation to perform
+ * @param mixed Value
+ * @return Database_Builder
+ */
+ public function or_having($columns, $op = '=', $value = NULL)
+ {
+ if (is_array($columns))
+ {
+ foreach ($columns as $column)
+ {
+ $this->having[] = array('OR' => $column);
+ }
+ }
+ else
+ {
+ $this->having[] = array('OR' => array($columns, $op, $value));
+ }
+ return $this;
+ }
+
+ /**
+ * Add fields to the ORDER BY portion
+ *
+ * @chainable
+ * @param mixed Field names or an array of fields (field => direction)
+ * @param string Direction or NULL for ascending
+ * @return Database_Builder
+ */
+ public function order_by($columns, $direction = NULL)
+ {
+ if (is_array($columns))
+ {
+ foreach ($columns as $column => $direction)
+ {
+ if (is_string($column))
+ {
+ $this->order_by[] = array($column => $direction);
+ }
+ else
+ {
+ // $direction is the column name when the array key is numeric
+ $this->order_by[] = array($direction => NULL);
+ }
+ }
+ }
+ else
+ {
+ $this->order_by[] = array($columns => $direction);
+ }
+ return $this;
+ }
+
+ /**
+ * Limit rows returned
+ *
+ * @chainable
+ * @param int Number of rows
+ * @return Database_Builder
+ */
+ public function limit($number)
+ {
+ $this->limit = (int) $number;
+
+ return $this;
+ }
+
+ /**
+ * Offset into result set
+ *
+ * @chainable
+ * @param int Offset
+ * @return Database_Builder
+ */
+ public function offset($number)
+ {
+ $this->offset = (int) $number;
+
+ return $this;
+ }
+
+ /**
+ * Alias for [Database_Builder::and_open]
+ *
+ * @chainable
+ * @param string Clause (WHERE OR HAVING)
+ * @return Database_Builder
+ */
+ public function open($clause = 'WHERE')
+ {
+ return $this->and_open($clause);
+ }
+
+ /**
+ * Open new **ANDs** parenthesis set
+ *
+ * @chainable
+ * @param string Clause (WHERE OR HAVING)
+ * @return Database_Builder
+ */
+ public function and_open($clause = 'WHERE')
+ {
+ if ($clause === 'WHERE')
+ {
+ $this->where[] = array('AND' => '(');
+ }
+ else
+ {
+ $this->having[] = array('AND' => '(');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Open new **OR** parenthesis set
+ *
+ * @chainable
+ * @param string Clause (WHERE OR HAVING)
+ * @return Database_Builder
+ */
+ public function or_open($clause = 'WHERE')
+ {
+ if ($clause === 'WHERE')
+ {
+ $this->where[] = array('OR' => '(');
+ }
+ else
+ {
+ $this->having[] = array('OR' => '(');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Close close parenthesis set
+ *
+ * @chainable
+ * @param string Clause (WHERE OR HAVING)
+ * @return Database_Builder
+ */
+ public function close($clause = 'WHERE')
+ {
+ if ($clause === 'WHERE')
+ {
+ $this->where[] = array(')');
+ }
+ else
+ {
+ $this->having[] = array(')');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set values for UPDATE
+ *
+ * @chainable
+ * @param mixed Column name or array of columns => vals
+ * @param mixed Value (can be a Database_Expression)
+ * @return Database_Builder
+ */
+ public function set($keys, $value = NULL)
+ {
+ if (is_string($keys))
+ {
+ $keys = array($keys => $value);
+ }
+
+ $this->set = array_merge($keys, $this->set);
+
+ return $this;
+ }
+
+ /**
+ * Columns used for INSERT queries
+ *
+ * @chainable
+ * @param array Columns
+ * @return Database_Builder
+ */
+ public function columns($columns)
+ {
+ if ( ! is_array($columns))
+ {
+ $columns = func_get_args();
+ }
+
+ $this->columns = $columns;
+
+ return $this;
+ }
+
+ /**
+ * Values used for INSERT queries
+ *
+ * @chainable
+ * @param array Values
+ * @return Database_Builder
+ */
+ public function values($values)
+ {
+ if ( ! is_array($values))
+ {
+ $values = func_get_args();
+ }
+
+ $this->values[] = $values;
+
+ return $this;
+ }
+
+ /**
+ * Set caching for the query
+ *
+ * @chainable
+ * @param mixed Time-to-live (FALSE to disable, NULL for Cache default, seconds otherwise)
+ * @return Database_Builder
+ */
+ public function cache($ttl = NULL)
+ {
+ $this->ttl = $ttl;
+
+ return $this;
+ }
+
+ /**
+ * Resets the database builder after execution. By default after you `execute()` a query
+ * the database builder will reset to its default state. You can use `reset(FALSE)`
+ * to stop this from happening. This is useful for pagination when you might want to
+ * apply a limit to the previous query.
+ *
+ * ##### Example
+ *
+ * $db = new Database_Builder;
+ * $all_results = $db->select()
+ * ->where('id', '=', 3)
+ * ->from('products')
+ * ->reset(FALSE)
+ * ->execute();
+ *
+ * // Run the query again with a limit of 10
+ * $ten_results = $db->limit(10)
+ * ->execute();
+ * @chainable
+ * @param bool reset builder
+ * @return Database_Builder
+ */
+ public function reset($reset = TRUE)
+ {
+ $this->reset = (bool) $reset;
+ return $this;
+ }
+
+ /**
+ * Compiles the given clause's conditions
+ *
+ * @param array Clause conditions
+ * @return string
+ */
+ protected function compile_conditions($groups)
+ {
+ $last_condition = NULL;
+
+ $sql = '';
+ foreach ($groups as $group)
+ {
+ // Process groups of conditions
+ foreach ($group as $logic => $condition)
+ {
+ if ($condition === '(')
+ {
+ if ( ! empty($sql) AND $last_condition !== '(')
+ {
+ // Include logic operator
+ $sql .= ' '.$logic.' ';
+ }
+
+ $sql .= '(';
+ }
+ elseif ($condition === ')')
+ {
+ $sql .= ')';
+ }
+ else
+ {
+ list($columns, $op, $value) = $condition;
+
+ // Stores each individual condition
+ $vals = array();
+
+ if ($columns instanceof Database_Expression)
+ {
+ // Add directly to condition list
+ $vals[] = (string) $columns;
+ }
+ else
+ {
+ $op = strtoupper($op);
+
+ if ( ! is_array($columns))
+ {
+ $columns = array($columns => $value);
+ }
+
+ foreach ($columns as $column => $value)
+ {
+ if ($value instanceof Database_Builder)
+ {
+ // Using a subquery
+ $value->db = $this->db;
+ $value = '('.(string) $value.')';
+ }
+ elseif (is_array($value))
+ {
+ if ($op === 'BETWEEN' OR $op === 'NOT BETWEEN')
+ {
+ // Falls between two values
+ $value = $this->db->quote($value[0]).' AND '.$this->db->quote($value[1]);
+ }
+ else
+ {
+ // Return as list
+ $value = array_map(array($this->db, 'quote'), $value);
+ $value = '('.implode(', ', $value).')';
+ }
+ }
+ else
+ {
+ $value = $this->db->quote($value);
+ }
+
+ if ( ! empty($column))
+ {
+ // Ignore blank columns
+ $column = $this->db->quote_column($column);
+ }
+
+ // Add to condition list
+ $vals[] = $column.' '.$op.' '.$value;
+ }
+ }
+
+ if ( ! empty($sql) AND $last_condition !== '(')
+ {
+ // Add the logic operator
+ $sql .= ' '.$logic.' ';
+ }
+
+ // Join the condition list items together by the given logic operator
+ $sql .= implode(' '.$logic.' ', $vals);
+ }
+
+ $last_condition = $condition;
+ }
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Compiles the columns portion of the query for INSERT
+ *
+ * @return string
+ */
+ protected function compile_columns()
+ {
+ return '('.implode(', ', array_map(array($this->db, 'quote_column'), $this->columns)).')';
+ }
+
+ /**
+ * Compiles the VALUES portion of the query for INSERT
+ *
+ * @return string
+ */
+ protected function compile_values()
+ {
+ $values = array();
+ foreach ($this->values as $group)
+ {
+ // Each set of values to be inserted
+ $values[] = '('.implode(', ', array_map(array($this->db, 'quote'), $group)).')';
+ }
+
+ return implode(', ', $values);
+ }
+
+ /**
+ * Create an UPDATE query
+ *
+ * @chainable
+ * @param string Table name
+ * @param array Array of Keys => Values
+ * @param array WHERE conditions
+ * @return Database_Builder
+ */
+ public function update($table = NULL, $set = NULL, $where = NULL)
+ {
+ $this->type = Database::UPDATE;
+
+ if (is_array($set))
+ {
+ $this->set($set);
+ }
+
+ if ($where !== NULL)
+ {
+ $this->where($where);
+ }
+
+ if ($table !== NULL)
+ {
+ $this->from($table);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Create an INSERT query. Use 'columns' and 'values' methods for multi-row inserts
+ *
+ * @chainable
+ * @param string Table name
+ * @param array Array of Keys => Values
+ * @return Database_Builder
+ */
+ public function insert($table = NULL, $set = NULL)
+ {
+ $this->type = Database::INSERT;
+
+ if (is_array($set))
+ {
+ $this->columns(array_keys($set));
+ $this->values(array_values($set));
+ }
+
+ if ($table !== NULL)
+ {
+ $this->from($table);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Create a DELETE query
+ *
+ * @chainable
+ * @param string Table name
+ * @param array WHERE conditions
+ * @return Database_Builder
+ */
+ public function delete($table, $where = NULL)
+ {
+ $this->type = Database::DELETE;
+
+ if ($where !== NULL)
+ {
+ $this->where($where);
+ }
+
+ if ($table !== NULL)
+ {
+ $this->from($table);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Count records for a given table
+ *
+ * @param string Table name
+ * @param array WHERE conditions
+ * @return int
+ */
+ public function count_records($table = FALSE, $where = NULL)
+ {
+ if (count($this->from) < 1)
+ {
+ if ($table === FALSE)
+ throw new Database_Exception('Database count_records requires a table');
+
+ $this->from($table);
+ }
+
+ if ($where !== NULL)
+ {
+ $this->where($where);
+ }
+
+ // Grab the count AS records_found
+ $result = $this->select(array('records_found' => 'COUNT("*")'))->execute();
+
+ return $result->get('records_found');
+ }
+
+ /**
+ * Executes the built query
+ *
+ * @param mixed Database name or object
+ * @return Database_Result
+ */
+ public function execute($db = NULL)
+ {
+ if ($db !== NULL)
+ {
+ $this->db = $db;
+ }
+
+ if ( ! is_object($this->db))
+ {
+ // Get the database instance
+ $this->db = Database::instance($this->db);
+ }
+
+ $query = $this->compile();
+
+ if ($this->reset)
+ {
+ // Reset the query after executing
+ $this->_reset();
+ }
+
+ if ($this->ttl !== FALSE AND $this->type === Database::SELECT)
+ {
+ // Return result from cache (only allowed with SELECT)
+ return $this->db->query_cache($query, $this->ttl);
+ }
+ else
+ {
+ // Load the result (no caching)
+ return $this->db->query($query);
+ }
+ }
+
+ /**
+ * Compiles the builder object into a SQL query
+ *
+ * @return string Compiled query
+ */
+ protected function compile()
+ {
+ if ( ! is_object($this->db))
+ {
+ // Use default database for compiling to string if none is given
+ $this->db = Database::instance($this->db);
+ }
+
+ if ($this->type === Database::SELECT)
+ {
+ // SELECT columns FROM table
+ $sql = $this->distinct ? 'SELECT DISTINCT ' : 'SELECT ';
+ $sql .= $this->compile_select();
+
+ if ( ! empty($this->from))
+ {
+ $sql .= "\nFROM ".$this->compile_from();
+ }
+ }
+ elseif ($this->type === Database::UPDATE)
+ {
+ $sql = 'UPDATE '.$this->compile_from()."\n".'SET '.$this->compile_set();
+ }
+ elseif ($this->type === Database::INSERT)
+ {
+ $sql = 'INSERT INTO '.$this->compile_from()."\n".$this->compile_columns()."\nVALUES ".$this->compile_values();
+ }
+ elseif ($this->type === Database::DELETE)
+ {
+ $sql = 'DELETE FROM '.$this->compile_from();
+ }
+
+ if ( ! empty($this->join))
+ {
+ $sql .= $this->compile_join();
+ }
+
+ if ( ! empty($this->where))
+ {
+ $sql .= "\n".'WHERE '.$this->compile_conditions($this->where);
+ }
+
+ if ( ! empty($this->group_by))
+ {
+ $sql .= "\n".'GROUP BY '.$this->compile_group_by();
+ }
+
+ if ( ! empty($this->having))
+ {
+ $sql .= "\n".'HAVING '.$this->compile_conditions($this->having);
+ }
+
+ if ( ! empty($this->order_by))
+ {
+ $sql .= "\nORDER BY ".$this->compile_order_by();
+ }
+
+ if (is_int($this->limit))
+ {
+ $sql .= "\nLIMIT ".$this->limit;
+ }
+
+ if (is_int($this->offset))
+ {
+ $sql .= "\nOFFSET ".$this->offset;
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Compiles the SELECT portion of the query
+ *
+ * @return string
+ */
+ protected function compile_select()
+ {
+ $vals = array();
+
+ foreach ($this->select as $alias => $name)
+ {
+ if ($name instanceof Database_Builder)
+ {
+ // Using a subquery
+ $name->db = $this->db;
+ $vals[] = '('.(string) $name.') AS '.$this->db->quote_column($alias);
+ }
+ elseif (is_string($alias))
+ {
+ $vals[] = $this->db->quote_column($name, $alias);
+ }
+ else
+ {
+ $vals[] = $this->db->quote_column($name);
+ }
+ }
+
+ return implode(', ', $vals);
+ }
+
+ /**
+ * Compiles the FROM portion of the query
+ *
+ * @return string
+ */
+ protected function compile_from()
+ {
+ $vals = array();
+
+ foreach ($this->from as $alias => $name)
+ {
+ if (is_string($alias))
+ {
+ // Using AS format so escape both
+ $vals[] = $this->db->quote_table($name, $alias);
+ }
+ else
+ {
+ // Just using the table name itself
+ $vals[] = $this->db->quote_table($name);
+ }
+ }
+
+ return implode(', ', $vals);
+ }
+
+ /**
+ * Compiles the JOIN portion of the query
+ *
+ * @return string
+ */
+ protected function compile_join()
+ {
+ $sql = '';
+ foreach ($this->join as $join)
+ {
+ list($table, $keys, $type) = $join;
+
+ if ($type !== NULL)
+ {
+ // Join type
+ $sql .= ' '.$type;
+ }
+
+ $sql .= ' JOIN '.$this->db->quote_table($table);
+
+ $condition = '';
+ if ($keys instanceof Database_Expression)
+ {
+ $condition = (string) $keys;
+ }
+ elseif (is_array($keys))
+ {
+ // ON condition is an array of matches
+ foreach ($keys as $key => $value)
+ {
+ if ( ! empty($condition))
+ {
+ $condition .= ' AND ';
+ }
+
+ $condition .= $this->db->quote_column($key).' = '.$this->db->quote_column($value);
+ }
+ }
+
+ if ( ! empty($condition))
+ {
+ // Add ON condition
+ $sql .= ' ON ('.$condition.')';
+ }
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Compiles the GROUP BY portion of the query
+ *
+ * @return string
+ */
+ protected function compile_group_by()
+ {
+ $vals = array();
+
+ foreach ($this->group_by as $column)
+ {
+ // Escape the column
+ $vals[] = $this->db->quote_column($column);
+ }
+
+ return implode(', ', $vals);
+ }
+
+ /**
+ * Compiles the ORDER BY portion of the query
+ *
+ * @return string
+ */
+ protected function compile_order_by()
+ {
+ $ordering = array();
+
+ foreach ($this->order_by as $column => $order_by)
+ {
+ list($column, $direction) = each($order_by);
+
+ $column = $this->db->quote_column($column);
+
+ if ($direction !== NULL)
+ {
+ $direction = ' '.$direction;
+ }
+
+ $ordering[] = $column.$direction;
+ }
+
+ return implode(', ', $ordering);
+ }
+
+ /**
+ * Compiles the SET portion of the query for UPDATE
+ *
+ * @return string
+ */
+ protected function compile_set()
+ {
+ $vals = array();
+
+ foreach ($this->set as $key => $value)
+ {
+ // Using an UPDATE so Key = Val
+ $vals[] = $this->db->quote_column($key).' = '.$this->db->quote($value);
+ }
+
+ return implode(', ', $vals);
+ }
+
+ /**
+ * Resets all query components
+ */
+ protected function _reset()
+ {
+ $this->select = array();
+ $this->from = array();
+ $this->join = array();
+ $this->where = array();
+ $this->group_by = array();
+ $this->having = array();
+ $this->order_by = array();
+ $this->limit = NULL;
+ $this->offset = NULL;
+ $this->set = array();
+ $this->values = array();
+ $this->type = NULL;
+ $this->distinct = FALSE;
+ $this->reset = TRUE;
+ $this->ttl = FALSE;
+ }
+
+} // End Database_Builder
diff --git a/system/libraries/Database_Cache_Result.php b/system/libraries/Database_Cache_Result.php
new file mode 100644
index 0000000..3945c42
--- /dev/null
+++ b/system/libraries/Database_Cache_Result.php
@@ -0,0 +1,81 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Cached database result.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2008-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Database_Cache_Result_Core extends Database_Result {
+
+ /**
+ * Result data (array of rows)
+ * @var array
+ */
+ protected $data;
+
+ public function __construct($data, $sql, $return_objects)
+ {
+ $this->data = $data;
+ $this->sql = $sql;
+ $this->total_rows = count($data);
+ $this->return_objects = $return_objects;
+ }
+
+ public function __destruct()
+ {
+ // Not used
+ }
+
+ public function as_array($return = FALSE)
+ {
+ // Return arrays rather than objects
+ $this->return_objects = FALSE;
+
+ if ( ! $return )
+ {
+ // Return this result object
+ return $this;
+ }
+
+ // Return the entire array of rows
+ return $this->data;
+ }
+
+ public function as_object($class = NULL, $return = FALSE)
+ {
+ if ($class !== NULL)
+ throw new Database_Exception('Database cache results do not support object casting');
+
+ // Return objects of type $class (or stdClass if none given)
+ $this->return_objects = TRUE;
+
+ return $this;
+ }
+
+ public function seek($offset)
+ {
+ if ( ! $this->offsetExists($offset))
+ return FALSE;
+
+ $this->current_row = $offset;
+
+ return TRUE;
+ }
+
+ public function current()
+ {
+ if ($this->return_objects)
+ {
+ // Return a new object with the current row of data
+ return (object) $this->data[$this->current_row];
+ }
+ else
+ {
+ // Return an array of the row
+ return $this->data[$this->current_row];
+ }
+ }
+
+} // End Database_Cache_Result \ No newline at end of file
diff --git a/system/libraries/Database_Exception.php b/system/libraries/Database_Exception.php
new file mode 100644
index 0000000..0f6bb75
--- /dev/null
+++ b/system/libraries/Database_Exception.php
@@ -0,0 +1,15 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Database exceptions.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2008-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Database_Exception_Core extends Kohana_Exception {
+
+ // Database error code
+ protected $code = E_DATABASE_ERROR;
+
+} // End Database_Exception \ No newline at end of file
diff --git a/system/libraries/Database_Expression.php b/system/libraries/Database_Expression.php
new file mode 100644
index 0000000..007a0cb
--- /dev/null
+++ b/system/libraries/Database_Expression.php
@@ -0,0 +1,23 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Database expression.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2008-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Database_Expression_Core {
+
+ protected $expression;
+
+ public function __construct($expression)
+ {
+ $this->expression = $expression;
+ }
+
+ public function __toString()
+ {
+ return $this->expression;
+ }
+}
diff --git a/system/libraries/Database_Mysql.php b/system/libraries/Database_Mysql.php
new file mode 100644
index 0000000..a325cbc
--- /dev/null
+++ b/system/libraries/Database_Mysql.php
@@ -0,0 +1,226 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * MySQL database connection.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2008-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Database_Mysql_Core extends Database {
+
+ // Quote character to use for identifiers (tables/columns/aliases)
+ protected $quote = '`';
+
+ // Use SET NAMES to set the character set
+ protected static $set_names;
+
+ public function connect()
+ {
+ if ($this->connection)
+ return;
+
+ if (Database_Mysql::$set_names === NULL)
+ {
+ // Determine if we can use mysql_set_charset(), which is only
+ // available on PHP 5.2.3+ when compiled against MySQL 5.0+
+ Database_Mysql::$set_names = ! function_exists('mysql_set_charset');
+ }
+
+ extract($this->config['connection']);
+
+ $host = isset($host) ? $host : $socket;
+ $port = isset($port) ? ':'.$port : '';
+
+ try
+ {
+ // Connect to the database
+ $this->connection = ($this->config['persistent'] === TRUE)
+ ? mysql_pconnect($host.$port, $user, $pass, $params)
+ : mysql_connect($host.$port, $user, $pass, TRUE, $params);
+ }
+ catch (Kohana_PHP_Exception $e)
+ {
+ // No connection exists
+ $this->connection = NULL;
+
+ // Unable to connect to the database
+ throw new Database_Exception('#:errno: :error',
+ array(':error' => mysql_error(),
+ ':errno' => mysql_errno()));
+ }
+
+ if ( ! mysql_select_db($database, $this->connection))
+ {
+ // Unable to select database
+ throw new Database_Exception('#:errno: :error',
+ array(':error' => mysql_error($this->connection),
+ ':errno' => mysql_errno($this->connection)));
+ }
+
+ if (isset($this->config['character_set']))
+ {
+ // Set the character set
+ $this->set_charset($this->config['character_set']);
+ }
+ }
+
+ public function disconnect()
+ {
+ try
+ {
+ // Database is assumed disconnected
+ $status = TRUE;
+
+ if (is_resource($this->connection))
+ {
+ $status = mysql_close($this->connection);
+ }
+ }
+ catch (Exception $e)
+ {
+ // Database is probably not disconnected
+ $status = is_resource($this->connection);
+ }
+
+ return $status;
+ }
+
+ public function set_charset($charset)
+ {
+ // Make sure the database is connected
+ $this->connection or $this->connect();
+
+ if (Database_Mysql::$set_names === TRUE)
+ {
+ // PHP is compiled against MySQL 4.x
+ $status = (bool) mysql_query('SET NAMES '.$this->quote($charset), $this->connection);
+ }
+ else
+ {
+ // PHP is compiled against MySQL 5.x
+ $status = mysql_set_charset($charset, $this->connection);
+ }
+
+ if ($status === FALSE)
+ {
+ // Unable to set charset
+ throw new Database_Exception('#:errno: :error',
+ array(':error' => mysql_error($this->connection),
+ ':errno' => mysql_errno($this->connection)));
+ }
+ }
+
+ public function query_execute($sql)
+ {
+ // Make sure the database is connected
+ $this->connection or $this->connect();
+
+ $result = mysql_query($sql, $this->connection);
+
+ // Set the last query
+ $this->last_query = $sql;
+
+ return new Database_Mysql_Result($result, $sql, $this->connection, $this->config['object']);
+ }
+
+ public function escape($value)
+ {
+ // Make sure the database is connected
+ $this->connection or $this->connect();
+
+ if (($value = mysql_real_escape_string($value, $this->connection)) === FALSE)
+ {
+ throw new Database_Exception('#:errno: :error',
+ array(':error' => mysql_error($this->connection),
+ ':errno' => mysql_errno($this->connection)));
+ }
+
+ return $value;
+ }
+
+ public function list_constraints($table)
+ {
+ $prefix = strlen($this->table_prefix());
+ $result = array();
+
+ $constraints = $this->query('
+ SELECT c.constraint_name, c.constraint_type, k.column_name, k.referenced_table_name, k.referenced_column_name
+ FROM information_schema.table_constraints c
+ JOIN information_schema.key_column_usage k ON (k.table_schema = c.table_schema AND k.table_name = c.table_name AND k.constraint_name = c.constraint_name)
+ WHERE c.table_schema = '.$this->quote($this->config['connection']['database']).'
+ AND c.table_name = '.$this->quote($this->table_prefix().$table).'
+ AND (k.referenced_table_schema IS NULL OR k.referenced_table_schema ='.$this->quote($this->config['connection']['database']).')
+ ORDER BY k.ordinal_position
+ ');
+
+ foreach ($constraints->as_array() as $row)
+ {
+ switch ($row['constraint_type'])
+ {
+ case 'FOREIGN KEY':
+ if (isset($result[$row['constraint_name']]))
+ {
+ $result[$row['constraint_name']][1][] = $row['column_name'];
+ $result[$row['constraint_name']][3][] = $row['referenced_column_name'];
+ }
+ else
+ {
+ $result[$row['constraint_name']] = array($row['constraint_type'], array($row['column_name']), substr($row['referenced_table_name'], $prefix), array($row['referenced_column_name']));
+ }
+ break;
+ case 'PRIMARY KEY':
+ case 'UNIQUE':
+ if (isset($result[$row['constraint_name']]))
+ {
+ $result[$row['constraint_name']][1][] = $row['column_name'];
+ }
+ else
+ {
+ $result[$row['constraint_name']] = array($row['constraint_type'], array($row['column_name']));
+ }
+ break;
+ }
+ }
+
+ return $result;
+ }
+
+ public function list_fields($table)
+ {
+ $result = array();
+
+ foreach ($this->query('SHOW COLUMNS FROM '.$this->quote_table($table))->as_array() as $row)
+ {
+ $column = $this->sql_type($row['Type']);
+
+ $column['default'] = $row['Default'];
+ $column['nullable'] = $row['Null'] === 'YES';
+ $column['sequenced'] = $row['Extra'] === 'auto_increment';
+
+ if (isset($column['length']) AND $column['type'] === 'float')
+ {
+ list($column['precision'], $column['scale']) = explode(',', $column['length']);
+ }
+
+ $result[$row['Field']] = $column;
+ }
+
+ return $result;
+ }
+
+ public function list_tables()
+ {
+ $prefix = strlen($this->table_prefix());
+ $tables = array();
+
+ foreach ($this->query('SHOW TABLES LIKE '.$this->quote($this->table_prefix().'%'))->as_array() as $row)
+ {
+ // The value is the table name
+ $tables[] = substr(current($row), $prefix);
+ }
+
+ return $tables;
+ }
+
+} // End Database_MySQL
diff --git a/system/libraries/Database_Mysql_Result.php b/system/libraries/Database_Mysql_Result.php
new file mode 100644
index 0000000..0f89898
--- /dev/null
+++ b/system/libraries/Database_Mysql_Result.php
@@ -0,0 +1,174 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * MySQL database result.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2008-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Database_Mysql_Result_Core extends Database_Result {
+
+ protected $internal_row = 0;
+
+ public function __construct($result, $sql, $link, $return_objects)
+ {
+ if (is_resource($result))
+ {
+ // True to return objects, false for arrays
+ $this->return_objects = $return_objects;
+
+ $this->total_rows = mysql_num_rows($result);
+ }
+ elseif (is_bool($result))
+ {
+ if ($result == FALSE)
+ {
+ throw new Database_Exception('#:errno: :error [ :query ]',
+ array(':error' => mysql_error($link),
+ ':query' => $sql,
+ ':errno' => mysql_errno($link)));
+
+ }
+ else
+ {
+ // It's a DELETE, INSERT, REPLACE, or UPDATE query
+ $this->insert_id = mysql_insert_id($link);
+ $this->total_rows = mysql_affected_rows($link);
+ }
+ }
+
+ // Store the result locally
+ $this->result = $result;
+
+ $this->sql = $sql;
+ }
+
+ public function __destruct()
+ {
+ if (is_resource($this->result))
+ {
+ mysql_free_result($this->result);
+ }
+ }
+
+ public function as_array($return = FALSE)
+ {
+ // Return arrays rather than objects
+ $this->return_objects = FALSE;
+
+ if ( ! $return )
+ {
+ // Return this result object
+ return $this;
+ }
+
+ // Return a nested array of all results
+ $array = array();
+
+ if ($this->total_rows > 0)
+ {
+ // Seek to the beginning of the result
+ mysql_data_seek($this->result, 0);
+
+ while ($row = mysql_fetch_assoc($this->result))
+ {
+ // Add each row to the array
+ $array[] = $row;
+ }
+
+ $this->internal_row = $this->total_rows;
+ }
+
+ return $array;
+ }
+
+ public function as_object($class = NULL, $return = FALSE)
+ {
+ // Return objects of type $class (or stdClass if none given)
+ $this->return_objects = ($class !== NULL) ? $class : TRUE;
+
+ if ( ! $return )
+ {
+ // Return this result object
+ return $this;
+ }
+
+ // Return a nested array of all results
+ $array = array();
+
+ if ($this->total_rows > 0)
+ {
+ // Seek to the beginning of the result
+ mysql_data_seek($this->result, 0);
+
+ if (is_string($this->return_objects))
+ {
+ while ($row = mysql_fetch_object($this->result, $this->return_objects))
+ {
+ // Add each row to the array
+ $array[] = $row;
+ }
+ }
+ else
+ {
+ while ($row = mysql_fetch_object($this->result))
+ {
+ // Add each row to the array
+ $array[] = $row;
+ }
+ }
+
+ $this->internal_row = $this->total_rows;
+ }
+
+ return $array;
+ }
+
+ /**
+ * SeekableIterator: seek
+ */
+ public function seek($offset)
+ {
+ if ($this->offsetExists($offset) AND mysql_data_seek($this->result, $offset))
+ {
+ // Set the current row to the offset
+ $this->current_row = $this->internal_row = $offset;
+
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+ }
+
+ /**
+ * Iterator: current
+ */
+ public function current()
+ {
+ if ($this->current_row !== $this->internal_row AND ! $this->seek($this->current_row))
+ return NULL;
+
+ ++$this->internal_row;
+
+ if ($this->return_objects)
+ {
+ if (is_string($this->return_objects))
+ {
+ return mysql_fetch_object($this->result, $this->return_objects);
+ }
+ else
+ {
+ return mysql_fetch_object($this->result);
+ }
+ }
+ else
+ {
+ // Return an array of the row
+ return mysql_fetch_assoc($this->result);
+ }
+ }
+
+} // End Database_MySQL_Result \ No newline at end of file
diff --git a/system/libraries/Database_Mysqli.php b/system/libraries/Database_Mysqli.php
new file mode 100644
index 0000000..41b635d
--- /dev/null
+++ b/system/libraries/Database_Mysqli.php
@@ -0,0 +1,90 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * MySQL database connection.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2008-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+
+define('RUNS_MYSQLND', function_exists('mysqli_fetch_all'));
+
+class Database_Mysqli_Core extends Database_Mysql {
+
+ public function connect()
+ {
+ if (is_object($this->connection))
+ return;
+
+ extract($this->config['connection']);
+
+ // Persistent connections are supported as of PHP 5.3
+ if (RUNS_MYSQLND AND $this->config['persistent'] === TRUE)
+ {
+ $host = 'p:'.$host;
+ }
+
+ $host = isset($host) ? $host : $socket;
+
+ $mysqli = mysqli_init();
+
+ if ( ! $mysqli->real_connect($host, $user, $pass, $database, $port, $socket, $params))
+ throw new Database_Exception('#:errno: :error',
+ array(':error' => $mysqli->connect_error, ':errno' => $mysqli->connect_errno));
+
+ $this->connection = $mysqli;
+
+ if (isset($this->config['character_set']))
+ {
+ // Set the character set
+ $this->set_charset($this->config['character_set']);
+ }
+ }
+
+ public function disconnect()
+ {
+ if (is_object($this->connection))
+ {
+ $this->connection->close();
+ }
+
+ $this->connection = NULL;
+ }
+
+ public function set_charset($charset)
+ {
+ // Make sure the database is connected
+ is_object($this->connection) or $this->connect();
+
+ if ( ! $this->connection->set_charset($charset))
+ {
+ // Unable to set charset
+ throw new Database_Exception('#:errno: :error',
+ array(':error' => $this->connection->connect_error,
+ ':errno' => $this->connection->connect_errno));
+ }
+ }
+
+ public function query_execute($sql)
+ {
+ // Make sure the database is connected
+ is_object($this->connection) or $this->connect();
+
+ $result = $this->connection->query($sql);
+
+ // Set the last query
+ $this->last_query = $sql;
+
+ return new Database_Mysqli_Result($result, $sql, $this->connection, $this->config['object']);
+ }
+
+ public function escape($value)
+ {
+ // Make sure the database is connected
+ is_object($this->connection) or $this->connect();
+
+ return $this->connection->real_escape_string($value);
+ }
+
+} // End Database_MySQLi
diff --git a/system/libraries/Database_Mysqli_Result.php b/system/libraries/Database_Mysqli_Result.php
new file mode 100644
index 0000000..f8b7b58
--- /dev/null
+++ b/system/libraries/Database_Mysqli_Result.php
@@ -0,0 +1,175 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * MySQL database result.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2008-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Database_Mysqli_Result_Core extends Database_Result {
+
+ protected $internal_row = 0;
+
+ public function __construct($result, $sql, $link, $return_objects)
+ {
+ if (is_object($result))
+ {
+ // True to return objects, false for arrays
+ $this->return_objects = $return_objects;
+
+ $this->total_rows = $result->num_rows;
+ }
+ elseif (is_bool($result))
+ {
+ if ($result == FALSE)
+ {
+ throw new Database_Exception('#:errno: :error [ :query ]',
+ array(':error' => $link->error,
+ ':query' => $sql,
+ ':errno' => $link->errno));
+ }
+ else
+ {
+ // It's a DELETE, INSERT, REPLACE, or UPDATE query
+ $this->insert_id = $link->insert_id;
+ $this->total_rows = $link->affected_rows;
+ }
+ }
+
+ // Store the result locally
+ $this->result = $result;
+
+ $this->sql = $sql;
+ }
+
+ public function __destruct()
+ {
+ if (is_object($this->result))
+ {
+ $this->result->free();
+ }
+ }
+
+ public function as_array($return = FALSE)
+ {
+ // Return arrays rather than objects
+ $this->return_objects = FALSE;
+
+ if ( ! $return )
+ {
+ // Return this result object
+ return $this;
+ }
+
+ // Return a nested array of all results
+ if (RUNS_MYSQLND)
+ return $this->result->fetch_all(MYSQLI_ASSOC);
+
+ $array = array();
+
+ if ($this->total_rows > 0)
+ {
+ // Seek to the beginning of the result
+ $this->result->data_seek(0);
+
+ while ($row = $this->result->fetch_assoc())
+ {
+ // Add each row to the array
+ $array[] = $row;
+ }
+ $this->internal_row = $this->total_rows;
+ }
+
+ return $array;
+ }
+
+ public function as_object($class = NULL, $return = FALSE)
+ {
+ // Return objects of type $class (or stdClass if none given)
+ $this->return_objects = ($class !== NULL) ? $class : TRUE;
+
+ if ( ! $return )
+ {
+ // Return this result object
+ return $this;
+ }
+
+ // Return a nested array of all results
+ $array = array();
+
+ if ($this->total_rows > 0)
+ {
+ // Seek to the beginning of the result
+ $this->result->data_seek(0);
+
+ if (is_string($this->return_objects))
+ {
+ while ($row = $this->result->fetch_object($this->return_objects))
+ {
+ // Add each row to the array
+ $array[] = $row;
+ }
+ }
+ else
+ {
+ while ($row = $this->result->fetch_object())
+ {
+ // Add each row to the array
+ $array[] = $row;
+ }
+ }
+
+ $this->internal_row = $this->total_rows;
+ }
+
+ return $array;
+ }
+
+ /**
+ * SeekableIterator: seek
+ */
+ public function seek($offset)
+ {
+ if ($this->offsetExists($offset) AND $this->result->data_seek($offset))
+ {
+ // Set the current row to the offset
+ $this->current_row = $offset;
+
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+ }
+
+ /**
+ * Iterator: current
+ */
+ public function current()
+ {
+ if ($this->current_row !== $this->internal_row AND ! $this->seek($this->current_row))
+ return NULL;
+
+ ++$this->internal_row;
+
+ if ($this->return_objects)
+ {
+ if (is_string($this->return_objects))
+ {
+ return $this->result->fetch_object($this->return_objects);
+ }
+ else
+ {
+ return $this->result->fetch_object();
+ }
+ }
+ else
+ {
+ // Return an array of the row
+ return $this->result->fetch_assoc();
+ }
+ }
+
+} // End Database_MySQLi_Result \ No newline at end of file
diff --git a/system/libraries/Database_Query.php b/system/libraries/Database_Query.php
new file mode 100644
index 0000000..d9399d6
--- /dev/null
+++ b/system/libraries/Database_Query.php
@@ -0,0 +1,95 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Database query wrapper.
+ *
+ * $Id: Database_Query.php 4679 2009-11-10 01:45:52Z isaiah $
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2008-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Database_Query_Core {
+
+ protected $sql;
+ protected $params;
+ protected $ttl = FALSE;
+
+ public function __construct($sql = NULL)
+ {
+ $this->sql = $sql;
+ }
+
+ public function __toString()
+ {
+ // Return the SQL of this query
+ return $this->sql;
+ }
+
+ public function sql($sql)
+ {
+ $this->sql = $sql;
+
+ return $this;
+ }
+
+ public function value($key, $value)
+ {
+ $this->params[$key] = $value;
+
+ return $this;
+ }
+
+ public function bind($key, & $value)
+ {
+ $this->params[$key] =& $value;
+
+ return $this;
+ }
+
+ public function execute($db = 'default')
+ {
+ if ( ! is_object($db))
+ {
+ // Get the database instance
+ $db = Database::instance($db);
+ }
+
+ // Import the SQL locally
+ $sql = $this->sql;
+
+ if ( ! empty($this->params))
+ {
+ // Quote all of the values
+ $params = array_map(array($db, 'quote'), $this->params);
+
+ // Replace the values in the SQL
+ $sql = strtr($sql, $params);
+ }
+
+ if ($this->ttl !== FALSE)
+ {
+ // Load the result from the cache
+ return $db->query_cache($sql, $this->ttl);
+ }
+ else
+ {
+ // Load the result (no caching)
+ return $db->query($sql);
+ }
+ }
+
+ /**
+ * Set caching for the query
+ *
+ * @param mixed Time-to-live (FALSE to disable, NULL for Cache default, seconds otherwise)
+ * @return Database_Query
+ */
+ public function cache($ttl = NULL)
+ {
+ $this->ttl = $ttl;
+
+ return $this;
+ }
+
+} // End Database_Query \ No newline at end of file
diff --git a/system/libraries/Database_Result.php b/system/libraries/Database_Result.php
new file mode 100644
index 0000000..cf2056f
--- /dev/null
+++ b/system/libraries/Database_Result.php
@@ -0,0 +1,170 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Database result wrapper.
+ *
+ * $Id: Database_Result.php 4679 2009-11-10 01:45:52Z isaiah $
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2008-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+abstract class Database_Result_Core implements Countable, Iterator, SeekableIterator, ArrayAccess {
+
+ protected $result;
+
+ protected $total_rows = 0;
+ protected $current_row = 0;
+ protected $insert_id;
+
+ // Return objects or arrays for each row
+ protected $return_objects;
+
+ /**
+ * Sets the total number of rows and stores the result locally.
+ *
+ * @param mixed $result query result
+ * @param boolean $return_objects True for results as objects, false for arrays
+ * @return void
+ */
+ abstract public function __construct($result, $sql, $link, $return_objects);
+
+ /**
+ * Result destruction cleans up all open result sets.
+ */
+ abstract public function __destruct();
+
+ /**
+ * Return arrays for reach result, or the entire set of results
+ *
+ * @param boolean $return True to return entire result array
+ * @return Database_Result|array
+ */
+ abstract public function as_array($return = FALSE);
+
+ /**
+ * Returns objects for each result
+ *
+ * @param string $class Class name to return objects as or NULL for stdClass
+ * @return Database_Result
+ */
+ abstract public function as_object($class = NULL, $return = FALSE);
+
+ /**
+ * Returns the insert id
+ *
+ * @return int
+ */
+ public function insert_id()
+ {
+ return $this->insert_id;
+ }
+
+ /**
+ * Return the named column from the current row.
+ *
+ * @param string Column name
+ * @return mixed
+ */
+ public function get($name)
+ {
+ // Get the current row
+ $row = $this->current();
+
+ if ( ! $this->return_objects)
+ return $row[$name];
+
+ return $row->$name;
+ }
+
+ /**
+ * Countable: count
+ */
+ public function count()
+ {
+ return $this->total_rows;
+ }
+
+ /**
+ * ArrayAccess: offsetExists
+ */
+ public function offsetExists($offset)
+ {
+ return ($offset >= 0 AND $offset < $this->total_rows);
+ }
+
+ /**
+ * ArrayAccess: offsetGet
+ */
+ public function offsetGet($offset)
+ {
+ if ( ! $this->seek($offset))
+ return NULL;
+
+ return $this->current();
+ }
+
+ /**
+ * ArrayAccess: offsetSet
+ *
+ * @throws Kohana_Database_Exception
+ */
+ final public function offsetSet($offset, $value)
+ {
+ throw new Kohana_Exception('Database results are read-only');
+ }
+
+ /**
+ * ArrayAccess: offsetUnset
+ *
+ * @throws Kohana_Database_Exception
+ */
+ final public function offsetUnset($offset)
+ {
+ throw new Kohana_Exception('Database results are read-only');
+ }
+
+ /**
+ * Iterator: key
+ */
+ public function key()
+ {
+ return $this->current_row;
+ }
+
+ /**
+ * Iterator: next
+ */
+ public function next()
+ {
+ ++$this->current_row;
+ return $this;
+ }
+
+ /**
+ * Iterator: prev
+ */
+ public function prev()
+ {
+ --$this->current_row;
+ return $this;
+ }
+
+ /**
+ * Iterator: rewind
+ */
+ public function rewind()
+ {
+ $this->current_row = 0;
+ return $this;
+ }
+
+ /**
+ * Iterator: valid
+ */
+ public function valid()
+ {
+ return $this->offsetExists($this->current_row);
+ }
+
+} // End Database_Result \ No newline at end of file
diff --git a/system/libraries/Encrypt.php b/system/libraries/Encrypt.php
new file mode 100644
index 0000000..15d4087
--- /dev/null
+++ b/system/libraries/Encrypt.php
@@ -0,0 +1,176 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * The Encrypt library provides two-way encryption of text and binary strings
+ * using the MCrypt extension.
+ * @see http://php.net/mcrypt
+ *
+ * $Id: Encrypt.php 4729 2009-12-29 20:35:19Z isaiah $
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Encrypt_Core {
+
+ // OS-dependant RAND type to use
+ protected static $rand;
+
+ // Configuration
+ protected $config;
+
+ /**
+ * Returns a singleton instance of Encrypt.
+ *
+ * @param array configuration options
+ * @return Encrypt_Core
+ */
+ public static function instance($config = NULL)
+ {
+ static $instance;
+
+ // Create the singleton
+ empty($instance) and $instance = new Encrypt((array) $config);
+
+ return $instance;
+ }
+
+ /**
+ * Loads encryption configuration and validates the data.
+ *
+ * @param array|string custom configuration or config group name
+ * @throws Kohana_Exception
+ */
+ public function __construct($config = FALSE)
+ {
+ if ( ! defined('MCRYPT_ENCRYPT'))
+ throw new Kohana_Exception('To use the Encrypt library, mcrypt must be enabled in your PHP installation');
+
+ if (is_string($config))
+ {
+ $name = $config;
+
+ // Test the config group name
+ if (($config = Kohana::config('encryption.'.$config)) === NULL)
+ throw new Kohana_Exception('The :name: group is not defined in your configuration.', array(':name:' => $name));
+ }
+
+ if (is_array($config))
+ {
+ // Append the default configuration options
+ $config += Kohana::config('encryption.default');
+ }
+ else
+ {
+ // Load the default group
+ $config = Kohana::config('encryption.default');
+ }
+
+ if (empty($config['key']))
+ throw new Kohana_Exception('To use the Encrypt library, you must set an encryption key in your config file');
+
+ // Find the max length of the key, based on cipher and mode
+ $size = mcrypt_get_key_size($config['cipher'], $config['mode']);
+
+ if (strlen($config['key']) > $size)
+ {
+ // Shorten the key to the maximum size
+ $config['key'] = substr($config['key'], 0, $size);
+ }
+
+ // Find the initialization vector size
+ $config['iv_size'] = mcrypt_get_iv_size($config['cipher'], $config['mode']);
+
+ // Cache the config in the object
+ $this->config = $config;
+
+ Kohana_Log::add('debug', 'Encrypt Library initialized');
+ }
+
+ /**
+ * Encrypts a string and returns an encrypted string that can be decoded.
+ *
+ * @param string data to be encrypted
+ * @return string encrypted data
+ */
+ public function encode($data)
+ {
+ // Set the rand type if it has not already been set
+ if (Encrypt::$rand === NULL)
+ {
+ if (KOHANA_IS_WIN)
+ {
+ // Windows only supports the system random number generator
+ Encrypt::$rand = MCRYPT_RAND;
+ }
+ else
+ {
+ if (defined('MCRYPT_DEV_URANDOM'))
+ {
+ // Use /dev/urandom
+ Encrypt::$rand = MCRYPT_DEV_URANDOM;
+ }
+ elseif (defined('MCRYPT_DEV_RANDOM'))
+ {
+ // Use /dev/random
+ Encrypt::$rand = MCRYPT_DEV_RANDOM;
+ }
+ else
+ {
+ // Use the system random number generator
+ Encrypt::$rand = MCRYPT_RAND;
+ }
+ }
+ }
+
+ if (Encrypt::$rand === MCRYPT_RAND)
+ {
+ // The system random number generator must always be seeded each
+ // time it is used, or it will not produce true random results
+ mt_srand();
+ }
+
+ // Create a random initialization vector of the proper size for the current cipher
+ $iv = mcrypt_create_iv($this->config['iv_size'], Encrypt::$rand);
+
+ // Encrypt the data using the configured options and generated iv
+ $data = mcrypt_encrypt($this->config['cipher'], $this->config['key'], $data, $this->config['mode'], $iv);
+
+ // Use base64 encoding to convert to a string
+ return base64_encode($iv.$data);
+ }
+
+ /**
+ * Decrypts an encoded string back to its original value.
+ *
+ * @param string encoded string to be decrypted
+ * @return string decrypted data or FALSE if decryption fails
+ */
+ public function decode($data)
+ {
+ // Convert the data back to binary
+ $data = base64_decode($data, TRUE);
+
+ if ( ! $data)
+ {
+ // Invalid base64 data
+ return FALSE;
+ }
+
+ // Extract the initialization vector from the data
+ $iv = substr($data, 0, $this->config['iv_size']);
+
+ if ($this->config['iv_size'] !== strlen($iv))
+ {
+ // The iv is not the correct size
+ return FALSE;
+ }
+
+ // Remove the iv from the data
+ $data = substr($data, $this->config['iv_size']);
+
+ // Return the decrypted data, trimming the \0 padding bytes from the end of the data
+ return rtrim(mcrypt_decrypt($this->config['cipher'], $this->config['key'], $data, $this->config['mode'], $iv), "\0");
+ }
+
+} // End Encrypt
diff --git a/system/libraries/I18n.php b/system/libraries/I18n.php
new file mode 100644
index 0000000..9401ddc
--- /dev/null
+++ b/system/libraries/I18n.php
@@ -0,0 +1,100 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Kohana I18N System
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+
+class I18n_Core
+{
+ protected static $locale;
+ // All the translations will be cached in here, after the first call of get_text()
+ protected static $translations = array();
+
+ public static function set_locale($locale)
+ {
+ // Reset the translations array
+ I18n::$translations = array();
+
+ I18n::$locale = $locale;
+ }
+
+
+ /**
+ *
+ * Returns the locale.
+ * If $ext is true, the UTF8 extension gets returned as well, otherwise, just the language code.
+ * Defaults to true.
+ *
+ * @return The locale
+ * @param boolean $ext[optional] Get the Extension?
+ */
+ public static function get_locale($ext = true)
+ {
+ if($ext)
+ return I18n::$locale;
+ else
+ return arr::get(explode('.', I18n::$locale), 0);
+ }
+
+
+ /**
+ *
+ * Translates $string into language I18n::$locale and caches all found translations on the first call
+ *
+ * @return The translated String
+ * @param string $string The String to translate
+ */
+ public static function get_text($string)
+ {
+ if ( ! I18n::$translations)
+ {
+ $locale = explode('_', I18n::get_locale(FALSE));
+
+ // Get the translation files
+ $translation_files = Kohana::find_file('i18n', $locale[0]);
+
+ if($local_translation_files = Kohana::find_file('i18n', $locale[0].'/'.$locale[1]))
+ $translation_files = array_merge($translation_files, $local_translation_files);
+
+ if ($translation_files)
+ {
+ // Merge the translations
+ foreach ($translation_files as $file)
+ {
+ include $file;
+ I18n::$translations = array_merge(I18n::$translations, $translations);
+ }
+ }
+ }
+
+ if (isset(I18n::$translations[$string]))
+ return I18n::$translations[$string];
+ else
+ return $string;
+ }
+}
+
+/**
+ * Loads the configured driver and validates it.
+ *
+ * @param string Text to output
+ * @param array Key/Value pairs of arguments to replace in the string
+ * @return string Translated text
+ */
+function __($string, $args = NULL)
+{
+ // KOHANA_LOCALE is the default locale, in which all of Kohana's __() calls are written in
+ if (I18n::get_locale() != Kohana::LOCALE)
+ {
+ $string = I18n::get_text($string);
+ }
+
+ if ($args === NULL)
+ return $string;
+
+ return strtr($string, $args);
+}
diff --git a/system/libraries/Image.php b/system/libraries/Image.php
new file mode 100644
index 0000000..991c8d5
--- /dev/null
+++ b/system/libraries/Image.php
@@ -0,0 +1,501 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Manipulate images using standard methods such as resize, crop, rotate, etc.
+ * This class must be re-initialized for every image you wish to manipulate.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Image_Core {
+
+ // Master Dimension
+ const NONE = 1;
+ const AUTO = 2;
+ const HEIGHT = 3;
+ const WIDTH = 4;
+
+ // Flip Directions
+ const HORIZONTAL = 5;
+ const VERTICAL = 6;
+
+ // Orientations
+ const PORTRAIT = 7;
+ const LANDSCAPE = 8;
+ const SQUARE = 9;
+
+ // Allowed image types
+ public static $allowed_types = array
+ (
+ IMAGETYPE_GIF => 'gif',
+ IMAGETYPE_JPEG => 'jpg',
+ IMAGETYPE_PNG => 'png',
+ IMAGETYPE_TIFF_II => 'tiff',
+ IMAGETYPE_TIFF_MM => 'tiff',
+ );
+
+ // Driver instance
+ protected $driver;
+
+ // Driver actions
+ protected $actions = array();
+
+ // Reference to the current image filename
+ protected $image = '';
+
+ /**
+ * Creates a new Image instance and returns it.
+ *
+ * @param string filename of image
+ * @param array non-default configurations
+ * @return object
+ */
+ public static function factory($image, $config = NULL)
+ {
+ return new Image($image, $config);
+ }
+
+ /**
+ * Creates a new image editor instance.
+ *
+ * @throws Kohana_Exception
+ * @param string filename of image
+ * @param array non-default configurations
+ * @return void
+ */
+ public function __construct($image, $config = NULL)
+ {
+ static $check;
+
+ // Make the check exactly once
+ ($check === NULL) and $check = function_exists('getimagesize');
+
+ if ($check === FALSE)
+ throw new Kohana_Exception('The Image library requires the getimagesize() PHP function, which is not available in your installation.');
+
+ // Check to make sure the image exists
+ if ( ! is_file($image))
+ throw new Kohana_Exception('The specified image, :image:, was not found. Please verify that images exist by using file_exists() before manipulating them.', array(':image:' => $image));
+
+ // Disable error reporting, to prevent PHP warnings
+ $ER = error_reporting(0);
+
+ // Fetch the image size and mime type
+ $image_info = getimagesize($image);
+
+ // Turn on error reporting again
+ error_reporting($ER);
+
+ // Make sure that the image is readable and valid
+ if ( ! is_array($image_info) OR count($image_info) < 3)
+ throw new Kohana_Exception('The file specified, :file:, is not readable or is not an image', array(':file:' => $image));
+
+ // Check to make sure the image type is allowed
+ if ( ! isset(Image::$allowed_types[$image_info[2]]))
+ throw new Kohana_Exception('The specified image, :type:, is not an allowed image type.', array(':type:' => $image));
+
+ // Image has been validated, load it
+ $this->image = array
+ (
+ 'file' => str_replace('\\', '/', realpath($image)),
+ 'width' => $image_info[0],
+ 'height' => $image_info[1],
+ 'type' => $image_info[2],
+ 'ext' => Image::$allowed_types[$image_info[2]],
+ 'mime' => $image_info['mime']
+ );
+
+ $this->determine_orientation();
+
+ // Load configuration
+ $this->config = (array) $config + Kohana::config('image');
+
+ // Set driver class name
+ $driver = 'Image_'.ucfirst($this->config['driver']).'_Driver';
+
+ // Load the driver
+ if ( ! Kohana::auto_load($driver))
+ throw new Kohana_Exception('The :driver: driver for the :library: library could not be found',
+ array(':driver:' => $this->config['driver'], ':library:' => get_class($this)));
+
+ // Initialize the driver
+ $this->driver = new $driver($this->config['params']);
+
+ // Validate the driver
+ if ( ! ($this->driver instanceof Image_Driver))
+ throw new Kohana_Exception('The :driver: driver for the :library: library must implement the :interface: interface',
+ array(':driver:' => $this->config['driver'], ':library:' => get_class($this), ':interface:' => 'Image_Driver'));
+ }
+
+ /**
+ * Works out the correct orientation for the image
+ *
+ * @return void
+ */
+ protected function determine_orientation()
+ {
+ switch (TRUE)
+ {
+ case $this->image['height'] > $this->image['width']:
+ $orientation = Image::PORTRAIT;
+ break;
+
+ case $this->image['height'] < $this->image['width']:
+ $orientation = Image::LANDSCAPE;
+ break;
+
+ default:
+ $orientation = Image::SQUARE;
+ }
+
+ $this->image['orientation'] = $orientation;
+ }
+
+ /**
+ * Handles retrieval of pre-save image properties
+ *
+ * @param string property name
+ * @return mixed
+ */
+ public function __get($property)
+ {
+ if (isset($this->image[$property]))
+ {
+ return $this->image[$property];
+ }
+ else
+ {
+ throw new Kohana_Exception('The :property: property does not exist in the :class: class.',
+ array(':property:' => $property, ':class:' => get_class($this)));
+ }
+ }
+
+ /**
+ * Resize an image to a specific width and height. By default, Kohana will
+ * maintain the aspect ratio using the width as the master dimension. If you
+ * wish to use height as master dim, set $image->master_dim = Image::HEIGHT
+ * This method is chainable.
+ *
+ * @throws Kohana_Exception
+ * @param integer width
+ * @param integer height
+ * @param integer one of: Image::NONE, Image::AUTO, Image::WIDTH, Image::HEIGHT
+ * @return object
+ */
+ public function resize($width, $height, $master = NULL)
+ {
+ if ( ! $this->valid_size('width', $width))
+ throw new Kohana_Exception('The width you specified, :width:, is not valid.', array(':width:' => $width));
+
+ if ( ! $this->valid_size('height', $height))
+ throw new Kohana_Exception('The height you specified, :height:, is not valid.', array(':height:' => $height));
+
+ if (empty($width) AND empty($height))
+ throw new Kohana_Exception('The dimensions specified for :function: are not valid.', array(':function:' => __FUNCTION__));
+
+ if ($master === NULL)
+ {
+ // Maintain the aspect ratio by default
+ $master = Image::AUTO;
+ }
+ elseif ( ! $this->valid_size('master', $master))
+ throw new Kohana_Exception('The master dimension specified is not valid.');
+
+ $this->actions['resize'] = array
+ (
+ 'width' => $width,
+ 'height' => $height,
+ 'master' => $master,
+ );
+
+ $this->determine_orientation();
+
+ return $this;
+ }
+
+ /**
+ * Crop an image to a specific width and height. You may also set the top
+ * and left offset.
+ * This method is chainable.
+ *
+ * @throws Kohana_Exception
+ * @param integer width
+ * @param integer height
+ * @param integer top offset, pixel value or one of: top, center, bottom
+ * @param integer left offset, pixel value or one of: left, center, right
+ * @return object
+ */
+ public function crop($width, $height, $top = 'center', $left = 'center')
+ {
+ if ( ! $this->valid_size('width', $width))
+ throw new Kohana_Exception('The width you specified, :width:, is not valid.', array(':width:' => $width));
+
+ if ( ! $this->valid_size('height', $height))
+ throw new Kohana_Exception('The height you specified, :height:, is not valid.', array(':height:' => $height));
+
+ if ( ! $this->valid_size('top', $top))
+ throw new Kohana_Exception('The top offset you specified, :top:, is not valid.', array(':top:' => $top));
+
+ if ( ! $this->valid_size('left', $left))
+ throw new Kohana_Exception('The left offset you specified, :left:, is not valid.', array(':left:' => $left));
+
+ if (empty($width) AND empty($height))
+ throw new Kohana_Exception('The dimensions specified for :function: are not valid.', array(':function:' => __FUNCTION__));
+
+ $this->actions['crop'] = array
+ (
+ 'width' => $width,
+ 'height' => $height,
+ 'top' => $top,
+ 'left' => $left,
+ );
+
+ $this->determine_orientation();
+
+ return $this;
+ }
+
+ /**
+ * Allows rotation of an image by 180 degrees clockwise or counter clockwise.
+ *
+ * @param integer degrees
+ * @return object
+ */
+ public function rotate($degrees)
+ {
+ $degrees = (int) $degrees;
+
+ if ($degrees > 180)
+ {
+ do
+ {
+ // Keep subtracting full circles until the degrees have normalized
+ $degrees -= 360;
+ }
+ while($degrees > 180);
+ }
+
+ if ($degrees < -180)
+ {
+ do
+ {
+ // Keep adding full circles until the degrees have normalized
+ $degrees += 360;
+ }
+ while($degrees < -180);
+ }
+
+ $this->actions['rotate'] = $degrees;
+
+ return $this;
+ }
+
+ /**
+ * Overlay a second image on top of this one.
+ *
+ * @throws Kohana_Exception
+ * @param string $overlay_file path to an image file
+ * @param integer $x x offset for the overlay
+ * @param integer $y y offset for the overlay
+ * @param integer $transparency transparency percent
+ */
+ public function composite($overlay_file, $x, $y, $transparency)
+ {
+ $image_info = getimagesize($overlay_file);
+
+ // Check to make sure the image type is allowed
+ if ( ! isset(Image::$allowed_types[$image_info[2]]))
+ throw new Kohana_Exception('The specified image, :type:, is not an allowed image type.', array(':type:' => $overlay_file));
+
+ $this->actions['composite'] = array
+ (
+ 'overlay_file' => $overlay_file,
+ 'mime' => $image_info['mime'],
+ 'x' => $x,
+ 'y' => $y,
+ 'transparency' => $transparency
+ );
+
+ return $this;
+ }
+
+ /**
+ * Flip an image horizontally or vertically.
+ *
+ * @throws Kohana_Exception
+ * @param integer direction
+ * @return object
+ */
+ public function flip($direction)
+ {
+ if ($direction !== Image::HORIZONTAL AND $direction !== Image::VERTICAL)
+ throw new Kohana_Exception('The flip direction specified is not valid.');
+
+ $this->actions['flip'] = $direction;
+
+ return $this;
+ }
+
+ /**
+ * Change the quality of an image.
+ *
+ * @param integer quality as a percentage
+ * @return object
+ */
+ public function quality($amount)
+ {
+ $this->actions['quality'] = max(1, min($amount, 100));
+
+ return $this;
+ }
+
+ /**
+ * Sharpen an image.
+ *
+ * @param integer amount to sharpen, usually ~20 is ideal
+ * @return object
+ */
+ public function sharpen($amount)
+ {
+ $this->actions['sharpen'] = max(1, min($amount, 100));
+
+ return $this;
+ }
+
+ /**
+ * Save the image to a new image or overwrite this image.
+ *
+ * @throws Kohana_Exception
+ * @param string new image filename
+ * @param integer permissions for new image
+ * @param boolean keep or discard image process actions
+ * @return object
+ */
+ public function save($new_image = FALSE, $chmod = 0644, $keep_actions = FALSE, $background = NULL)
+ {
+ // If no new image is defined, use the current image
+ empty($new_image) and $new_image = $this->image['file'];
+
+ // Separate the directory and filename
+ $dir = pathinfo($new_image, PATHINFO_DIRNAME);
+ $file = pathinfo($new_image, PATHINFO_BASENAME);
+
+ // Normalize the path
+ $dir = str_replace('\\', '/', realpath($dir)).'/';
+
+ if ( ! is_writable($dir))
+ throw new Kohana_Exception('The specified directory, :dir:, is not writable.', array(':dir:' => $dir));
+
+ if ($status = $this->driver->process($this->image, $this->actions, $dir, $file, FALSE, $background))
+ {
+ if ($chmod !== FALSE)
+ {
+ // Set permissions
+ chmod($new_image, $chmod);
+ }
+ }
+
+ if ($keep_actions !== TRUE)
+ {
+ // Reset actions. Subsequent save() or render() will not apply previous actions.
+ $this->actions = array();
+ }
+
+ return $status;
+ }
+
+ /**
+ * Output the image to the browser.
+ *
+ * @param boolean keep or discard image process actions
+ * @return object
+ */
+ public function render($keep_actions = FALSE, $background = NULL)
+ {
+ $new_image = $this->image['file'];
+
+ // Separate the directory and filename
+ $dir = pathinfo($new_image, PATHINFO_DIRNAME);
+ $file = pathinfo($new_image, PATHINFO_BASENAME);
+
+ // Normalize the path
+ $dir = str_replace('\\', '/', realpath($dir)).'/';
+
+ // Process the image with the driver
+ $status = $this->driver->process($this->image, $this->actions, $dir, $file, TRUE, $background);
+
+ if ($keep_actions !== TRUE)
+ {
+ // Reset actions. Subsequent save() or render() will not apply previous actions.
+ $this->actions = array();
+ }
+
+ return $status;
+ }
+
+ /**
+ * Sanitize a given value type.
+ *
+ * @param string type of property
+ * @param mixed property value
+ * @return boolean
+ */
+ protected function valid_size($type, & $value)
+ {
+ if (is_null($value))
+ return TRUE;
+
+ if ( ! is_scalar($value))
+ return FALSE;
+
+ switch ($type)
+ {
+ case 'width':
+ case 'height':
+ if (is_string($value) AND ! ctype_digit($value))
+ {
+ // Only numbers and percent signs
+ if ( ! preg_match('/^[0-9]++%$/D', $value))
+ return FALSE;
+ }
+ else
+ {
+ $value = (int) $value;
+ }
+ break;
+ case 'top':
+ if (is_string($value) AND ! ctype_digit($value))
+ {
+ if ( ! in_array($value, array('top', 'bottom', 'center')))
+ return FALSE;
+ }
+ else
+ {
+ $value = (int) $value;
+ }
+ break;
+ case 'left':
+ if (is_string($value) AND ! ctype_digit($value))
+ {
+ if ( ! in_array($value, array('left', 'right', 'center')))
+ return FALSE;
+ }
+ else
+ {
+ $value = (int) $value;
+ }
+ break;
+ case 'master':
+ if ($value !== Image::NONE AND
+ $value !== Image::AUTO AND
+ $value !== Image::WIDTH AND
+ $value !== Image::HEIGHT)
+ return FALSE;
+ break;
+ }
+
+ return TRUE;
+ }
+
+} // End Image \ No newline at end of file
diff --git a/system/libraries/Input.php b/system/libraries/Input.php
new file mode 100644
index 0000000..fa984a8
--- /dev/null
+++ b/system/libraries/Input.php
@@ -0,0 +1,507 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Input library.
+ *
+ * $Id: Input.php 4729 2009-12-29 20:35:19Z isaiah $
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Input_Core {
+
+ // Enable or disable automatic XSS cleaning
+ protected $use_xss_clean = FALSE;
+
+ // Are magic quotes enabled?
+ protected $magic_quotes_gpc = FALSE;
+
+ // IP address of current user
+ public $ip_address;
+
+ // Input singleton
+ protected static $instance;
+
+ /**
+ * Retrieve a singleton instance of Input. This will always be the first
+ * created instance of this class.
+ *
+ * @return object
+ */
+ public static function instance()
+ {
+ if (Input::$instance === NULL)
+ {
+ // Create a new instance
+ return new Input;
+ }
+
+ return Input::$instance;
+ }
+
+ /**
+ * Sanitizes global GET, POST and COOKIE data. Also takes care of
+ * magic_quotes and register_globals, if they have been enabled.
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ // Convert all global variables to Kohana charset
+ $_GET = Input::clean($_GET);
+ $_POST = Input::clean($_POST);
+ $_COOKIE = Input::clean($_COOKIE);
+ $_SERVER = Input::clean($_SERVER);
+
+ if (Kohana::$server_api === 'cli')
+ {
+ // Convert command line arguments
+ $_SERVER['argv'] = Input::clean($_SERVER['argv']);
+ }
+
+ // Use XSS clean?
+ $this->use_xss_clean = (bool) Kohana::config('core.global_xss_filtering');
+
+ if (Input::$instance === NULL)
+ {
+ // magic_quotes_runtime is enabled
+ if (get_magic_quotes_runtime())
+ {
+ @set_magic_quotes_runtime(0);
+ Kohana_Log::add('debug', 'Disable magic_quotes_runtime! It is evil and deprecated: http://php.net/magic_quotes');
+ }
+
+ // magic_quotes_gpc is enabled
+ if (get_magic_quotes_gpc())
+ {
+ $this->magic_quotes_gpc = TRUE;
+ Kohana_Log::add('debug', 'Disable magic_quotes_gpc! It is evil and deprecated: http://php.net/magic_quotes');
+ }
+
+ if (is_array($_GET))
+ {
+ foreach ($_GET as $key => $val)
+ {
+ // Sanitize $_GET
+ $_GET[$this->clean_input_keys($key)] = $this->clean_input_data($val);
+ }
+ }
+ else
+ {
+ $_GET = array();
+ }
+
+ if (is_array($_POST))
+ {
+ foreach ($_POST as $key => $val)
+ {
+ // Sanitize $_POST
+ $_POST[$this->clean_input_keys($key)] = $this->clean_input_data($val);
+ }
+ }
+ else
+ {
+ $_POST = array();
+ }
+
+ if (is_array($_COOKIE))
+ {
+ foreach ($_COOKIE as $key => $val)
+ {
+ // Ignore special attributes in RFC2109 compliant cookies
+ if ($key == '$Version' OR $key == '$Path' OR $key == '$Domain')
+ continue;
+
+ // Sanitize $_COOKIE
+ $_COOKIE[$this->clean_input_keys($key)] = $this->clean_input_data($val);
+ }
+ }
+ else
+ {
+ $_COOKIE = array();
+ }
+
+ // Create a singleton
+ Input::$instance = $this;
+
+ Kohana_Log::add('debug', 'Global GET, POST and COOKIE data sanitized');
+ }
+ }
+
+ /**
+ * Fetch an item from the $_GET array.
+ *
+ * @param string key to find
+ * @param mixed default value
+ * @param boolean XSS clean the value
+ * @return mixed
+ */
+ public function get($key = array(), $default = NULL, $xss_clean = FALSE)
+ {
+ return $this->search_array($_GET, $key, $default, $xss_clean);
+ }
+
+ /**
+ * Fetch an item from the $_POST array.
+ *
+ * @param string key to find
+ * @param mixed default value
+ * @param boolean XSS clean the value
+ * @return mixed
+ */
+ public function post($key = array(), $default = NULL, $xss_clean = FALSE)
+ {
+ return $this->search_array($_POST, $key, $default, $xss_clean);
+ }
+
+ /**
+ * Fetch an item from the cookie::get() ($_COOKIE won't work with signed
+ * cookies.)
+ *
+ * @param string key to find
+ * @param mixed default value
+ * @param boolean XSS clean the value
+ * @return mixed
+ */
+ public function cookie($key = array(), $default = NULL, $xss_clean = FALSE)
+ {
+ return $this->search_array(cookie::get(), $key, $default, $xss_clean);
+ }
+
+ /**
+ * Fetch an item from the $_SERVER array.
+ *
+ * @param string key to find
+ * @param mixed default value
+ * @param boolean XSS clean the value
+ * @return mixed
+ */
+ public function server($key = array(), $default = NULL, $xss_clean = FALSE)
+ {
+ return $this->search_array($_SERVER, $key, $default, $xss_clean);
+ }
+
+ /**
+ * Fetch an item from a global array.
+ *
+ * @param array array to search
+ * @param string key to find
+ * @param mixed default value
+ * @param boolean XSS clean the value
+ * @return mixed
+ */
+ protected function search_array($array, $key, $default = NULL, $xss_clean = FALSE)
+ {
+ if ($key === array())
+ return $array;
+
+ if ( ! isset($array[$key]))
+ return $default;
+
+ // Get the value
+ $value = $array[$key];
+
+ if ($this->use_xss_clean === FALSE AND $xss_clean === TRUE)
+ {
+ // XSS clean the value
+ $value = $this->xss_clean($value);
+ }
+
+ return $value;
+ }
+
+ /**
+ * Fetch the IP Address.
+ *
+ * @return string
+ */
+ public function ip_address()
+ {
+ if ($this->ip_address !== NULL)
+ return $this->ip_address;
+
+ // Server keys that could contain the client IP address
+ $keys = array('HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'REMOTE_ADDR');
+
+ foreach ($keys as $key)
+ {
+ if ($ip = $this->server($key))
+ {
+ $this->ip_address = $ip;
+
+ // An IP address has been found
+ break;
+ }
+ }
+
+ if ($comma = strrpos($this->ip_address, ',') !== FALSE)
+ {
+ $this->ip_address = substr($this->ip_address, $comma + 1);
+ }
+
+ if ( ! valid::ip($this->ip_address))
+ {
+ // Use an empty IP
+ $this->ip_address = '0.0.0.0';
+ }
+
+ return $this->ip_address;
+ }
+
+ /**
+ * Clean cross site scripting exploits from string.
+ * HTMLPurifier may be used if installed, otherwise defaults to built in method.
+ * Note - This function should only be used to deal with data upon submission.
+ * It's not something that should be used for general runtime processing
+ * since it requires a fair amount of processing overhead.
+ *
+ * @param string data to clean
+ * @param string xss_clean method to use ('htmlpurifier' or defaults to built-in method)
+ * @return string
+ */
+ public function xss_clean($data, $tool = NULL)
+ {
+ if ($tool === NULL)
+ {
+ // Use the default tool
+ $tool = Kohana::config('core.global_xss_filtering');
+ }
+
+ if (is_array($data))
+ {
+ foreach ($data as $key => $val)
+ {
+ $data[$key] = $this->xss_clean($val, $tool);
+ }
+
+ return $data;
+ }
+
+ // Do not clean empty strings
+ if (trim($data) === '')
+ return $data;
+
+ if (is_bool($tool))
+ {
+ $tool = 'default';
+ }
+ elseif ( ! method_exists($this, 'xss_filter_'.$tool))
+ {
+ Kohana_Log::add('error', 'Unable to use Input::xss_filter_'.$tool.'(), no such method exists');
+ $tool = 'default';
+ }
+
+ $method = 'xss_filter_'.$tool;
+
+ return $this->$method($data);
+ }
+
+ /**
+ * Default built-in cross site scripting filter.
+ *
+ * @param string data to clean
+ * @return string
+ */
+ protected function xss_filter_default($data)
+ {
+ // http://svn.bitflux.ch/repos/public/popoon/trunk/classes/externalinput.php
+ // +----------------------------------------------------------------------+
+ // | Copyright (c) 2001-2006 Bitflux GmbH |
+ // +----------------------------------------------------------------------+
+ // | Licensed under the Apache License, Version 2.0 (the "License"); |
+ // | you may not use this file except in compliance with the License. |
+ // | You may obtain a copy of the License at |
+ // | http://www.apache.org/licenses/LICENSE-2.0 |
+ // | Unless required by applicable law or agreed to in writing, software |
+ // | distributed under the License is distributed on an "AS IS" BASIS, |
+ // | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
+ // | implied. See the License for the specific language governing |
+ // | permissions and limitations under the License. |
+ // +----------------------------------------------------------------------+
+ // | Author: Christian Stocker <chregu@bitflux.ch> |
+ // +----------------------------------------------------------------------+
+ //
+ // Kohana Modifications:
+ // * Changed double quotes to single quotes, changed indenting and spacing
+ // * Removed magic_quotes stuff
+ // * Increased regex readability:
+ // * Used delimeters that aren't found in the pattern
+ // * Removed all unneeded escapes
+ // * Deleted U modifiers and swapped greediness where needed
+ // * Increased regex speed:
+ // * Made capturing parentheses non-capturing where possible
+ // * Removed parentheses where possible
+ // * Split up alternation alternatives
+ // * Made some quantifiers possessive
+ //
+ // Gallery Modifications:
+ // * Wrap the loop around all the changes to detect nested exploits
+
+ do
+ {
+ $old_data = $data;
+
+ // Fix &entity\n;
+ $data = str_replace(array('&amp;','&lt;','&gt;'), array('&amp;amp;','&amp;lt;','&amp;gt;'), $data);
+ $data = preg_replace('/(&#*\w+)[\x00-\x20]+;/u', '$1;', $data);
+ $data = preg_replace('/(&#x*[0-9A-F]+);*/iu', '$1;', $data);
+ $data = html_entity_decode($data, ENT_COMPAT, 'UTF-8');
+
+ // Remove any attribute starting with "on" or xmlns
+ $data = preg_replace('#(?:on[a-z]+|xmlns)\s*=\s*[\'"\x00-\x20]?[^\'>"]*[\'"\x00-\x20]?\s?#iu', '', $data);
+
+ // Remove javascript: and vbscript: protocols
+ $data = preg_replace('#([a-z]*)[\x00-\x20]*=[\x00-\x20]*([`\'"]*)[\x00-\x20]*j[\x00-\x20]*a[\x00-\x20]*v[\x00-\x20]*a[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2nojavascript...', $data);
+ $data = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*v[\x00-\x20]*b[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2novbscript...', $data);
+ $data = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*-moz-binding[\x00-\x20]*:#u', '$1=$2nomozbinding...', $data);
+
+ //remove any style attributes, IE allows too much stupid things in them, eg.
+ //<span style="width: expression(alert('Ping!'));"></span>
+ // and in general you really don't want style declarations in your UGC
+ $data = preg_replace('#(<[^>]+[\x00-\x20\"\'\/])style[^>]*>#iUu', "$1>", $data);
+
+ // Remove namespaced elements (we do not need them)
+ $data = preg_replace('#</*\w+:\w[^>]*+>#i', '', $data);
+
+ // Remove really unwanted tags
+ $data = preg_replace('#</*(?:applet|b(?:ase|gsound|link)|embed|frame(?:set)?|i(?:frame|layer)|l(?:ayer|ink)|meta|object|s(?:cript|tyle)|title|xml)[^>]*+>#i', '', $data);
+ }
+ while ($old_data !== $data);
+
+ return $data;
+ }
+
+ /**
+ * HTMLPurifier cross site scripting filter. This version assumes the
+ * existence of the "Standalone Distribution" htmlpurifier library, and is set to not tidy
+ * input.
+ *
+ * @param string data to clean
+ * @return string
+ */
+ protected function xss_filter_htmlpurifier($data)
+ {
+ /**
+ * @todo License should go here, http://htmlpurifier.org/
+ */
+ if ( ! class_exists('HTMLPurifier_Config', FALSE))
+ {
+ // Load HTMLPurifier
+ require Kohana::find_file('vendor', 'htmlpurifier/HTMLPurifier.standalone', TRUE);
+ }
+
+ // Set configuration
+ $config = HTMLPurifier_Config::createDefault();
+ $config->set('HTML.TidyLevel', 'none'); // Only XSS cleaning now
+
+ $cache = Kohana::config('html_purifier.cache');
+
+ if ($cache AND is_string($cache))
+ {
+ $config->set('Cache.SerializerPath', $cache);
+ }
+
+ // Run HTMLPurifier
+ $data = HTMLPurifier::instance($config)->purify($data);
+
+ return $data;
+ }
+
+ /**
+ * This is a helper method. It enforces W3C specifications for allowed
+ * key name strings, to prevent malicious exploitation.
+ *
+ * @param string string to clean
+ * @return string
+ */
+ public function clean_input_keys($str)
+ {
+ if ( ! preg_match('#^[\pL0-9:_.-]++$#uD', $str))
+ {
+ exit('Disallowed key characters in global data.');
+ }
+
+ return $str;
+ }
+
+ /**
+ * This is a helper method. It escapes data and forces all newline
+ * characters to "\n".
+ *
+ * @param unknown_type string to clean
+ * @return string
+ */
+ public function clean_input_data($str)
+ {
+ if (is_array($str))
+ {
+ $new_array = array();
+ foreach ($str as $key => $val)
+ {
+ // Recursion!
+ $new_array[$this->clean_input_keys($key)] = $this->clean_input_data($val);
+ }
+ return $new_array;
+ }
+
+ if ($this->magic_quotes_gpc === TRUE)
+ {
+ // Remove annoying magic quotes
+ $str = stripslashes($str);
+ }
+
+ if ($this->use_xss_clean === TRUE)
+ {
+ $str = $this->xss_clean($str);
+ }
+
+ if (strpos($str, "\r") !== FALSE)
+ {
+ // Standardize newlines
+ $str = str_replace(array("\r\n", "\r"), "\n", $str);
+ }
+
+ return $str;
+ }
+
+ /**
+ * Recursively cleans arrays, objects, and strings. Removes ASCII control
+ * codes and converts to UTF-8 while silently discarding incompatible
+ * UTF-8 characters.
+ *
+ * @param string string to clean
+ * @return string
+ */
+ public static function clean($str)
+ {
+ if (is_array($str) OR is_object($str))
+ {
+ foreach ($str as $key => $val)
+ {
+ // Recursion!
+ $str[Input::clean($key)] = Input::clean($val);
+ }
+ }
+ elseif (is_string($str) AND $str !== '')
+ {
+ // Remove control characters
+ $str = text::strip_ascii_ctrl($str);
+
+ if ( ! text::is_ascii($str))
+ {
+ // Disable notices
+ $ER = error_reporting(~E_NOTICE);
+
+ // iconv is expensive, so it is only used when needed
+ $str = iconv(Kohana::CHARSET, Kohana::CHARSET.'//IGNORE', $str);
+
+ // Turn notices back on
+ error_reporting($ER);
+ }
+ }
+
+ return $str;
+ }
+
+} // End Input Class
diff --git a/system/libraries/Kohana_404_Exception.php b/system/libraries/Kohana_404_Exception.php
new file mode 100644
index 0000000..7bb7708
--- /dev/null
+++ b/system/libraries/Kohana_404_Exception.php
@@ -0,0 +1,56 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Creates a "Page Not Found" exception.
+ *
+ * $Id: Kohana_404_Exception.php 4729 2009-12-29 20:35:19Z isaiah $
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+
+class Kohana_404_Exception_Core extends Kohana_Exception {
+
+ protected $code = E_PAGE_NOT_FOUND;
+
+ /**
+ * Set internal properties.
+ *
+ * @param string URI of page
+ * @param string custom error template
+ */
+ public function __construct($page = NULL)
+ {
+ if ($page === NULL)
+ {
+ // Use the complete URI
+ $page = Router::$complete_uri;
+ }
+
+ parent::__construct('The page you requested, %page%, could not be found.', array('%page%' => $page));
+ }
+
+ /**
+ * Throws a new 404 exception.
+ *
+ * @throws Kohana_404_Exception
+ * @return void
+ */
+ public static function trigger($page = NULL)
+ {
+ throw new Kohana_404_Exception($page);
+ }
+
+ /**
+ * Sends 404 headers, to emulate server behavior.
+ *
+ * @return void
+ */
+ public function sendHeaders()
+ {
+ // Send the 404 header
+ header('HTTP/1.1 404 File Not Found');
+ }
+
+} // End Kohana 404 Exception \ No newline at end of file
diff --git a/system/libraries/Kohana_Log.php b/system/libraries/Kohana_Log.php
new file mode 100644
index 0000000..5126013
--- /dev/null
+++ b/system/libraries/Kohana_Log.php
@@ -0,0 +1,90 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Logging class.
+ *
+ * $Id: Kohana_Log.php 4729 2009-12-29 20:35:19Z isaiah $
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Kohana_Log_Core {
+
+ // Configuration
+ protected static $config;
+
+ // Drivers
+ protected static $drivers;
+
+ // Logged messages
+ protected static $messages;
+
+ /**
+ * Add a new message to the log.
+ *
+ * @param string type of message
+ * @param string message text
+ * @return void
+ */
+ public static function add($type, $message)
+ {
+ // Make sure the drivers and config are loaded
+ if ( ! is_array(Kohana_Log::$config))
+ {
+ Kohana_Log::$config = Kohana::config('log');
+ }
+
+ if ( ! is_array(Kohana_Log::$drivers))
+ {
+ foreach ( (array) Kohana::config('log.drivers') as $driver_name)
+ {
+ // Set driver name
+ $driver = 'Log_'.ucfirst($driver_name).'_Driver';
+
+ // Load the driver
+ if ( ! Kohana::auto_load($driver))
+ throw new Kohana_Exception('Log Driver Not Found: %driver%', array('%driver%' => $driver));
+
+ // Initialize the driver
+ $driver = new $driver(array_merge(Kohana::config('log'), Kohana::config('log_'.$driver_name)));
+
+ // Validate the driver
+ if ( ! ($driver instanceof Log_Driver))
+ throw new Kohana_Exception('%driver% does not implement the Log_Driver interface', array('%driver%' => $driver));
+
+ Kohana_Log::$drivers[] = $driver;
+ }
+
+ // Always save logs on shutdown
+ Event::add('system.shutdown', array('Kohana_Log', 'save'));
+ }
+
+ Kohana_Log::$messages[] = array('date' => time(), 'type' => $type, 'message' => $message);
+ }
+
+ /**
+ * Save all currently logged messages.
+ *
+ * @return void
+ */
+ public static function save()
+ {
+ if (empty(Kohana_Log::$messages))
+ return;
+
+ foreach (Kohana_Log::$drivers as $driver)
+ {
+ // We can't throw exceptions here or else we will get a
+ // Exception thrown without a stack frame error
+ try
+ {
+ $driver->save(Kohana_Log::$messages);
+ }
+ catch(Exception $e){}
+ }
+
+ // Reset the messages
+ Kohana_Log::$messages = array();
+ }
+} \ No newline at end of file
diff --git a/system/libraries/Kohana_PHP_Exception.php b/system/libraries/Kohana_PHP_Exception.php
new file mode 100644
index 0000000..779c229
--- /dev/null
+++ b/system/libraries/Kohana_PHP_Exception.php
@@ -0,0 +1,99 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Kohana PHP Error Exceptions
+ *
+ * $Id: Kohana_PHP_Exception.php 4729 2009-12-29 20:35:19Z isaiah $
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+
+class Kohana_PHP_Exception_Core extends Kohana_Exception {
+
+ public static $enabled = FALSE;
+
+ /**
+ * Enable Kohana PHP error handling.
+ *
+ * @return void
+ */
+ public static function enable()
+ {
+ if ( ! Kohana_PHP_Exception::$enabled)
+ {
+ // Handle runtime errors
+ set_error_handler(array('Kohana_PHP_Exception', 'error_handler'));
+
+ // Handle errors which halt execution
+ Event::add('system.shutdown', array('Kohana_PHP_Exception', 'shutdown_handler'));
+
+ Kohana_PHP_Exception::$enabled = TRUE;
+ }
+ }
+
+ /**
+ * Disable Kohana PHP error handling.
+ *
+ * @return void
+ */
+ public static function disable()
+ {
+ if (Kohana_PHP_Exception::$enabled)
+ {
+ restore_error_handler();
+
+ Event::clear('system.shutdown', array('Kohana_PHP_Exception', 'shutdown_handler'));
+
+ Kohana_PHP_Exception::$enabled = FALSE;
+ }
+ }
+
+ /**
+ * Create a new PHP error exception.
+ *
+ * @return void
+ */
+ public function __construct($code, $error, $file, $line, $context = NULL)
+ {
+ parent::__construct($error);
+
+ // Set the error code, file, line, and context manually
+ $this->code = $code;
+ $this->file = $file;
+ $this->line = $line;
+ }
+
+ /**
+ * PHP error handler.
+ *
+ * @throws Kohana_PHP_Exception
+ * @return void
+ */
+ public static function error_handler($code, $error, $file, $line, $context = NULL)
+ {
+ // Respect error_reporting settings
+ if (error_reporting() & $code)
+ {
+ // Throw an exception
+ throw new Kohana_PHP_Exception($code, $error, $file, $line, $context);
+ }
+ }
+
+ /**
+ * Catches errors that are not caught by the error handler, such as E_PARSE.
+ *
+ * @uses Kohana_Exception::handle()
+ * @return void
+ */
+ public static function shutdown_handler()
+ {
+ if (Kohana_PHP_Exception::$enabled AND $error = error_get_last() AND (error_reporting() & $error['type']))
+ {
+ // Fake an exception for nice debugging
+ Kohana_Exception::handle(new Kohana_PHP_Exception($error['type'], $error['message'], $error['file'], $error['line']));
+ }
+ }
+
+} // End Kohana PHP Exception
diff --git a/system/libraries/Kohana_User_Exception.php b/system/libraries/Kohana_User_Exception.php
new file mode 100644
index 0000000..a0ec3ac
--- /dev/null
+++ b/system/libraries/Kohana_User_Exception.php
@@ -0,0 +1,30 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Creates a custom exception message.
+ *
+ * $Id: Kohana_User_Exception.php 4729 2009-12-29 20:35:19Z isaiah $
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+
+class Kohana_User_Exception_Core extends Kohana_Exception {
+
+ /**
+ * Set exception title and message.
+ *
+ * @param string exception title string
+ * @param string exception message string
+ * @param string custom error template
+ */
+ public function __construct($title, $message, array $variables = NULL)
+ {
+ parent::__construct($message, $variables);
+
+ // Code is the error title
+ $this->code = $title;
+ }
+
+} // End Kohana User Exception
diff --git a/system/libraries/Model.php b/system/libraries/Model.php
new file mode 100644
index 0000000..ee50d50
--- /dev/null
+++ b/system/libraries/Model.php
@@ -0,0 +1,62 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Model base class.
+ *
+ * $Id: Model.php 4729 2009-12-29 20:35:19Z isaiah $
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Model_Core {
+
+ /**
+ * Creates and returns a new model.
+ *
+ * @param string model name
+ * @param mixed constructor arguments
+ * @param boolean construct the model with multiple arguments
+ * @return Model
+ */
+ public static function factory($name, $args = NULL, $multiple = FALSE)
+ {
+ // Model class name
+ $class = ucfirst($name).'_Model';
+
+ if ($args === NULL)
+ {
+ // Create a new model with no arguments
+ return new $class;
+ }
+
+ if ($multiple !== TRUE)
+ {
+ // Create a model with a single argument
+ return new $class($args);
+ }
+
+ $class = new ReflectionClass($class);
+
+ // Create a model with multiple arguments
+ return $class->newInstanceArgs($args);
+ }
+
+ // Database object
+ protected $db = 'default';
+
+ /**
+ * Loads the database instance, if the database is not already loaded.
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ if ( ! is_object($this->db))
+ {
+ // Load the default database
+ $this->db = Database::instance($this->db);
+ }
+ }
+
+} // End Model \ No newline at end of file
diff --git a/system/libraries/ORM.php b/system/libraries/ORM.php
new file mode 100644
index 0000000..16e047b
--- /dev/null
+++ b/system/libraries/ORM.php
@@ -0,0 +1,1528 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * [Object Relational Mapping][ref-orm] (ORM) is a method of abstracting database
+ * access to standard PHP calls. All table rows are represented as model objects,
+ * with object properties representing row data. ORM in Kohana generally follows
+ * the [Active Record][ref-act] pattern.
+ *
+ * [ref-orm]: http://wikipedia.org/wiki/Object-relational_mapping
+ * [ref-act]: http://wikipedia.org/wiki/Active_record
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class ORM_Core {
+
+ // Current relationships
+ protected $has_one = array();
+ protected $belongs_to = array();
+ protected $has_many = array();
+ protected $has_and_belongs_to_many = array();
+ protected $has_many_through = array();
+
+ // Relationships that should always be joined
+ protected $load_with = array();
+
+ // Current object
+ protected $object = array();
+ protected $changed = array();
+ protected $related = array();
+ protected $_valid = FALSE;
+ protected $_loaded = FALSE;
+ protected $_saved = FALSE;
+ protected $sorting;
+ protected $rules = array();
+
+ // Related objects
+ protected $object_relations = array();
+ protected $changed_relations = array();
+
+ // Model table information
+ protected $object_name;
+ protected $object_plural;
+ protected $table_name;
+ protected $table_columns;
+ protected $ignored_columns;
+
+ // Auto-update columns for creation and updates
+ protected $updated_column = NULL;
+ protected $created_column = NULL;
+
+ // Table primary key and value
+ protected $primary_key = 'id';
+ protected $primary_val = 'name';
+
+ // Array of foreign key name overloads
+ protected $foreign_key = array();
+
+ // Model configuration
+ protected $table_names_plural = TRUE;
+ protected $reload_on_wakeup = TRUE;
+
+ // Database configuration
+ protected $db = 'default';
+ protected $db_applied = array();
+ protected $db_builder;
+
+ // With calls already applied
+ protected $with_applied = array();
+
+ // Stores column information for ORM models
+ protected static $column_cache = array();
+
+ /**
+ * Creates and returns a new model.
+ *
+ * @chainable
+ * @param string model name
+ * @param mixed parameter for find()
+ * @return ORM
+ */
+ public static function factory($model, $id = NULL)
+ {
+ // Set class name
+ $model = ucfirst($model).'_Model';
+
+ return new $model($id);
+ }
+
+ /**
+ * Prepares the model database connection and loads the object.
+ *
+ * @param mixed parameter for find or object to load
+ * @return void
+ */
+ public function __construct($id = NULL)
+ {
+ // Set the object name and plural name
+ $this->object_name = strtolower(substr(get_class($this), 0, -6));
+ $this->object_plural = inflector::plural($this->object_name);
+
+ if ( ! isset($this->sorting))
+ {
+ // Default sorting
+ $this->sorting = array($this->primary_key => 'asc');
+ }
+
+ // Initialize database
+ $this->__initialize();
+
+ // Clear the object
+ $this->clear();
+
+ if (is_object($id))
+ {
+ // Load an object
+ $this->_load_values((array) $id);
+ }
+ elseif ( ! empty($id))
+ {
+ // Set the object's primary key, but don't load it until needed
+ $this->object[$this->primary_key] = $id;
+
+ // Object is considered saved until something is set
+ $this->_saved = TRUE;
+ }
+ }
+
+ /**
+ * Prepares the model database connection, determines the table name,
+ * and loads column information.
+ *
+ * @return void
+ */
+ public function __initialize()
+ {
+ if ( ! is_object($this->db))
+ {
+ // Get database instance
+ $this->db = Database::instance($this->db);
+ }
+
+ if (empty($this->table_name))
+ {
+ // Table name is the same as the object name
+ $this->table_name = $this->object_name;
+
+ if ($this->table_names_plural === TRUE)
+ {
+ // Make the table name plural
+ $this->table_name = inflector::plural($this->table_name);
+ }
+ }
+
+ if (is_array($this->ignored_columns))
+ {
+ // Make the ignored columns mirrored = mirrored
+ $this->ignored_columns = array_combine($this->ignored_columns, $this->ignored_columns);
+ }
+
+ // Load column information
+ $this->reload_columns();
+
+ // Initialize the builder
+ $this->db_builder = db::build();
+ }
+
+ /**
+ * Allows serialization of only the object data and state, to prevent
+ * "stale" objects being unserialized, which also requires less memory.
+ *
+ * @return array
+ */
+ public function __sleep()
+ {
+ // Store only information about the object
+ return array('object_name', 'object', 'changed', '_loaded', '_saved', 'sorting');
+ }
+
+ /**
+ * Prepares the database connection and reloads the object.
+ *
+ * @return void
+ */
+ public function __wakeup()
+ {
+ // Initialize database
+ $this->__initialize();
+
+ if ($this->reload_on_wakeup === TRUE)
+ {
+ // Reload the object
+ $this->reload();
+ }
+ }
+
+ /**
+ * Handles pass-through to database methods. Calls to query methods
+ * (query, get, insert, update) are not allowed. Query builder methods
+ * are chainable.
+ *
+ * @param string method name
+ * @param array method arguments
+ * @return mixed
+ */
+ public function __call($method, array $args)
+ {
+ if (method_exists($this->db_builder, $method))
+ {
+ if (in_array($method, array('execute', 'insert', 'update', 'delete')))
+ throw new Kohana_Exception('Query methods cannot be used through ORM');
+
+ // Method has been applied to the database
+ $this->db_applied[$method] = $method;
+
+ // Number of arguments passed
+ $num_args = count($args);
+
+ if ($method === 'select' AND $num_args > 3)
+ {
+ // Call select() manually to avoid call_user_func_array
+ $this->db_builder->select($args);
+ }
+ else
+ {
+ // We use switch here to manually call the database methods. This is
+ // done for speed: call_user_func_array can take over 300% longer to
+ // make calls. Most database methods are 4 arguments or less, so this
+ // avoids almost any calls to call_user_func_array.
+
+ switch ($num_args)
+ {
+ case 0:
+ if (in_array($method, array('open', 'and_open', 'or_open', 'close', 'cache')))
+ {
+ // Should return ORM, not Database
+ $this->db_builder->$method();
+ }
+ else
+ {
+ // Support for things like reset_select, reset_write, list_tables
+ return $this->db_builder->$method();
+ }
+ break;
+ case 1:
+ $this->db_builder->$method($args[0]);
+ break;
+ case 2:
+ $this->db_builder->$method($args[0], $args[1]);
+ break;
+ case 3:
+ $this->db_builder->$method($args[0], $args[1], $args[2]);
+ break;
+ case 4:
+ $this->db_builder->$method($args[0], $args[1], $args[2], $args[3]);
+ break;
+ default:
+ // Here comes the snail...
+ call_user_func_array(array($this->db, $method), $args);
+ break;
+ }
+ }
+
+ return $this;
+ }
+ else
+ {
+ throw new Kohana_Exception('Invalid method :method called in :class',
+ array(':method' => $method, ':class' => get_class($this)));
+ }
+ }
+
+ /**
+ * Handles retrieval of all model values, relationships, and metadata.
+ *
+ * @param string column name
+ * @return mixed
+ */
+ public function __get($column)
+ {
+ if (array_key_exists($column, $this->object))
+ {
+ if( ! $this->loaded() AND ! $this->empty_primary_key())
+ {
+ // Column asked for but the object hasn't been loaded yet, so do it now
+ // Ignore loading of any columns that have been changed
+ $this->find($this->object[$this->primary_key], TRUE);
+ }
+
+ return $this->object[$column];
+ }
+ elseif (isset($this->related[$column]))
+ {
+ return $this->related[$column];
+ }
+ elseif ($column === 'primary_key_value')
+ {
+ if( ! $this->loaded() AND ! $this->empty_primary_key() AND $this->unique_key($this->object[$this->primary_key]) !== $this->primary_key)
+ {
+ // Load if object hasn't been loaded and the key given isn't the primary_key
+ // that we need (i.e. passing an email address to ORM::factory rather than the id)
+ $this->find($this->object[$this->primary_key], TRUE);
+ }
+
+ return $this->object[$this->primary_key];
+ }
+ elseif ($model = $this->related_object($column))
+ {
+ // This handles the has_one and belongs_to relationships
+
+ if (in_array($model->object_name, $this->belongs_to))
+ {
+ if ( ! $this->loaded() AND ! $this->empty_primary_key())
+ {
+ // Load this object first so we know what id to look for in the foreign table
+ $this->find($this->object[$this->primary_key], TRUE);
+ }
+
+ // Foreign key lies in this table (this model belongs_to target model)
+ $where = array($model->foreign_key(TRUE), '=', $this->object[$this->foreign_key($column)]);
+ }
+ else
+ {
+ // Foreign key lies in the target table (this model has_one target model)
+ $where = array($this->foreign_key($column, $model->table_name), '=', $this->primary_key_value);
+ }
+
+ // one<>alias:one relationship
+ return $this->related[$column] = $model->find($where);
+ }
+ elseif (isset($this->has_many_through[$column]))
+ {
+ // Load the "middle" model
+ $through = ORM::factory(inflector::singular($this->has_many_through[$column]));
+
+ // Load the "end" model
+ $model = ORM::factory(inflector::singular($column));
+
+ // Join ON target model's primary key set to 'through' model's foreign key
+ // User-defined foreign keys must be defined in the 'through' model
+ $join_table = $through->table_name;
+ $join_col1 = $through->foreign_key($model->object_name, $join_table);
+ $join_col2 = $model->foreign_key(TRUE);
+
+ // one<>alias:many relationship
+ return $model
+ ->join($join_table, $join_col1, $join_col2)
+ ->where($through->foreign_key($this->object_name, $join_table), '=', $this->primary_key_value);
+ }
+ elseif (isset($this->has_many[$column]))
+ {
+ // one<>many aliased relationship
+ $model_name = $this->has_many[$column];
+
+ $model = ORM::factory(inflector::singular($model_name));
+
+ return $model->where($this->foreign_key($column, $model->table_name), '=', $this->primary_key_value);
+ }
+ elseif (in_array($column, $this->has_many))
+ {
+ // one<>many relationship
+ $model = ORM::factory(inflector::singular($column));
+ return $model->where($this->foreign_key($column, $model->table_name), '=', $this->primary_key_value);
+ }
+ elseif (in_array($column, $this->has_and_belongs_to_many))
+ {
+ // Load the remote model, always singular
+ $model = ORM::factory(inflector::singular($column));
+
+ if ($this->has($model, TRUE))
+ {
+ // many<>many relationship
+ return $model->where($model->foreign_key(TRUE), 'IN', $this->changed_relations[$column]);
+ }
+ else
+ {
+ // empty many<>many relationship
+ return $model->where($model->foreign_key(TRUE), 'IS', NULL);
+ }
+ }
+ elseif (isset($this->ignored_columns[$column]))
+ {
+ return NULL;
+ }
+ elseif (in_array($column, array
+ (
+ 'object_name', 'object_plural','_valid', // Object
+ 'primary_key', 'primary_val', 'table_name', 'table_columns', // Table
+ 'has_one', 'belongs_to', 'has_many', 'has_many_through', 'has_and_belongs_to_many', 'load_with' // Relationships
+ )))
+ {
+ // Model meta information
+ return $this->$column;
+ }
+ else
+ {
+ throw new Kohana_Exception('The :property property does not exist in the :class class',
+ array(':property' => $column, ':class' => get_class($this)));
+ }
+ }
+
+ /**
+ * Tells you if the Model has been loaded or not
+ *
+ * @return bool
+ */
+ public function loaded() {
+ if ( ! $this->_loaded AND ! $this->empty_primary_key())
+ {
+ // If returning the loaded member and no load has been attempted, do it now
+ $this->find($this->object[$this->primary_key], TRUE);
+ }
+ return $this->_loaded;
+ }
+
+ /**
+ * Tells you if the model was saved successfully or not
+ *
+ * @return bool
+ */
+ public function saved() {
+ return $this->_saved;
+ }
+
+ /**
+ * Handles setting of all model values, and tracks changes between values.
+ *
+ * @param string column name
+ * @param mixed column value
+ * @return void
+ */
+ public function __set($column, $value)
+ {
+ if (isset($this->ignored_columns[$column]))
+ {
+ return NULL;
+ }
+ elseif (isset($this->object[$column]) OR array_key_exists($column, $this->object))
+ {
+ if (isset($this->table_columns[$column]))
+ {
+ // Data has changed
+ $this->changed[$column] = $column;
+
+ // Object is no longer saved or valid
+ $this->_saved = $this->_valid = FALSE;
+ }
+
+ $this->object[$column] = $value;
+ }
+ elseif (in_array($column, $this->has_and_belongs_to_many) AND is_array($value))
+ {
+ // Load relations
+ $model = ORM::factory(inflector::singular($column));
+
+ if ( ! isset($this->object_relations[$column]))
+ {
+ // Load relations
+ $this->has($model);
+ }
+
+ // Change the relationships
+ $this->changed_relations[$column] = $value;
+
+ if (isset($this->related[$column]))
+ {
+ // Force a reload of the relationships
+ unset($this->related[$column]);
+ }
+ }
+ else
+ {
+ throw new Kohana_Exception('The :property: property does not exist in the :class: class',
+ array(':property:' => $column, ':class:' => get_class($this)));
+ }
+ }
+
+ /**
+ * Chainable set method
+ *
+ * @param string name of field or array of key => val
+ * @param mixed value
+ * @return ORM
+ */
+ public function set($name, $value = NULL)
+ {
+ if (is_array($name))
+ {
+ foreach ($name as $key => $value)
+ {
+ $this->__set($key, $value);
+ }
+ }
+ else
+ {
+ $this->__set($name, $value);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Checks if object data is set.
+ *
+ * @param string column name
+ * @return boolean
+ */
+ public function __isset($column)
+ {
+ return (isset($this->object[$column]) OR isset($this->related[$column]));
+ }
+
+ /**
+ * Unsets object data.
+ *
+ * @param string column name
+ * @return void
+ */
+ public function __unset($column)
+ {
+ unset($this->object[$column], $this->changed[$column], $this->related[$column]);
+ }
+
+ /**
+ * Returns the values of this object as an array.
+ *
+ * @return array
+ */
+ public function as_array()
+ {
+ $object = array();
+
+ foreach ($this->object as $key => $val)
+ {
+ // Reconstruct the array (calls __get)
+ $object[$key] = $this->$key;
+ }
+
+ foreach ($this->with_applied as $model => $enabled)
+ {
+ // Generate arrays for relationships
+ if ($enabled)
+ {
+ $object[$model] = $this->$model->as_array();
+ }
+ }
+
+ return $object;
+ }
+
+ /**
+ * Binds another one-to-one object to this model. One-to-one objects
+ * can be nested using 'object1:object2' syntax
+ *
+ * @param string target model to bind to
+ * @return void
+ */
+ public function with($target_path)
+ {
+ if (isset($this->with_applied[$target_path]))
+ {
+ // Don't join anything already joined
+ return $this;
+ }
+
+ // Split object parts
+ $objects = explode(':', $target_path);
+ $target = $this;
+ foreach ($objects as $object)
+ {
+ // Go down the line of objects to find the given target
+ $parent = $target;
+ $target = $parent->related_object($object);
+
+ if ( ! $target)
+ {
+ // Can't find related object
+ return $this;
+ }
+ }
+
+ $target_name = $object;
+
+ // Pop-off top object to get the parent object (user:photo:tag becomes user:photo - the parent table prefix)
+ array_pop($objects);
+ $parent_path = implode(':', $objects);
+
+ if (empty($parent_path))
+ {
+ // Use this table name itself for the parent object
+ $parent_path = $this->table_name;
+ }
+ else
+ {
+ if( ! isset($this->with_applied[$parent_path]))
+ {
+ // If the parent object hasn't been joined yet, do it first (otherwise LEFT JOINs fail)
+ $this->with($parent_path);
+ }
+ }
+
+ // Add to with_applied to prevent duplicate joins
+ $this->with_applied[$target_path] = TRUE;
+
+ $select = array();
+
+ // Use the keys of the empty object to determine the columns
+ foreach (array_keys($target->object) as $column)
+ {
+ // Add the prefix so that load_result can determine the relationship
+ $select[$target_path.':'.$column] = $target_path.'.'.$column;
+ }
+
+ // Select all of the prefixed keys in the object
+ $this->db_builder->select($select);
+
+ if (in_array($target->object_name, $parent->belongs_to))
+ {
+ // Parent belongs_to target, use target's primary key as join column
+ $join_col1 = $target->foreign_key(TRUE, $target_path);
+ $join_col2 = $parent->foreign_key($target_name, $parent_path);
+ }
+ else
+ {
+ // Parent has_one target, use parent's primary key as join column
+ $join_col2 = $parent->foreign_key(TRUE, $parent_path);
+ $join_col1 = $parent->foreign_key($target_name, $target_path);
+ }
+
+ // This trick allows for models to use different table prefixes (sharing the same database)
+ $join_table = array($this->db->quote_table($target_path) => $target->db->quote_table($target->table_name));
+
+ // Join the related object into the result
+ // Use Database_Expression to disable prefixing
+ $this->db_builder->join(new Database_Expression($join_table), $join_col1, $join_col2, 'LEFT');
+
+ return $this;
+ }
+
+ /**
+ * Finds and loads a single database row into the object.
+ *
+ * @chainable
+ * @param mixed primary key or an array of clauses
+ * @param bool ignore loading of columns that have been modified
+ * @return ORM
+ */
+ public function find($id = NULL, $ignore_changed = FALSE)
+ {
+ if ($id !== NULL)
+ {
+ if (is_array($id))
+ {
+ // Search for all clauses
+ $this->db_builder->where(array($id));
+ }
+ else
+ {
+ // Search for a specific column
+ $this->db_builder->where($this->table_name.'.'.$this->unique_key($id), '=', $id);
+ }
+ }
+
+ return $this->load_result(FALSE, $ignore_changed);
+ }
+
+ /**
+ * Finds multiple database rows and returns an iterator of the rows found.
+ *
+ * @chainable
+ * @param integer SQL limit
+ * @param integer SQL offset
+ * @return ORM_Iterator
+ */
+ public function find_all($limit = NULL, $offset = NULL)
+ {
+ if ($limit !== NULL AND ! isset($this->db_applied['limit']))
+ {
+ // Set limit
+ $this->limit($limit);
+ }
+
+ if ($offset !== NULL AND ! isset($this->db_applied['offset']))
+ {
+ // Set offset
+ $this->offset($offset);
+ }
+
+ return $this->load_result(TRUE);
+ }
+
+ /**
+ * Creates a key/value array from all of the objects available. Uses find_all
+ * to find the objects.
+ *
+ * @param string key column
+ * @param string value column
+ * @return array
+ */
+ public function select_list($key = NULL, $val = NULL)
+ {
+ if ($key === NULL)
+ {
+ $key = $this->primary_key;
+ }
+
+ if ($val === NULL)
+ {
+ $val = $this->primary_val;
+ }
+
+ // Return a select list from the results
+ return $this->select($this->table_name.'.'.$key, $this->table_name.'.'.$val)->find_all()->select_list($key, $val);
+ }
+
+ /**
+ * Validates the current object. This method requires that rules are set to be useful, if called with out
+ * any rules set, or if a Validation object isn't passed, nothing happens.
+ *
+ * @param object Validation array
+ * @param boolean Save on validate
+ * @return ORM
+ * @chainable
+ */
+ public function validate(Validation $array = NULL)
+ {
+ if ($array === NULL)
+ $array = new Validation($this->object);
+
+ if (count($this->rules) > 0)
+ {
+ foreach ($this->rules as $field => $parameters)
+ {
+ foreach ($parameters as $type => $value) {
+ switch ($type) {
+ case 'pre_filter':
+ $array->pre_filter($value,$field);
+ break;
+ case 'rules':
+ $rules = array_merge(array($field),$value);
+ call_user_func_array(array($array,'add_rules'), $rules);
+ break;
+ case 'callbacks':
+ $callbacks = array_merge(array($field),$value);
+ call_user_func_array(array($array,'add_callbacks'), $callbacks);
+ break;
+ }
+ }
+ }
+ }
+ // Validate the array
+ if (($this->_valid = $array->validate()) === FALSE)
+ {
+ ORM_Validation_Exception::handle_validation($this->table_name, $array);
+ }
+
+ // Fields may have been modified by filters
+ $this->object = array_merge($this->object, $array->getArrayCopy());
+
+ // Return validation status
+ return $this;
+ }
+
+ /**
+ * Saves the current object.
+ *
+ * @chainable
+ * @return ORM
+ */
+ public function save()
+ {
+ if ( ! empty($this->changed))
+ {
+ // Require model validation before saving
+ if ( ! $this->_valid)
+ {
+ $this->validate();
+ }
+
+ $data = array();
+ foreach ($this->changed as $column)
+ {
+ // Compile changed data
+ $data[$column] = $this->object[$column];
+ }
+
+ if ( ! $this->empty_primary_key() AND ! isset($this->changed[$this->primary_key]))
+ {
+ // Primary key isn't empty and hasn't been changed so do an update
+
+ if (is_array($this->updated_column))
+ {
+ // Fill the updated column
+ $column = $this->updated_column['column'];
+ $format = $this->updated_column['format'];
+
+ $data[$column] = $this->object[$column] = ($format === TRUE) ? time() : date($format);
+ }
+
+ $query = db::update($this->table_name)
+ ->set($data)
+ ->where($this->primary_key, '=', $this->primary_key_value)
+ ->execute($this->db);
+
+ // Object has been saved
+ $this->_saved = TRUE;
+ }
+ else
+ {
+ if (is_array($this->created_column))
+ {
+ // Fill the created column
+ $column = $this->created_column['column'];
+ $format = $this->created_column['format'];
+
+ $data[$column] = $this->object[$column] = ($format === TRUE) ? time() : date($format);
+ }
+
+ $result = db::insert($this->table_name)
+ ->columns(array_keys($data))
+ ->values(array_values($data))
+ ->execute($this->db);
+
+ if ($result->count() > 0)
+ {
+ if (empty($this->object[$this->primary_key]))
+ {
+ // Load the insert id as the primary key
+ $this->object[$this->primary_key] = $result->insert_id();
+ }
+
+ // Object is now loaded and saved
+ $this->_loaded = $this->_saved = TRUE;
+ }
+ }
+
+ if ($this->saved() === TRUE)
+ {
+ // All changes have been saved
+ $this->changed = array();
+ }
+ }
+
+ if ($this->saved() === TRUE AND ! empty($this->changed_relations))
+ {
+ foreach ($this->changed_relations as $column => $values)
+ {
+ // All values that were added
+ $added = array_diff($values, $this->object_relations[$column]);
+
+ // All values that were saved
+ $removed = array_diff($this->object_relations[$column], $values);
+
+ if (empty($added) AND empty($removed))
+ {
+ // No need to bother
+ continue;
+ }
+
+ // Clear related columns
+ unset($this->related[$column]);
+
+ // Load the model
+ $model = ORM::factory(inflector::singular($column));
+
+ if (($join_table = array_search($column, $this->has_and_belongs_to_many)) === FALSE)
+ continue;
+
+ if (is_int($join_table))
+ {
+ // No "through" table, load the default JOIN table
+ $join_table = $model->join_table($this->table_name);
+ }
+
+ // Foreign keys for the join table
+ $object_fk = $this->foreign_key($join_table);
+ $related_fk = $model->foreign_key($join_table);
+
+ if ( ! empty($added))
+ {
+ foreach ($added as $id)
+ {
+ // Insert the new relationship
+ db::insert($join_table)
+ ->columns($object_fk, $related_fk)
+ ->values($this->primary_key_value, $id)
+ ->execute($this->db);
+ }
+ }
+
+ if ( ! empty($removed))
+ {
+ db::delete($join_table)
+ ->where($object_fk, '=', $this->primary_key_value)
+ ->where($related_fk, 'IN', $removed)
+ ->execute($this->db);
+ }
+
+ // Clear all relations for this column
+ unset($this->object_relations[$column], $this->changed_relations[$column]);
+ }
+ }
+
+ if ($this->saved() === TRUE)
+ {
+ // Always force revalidation after saving
+ $this->_valid = FALSE;
+
+ // Clear the per-request database cache
+ $this->db->clear_cache(NULL, Database::PER_REQUEST);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Deletes the current object from the database. This does NOT destroy
+ * relationships that have been created with other objects.
+ *
+ * @chainable
+ * @param mixed id to delete
+ * @return ORM
+ */
+ public function delete($id = NULL)
+ {
+ if ($id === NULL)
+ {
+ // Use the the primary key value
+ $id = $this->primary_key_value;
+ }
+
+ // Delete this object
+ db::delete($this->table_name)
+ ->where($this->primary_key, '=', $id)
+ ->execute($this->db);
+
+ // Clear the per-request database cache
+ $this->db->clear_cache(NULL, Database::PER_REQUEST);
+
+ return $this->clear();
+ }
+
+ /**
+ * Delete all objects in the associated table. This does NOT destroy
+ * relationships that have been created with other objects.
+ *
+ * @chainable
+ * @param array ids to delete
+ * @return ORM
+ */
+ public function delete_all($ids = NULL)
+ {
+ if (is_array($ids))
+ {
+ // Delete only given ids
+ db::delete($this->table_name)
+ ->where($this->primary_key, 'IN', $ids)
+ ->execute($this->db);
+ }
+ elseif ($ids === NULL)
+ {
+ // Delete all records
+ db::delete($this->table_name)
+ ->execute($this->db);
+ }
+ else
+ {
+ // Do nothing - safeguard
+ return $this;
+ }
+
+ // Clear the per-request database cache
+ $this->db->clear_cache(NULL, Database::PER_REQUEST);
+
+ return $this->clear();
+ }
+
+ /**
+ * Unloads the current object and clears the status.
+ *
+ * @chainable
+ * @return ORM
+ */
+ public function clear()
+ {
+ // Create an array with all the columns set to NULL
+ $columns = array_keys($this->table_columns);
+ $values = array_combine($columns, array_fill(0, count($columns), NULL));
+
+ // Replace the current object with an empty one
+ $this->_load_values($values);
+
+ return $this;
+ }
+
+ /**
+ * Reloads the current object from the database.
+ *
+ * @chainable
+ * @return ORM
+ */
+ public function reload()
+ {
+ if ($this->_loaded) {
+ return $this->find($this->object[$this->primary_key]);
+ } else {
+ return $this->clear();
+ }
+ }
+
+ /**
+ * Reload column definitions.
+ *
+ * @chainable
+ * @param boolean force reloading
+ * @return ORM
+ */
+ public function reload_columns($force = FALSE)
+ {
+ if ($force === TRUE OR empty($this->table_columns))
+ {
+ if (isset(ORM::$column_cache[$this->object_name]))
+ {
+ // Use cached column information
+ $this->table_columns = ORM::$column_cache[$this->object_name];
+ }
+ else
+ {
+ // Load table columns
+ ORM::$column_cache[$this->object_name] = $this->table_columns = $this->list_fields();
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Tests if this object has a relationship to a different model.
+ *
+ * @param object related ORM model
+ * @param boolean check for any relations to given model
+ * @return boolean
+ */
+ public function has(ORM $model, $any = FALSE)
+ {
+ // Determine plural or singular relation name
+ $related = ($model->table_names_plural === TRUE) ? $model->object_plural : $model->object_name;
+
+ if (($join_table = array_search($related, $this->has_and_belongs_to_many)) === FALSE)
+ return FALSE;
+
+ if (is_int($join_table))
+ {
+ // No "through" table, load the default JOIN table
+ $join_table = $model->join_table($this->table_name);
+ }
+
+ if ( ! isset($this->object_relations[$related]))
+ {
+ // Load the object relationships
+ $this->changed_relations[$related] = $this->object_relations[$related] = $this->load_relations($join_table, $model);
+ }
+
+ if( ! $model->loaded() AND ! $model->empty_primary_key())
+ {
+ // Load the related model if it hasn't already been
+ $model->find($model->object[$model->primary_key]);
+ }
+
+ if ( ! $model->empty_primary_key())
+ {
+ // Check if a specific object exists
+ return in_array($model->primary_key_value, $this->changed_relations[$related]);
+ }
+ elseif ($any)
+ {
+ // Check if any relations to given model exist
+ return ! empty($this->changed_relations[$related]);
+ }
+ else
+ {
+ return FALSE;
+ }
+ }
+
+ /**
+ * Adds a new relationship to between this model and another.
+ *
+ * @param object related ORM model
+ * @return boolean
+ */
+ public function add(ORM $model)
+ {
+ if ($this->has($model))
+ return TRUE;
+
+ // Get the faked column name
+ $column = $model->object_plural;
+
+ // Add the new relation to the update
+ $this->changed_relations[$column][] = $model->primary_key_value;
+
+ if (isset($this->related[$column]))
+ {
+ // Force a reload of the relationships
+ unset($this->related[$column]);
+ }
+
+ return TRUE;
+ }
+
+ /**
+ * Adds a new relationship to between this model and another.
+ *
+ * @param object related ORM model
+ * @return boolean
+ */
+ public function remove(ORM $model)
+ {
+ if ( ! $this->has($model))
+ return FALSE;
+
+ // Get the faked column name
+ $column = $model->object_plural;
+
+ if (($key = array_search($model->primary_key_value, $this->changed_relations[$column])) === FALSE)
+ return FALSE;
+
+ // Remove the relationship
+ unset($this->changed_relations[$column][$key]);
+
+ if (isset($this->related[$column]))
+ {
+ // Force a reload of the relationships
+ unset($this->related[$column]);
+ }
+
+ return TRUE;
+ }
+
+ /**
+ * Count the number of records in the table.
+ *
+ * @return integer
+ */
+ public function count_all()
+ {
+ // Return the total number of records in a table
+ return $this->db_builder->count_records($this->table_name);
+ }
+
+ /**
+ * Proxy method to Database list_fields.
+ *
+ * @return array
+ */
+ public function list_fields()
+ {
+ // Proxy to database
+ return $this->db->list_fields($this->table_name);
+ }
+
+ /**
+ * Proxy method to Database clear_cache.
+ *
+ * @chainable
+ * @param string SQL query to clear
+ * @param integer Type of cache to clear, Database::CROSS_REQUEST or Database::PER_REQUEST
+ * @return ORM
+ */
+ public function clear_cache($sql = NULL, $type = NULL)
+ {
+ // Proxy to database
+ $this->db->clear_cache($sql, $type);
+
+ ORM::$column_cache = array();
+
+ return $this;
+ }
+
+ /**
+ * Returns the unique key for a specific value. This method is expected
+ * to be overloaded in models if the model has other unique columns.
+ *
+ * @param mixed unique value
+ * @return string
+ */
+ public function unique_key($id)
+ {
+ return $this->primary_key;
+ }
+
+ /**
+ * Determines the name of a foreign key for a specific table.
+ *
+ * @param string related table name
+ * @param string prefix table name (used for JOINs)
+ * @return string
+ */
+ public function foreign_key($table = NULL, $prefix_table = NULL)
+ {
+ if ($table === TRUE)
+ {
+ if (is_string($prefix_table))
+ {
+ // Use prefix table name and this table's PK
+ return $prefix_table.'.'.$this->primary_key;
+ }
+ else
+ {
+ // Return the name of this table's PK
+ return $this->table_name.'.'.$this->primary_key;
+ }
+ }
+
+ if (is_string($prefix_table))
+ {
+ // Add a period for prefix_table.column support
+ $prefix_table .= '.';
+ }
+
+ if (isset($this->foreign_key[$table]))
+ {
+ // Use the defined foreign key name, no magic here!
+ $foreign_key = $this->foreign_key[$table];
+ }
+ else
+ {
+ if ( ! is_string($table) OR ! array_key_exists($table.'_'.$this->primary_key, $this->object))
+ {
+ // Use this table
+ $table = $this->table_name;
+
+ if (strpos($table, '.') !== FALSE)
+ {
+ // Hack around support for PostgreSQL schemas
+ list ($schema, $table) = explode('.', $table, 2);
+ }
+
+ if ($this->table_names_plural === TRUE)
+ {
+ // Make the key name singular
+ $table = inflector::singular($table);
+ }
+ }
+
+ $foreign_key = $table.'_'.$this->primary_key;
+ }
+
+ return $prefix_table.$foreign_key;
+ }
+
+ /**
+ * This uses alphabetical comparison to choose the name of the table.
+ *
+ * Example: The joining table of users and roles would be roles_users,
+ * because "r" comes before "u". Joining products and categories would
+ * result in categories_products, because "c" comes before "p".
+ *
+ * Example: zoo > zebra > robber > ocean > angel > aardvark
+ *
+ * @param string table name
+ * @return string
+ */
+ public function join_table($table)
+ {
+ if ($this->table_name > $table)
+ {
+ $table = $table.'_'.$this->table_name;
+ }
+ else
+ {
+ $table = $this->table_name.'_'.$table;
+ }
+
+ return $table;
+ }
+
+ /**
+ * Returns an ORM model for the given object name;
+ *
+ * @param string object name
+ * @return ORM
+ */
+ protected function related_object($object)
+ {
+ if (isset($this->has_one[$object]))
+ {
+ $object = ORM::factory($this->has_one[$object]);
+ }
+ elseif (isset($this->belongs_to[$object]))
+ {
+ $object = ORM::factory($this->belongs_to[$object]);
+ }
+ elseif (in_array($object, $this->has_one) OR in_array($object, $this->belongs_to))
+ {
+ $object = ORM::factory($object);
+ }
+ else
+ {
+ return FALSE;
+ }
+
+ return $object;
+ }
+
+ /**
+ * Loads an array of values into into the current object.
+ *
+ * @chainable
+ * @param array values to load
+ * @return ORM
+ */
+ public function load_values(array $values)
+ {
+ // Related objects
+ $related = array();
+
+ foreach ($values as $column => $value)
+ {
+ if (strpos($column, ':') === FALSE)
+ {
+ if ( ! isset($this->changed[$column]))
+ {
+ if (isset($this->table_columns[$column]))
+ {
+ //Update the column, respects __get()
+ $this->$column = $value;
+ }
+ }
+ }
+ else
+ {
+ list ($prefix, $column) = explode(':', $column, 2);
+
+ $related[$prefix][$column] = $value;
+ }
+ }
+
+ if ( ! empty($related))
+ {
+ foreach ($related as $object => $values)
+ {
+ // Load the related objects with the values in the result
+ $this->related[$object] = $this->related_object($object)->load_values($values);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Loads an array of values into into the current object. Only used internally
+ *
+ * @chainable
+ * @param array values to load
+ * @param bool ignore loading of columns that have been modified
+ * @return ORM
+ */
+ public function _load_values(array $values, $ignore_changed = FALSE)
+ {
+ if (array_key_exists($this->primary_key, $values))
+ {
+ if ( ! $ignore_changed)
+ {
+ // Replace the object and reset the object status
+ $this->object = $this->changed = $this->related = array();
+ }
+
+ // Set the loaded and saved object status based on the primary key
+ $this->_loaded = $this->_saved = ($values[$this->primary_key] !== NULL);
+ }
+
+ // Related objects
+ $related = array();
+
+ foreach ($values as $column => $value)
+ {
+ if (strpos($column, ':') === FALSE)
+ {
+ if ( ! $ignore_changed OR ! isset($this->changed[$column]))
+ {
+ $this->object[$column] = $value;
+ }
+ }
+ else
+ {
+ list ($prefix, $column) = explode(':', $column, 2);
+
+ $related[$prefix][$column] = $value;
+ }
+ }
+
+ if ( ! empty($related))
+ {
+ foreach ($related as $object => $values)
+ {
+ // Load the related objects with the values in the result
+ $this->related[$object] = $this->related_object($object)->_load_values($values);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Loads a database result, either as a new object for this model, or as
+ * an iterator for multiple rows.
+ *
+ * @chainable
+ * @param boolean return an iterator or load a single row
+ * @param boolean ignore loading of columns that have been modified
+ * @return ORM for single rows
+ * @return ORM_Iterator for multiple rows
+ */
+ protected function load_result($array = FALSE, $ignore_changed = FALSE)
+ {
+ $this->db_builder->from($this->table_name);
+
+ if ($array === FALSE)
+ {
+ // Only fetch 1 record
+ $this->db_builder->limit(1);
+ }
+
+ if ( ! isset($this->db_applied['select']))
+ {
+ // Select all columns by default
+ $this->db_builder->select($this->table_name.'.*');
+ }
+
+ if ( ! empty($this->load_with))
+ {
+ foreach ($this->load_with as $alias => $object)
+ {
+ // Join each object into the results
+ if (is_string($alias))
+ {
+ // Use alias
+ $this->with($alias);
+ }
+ else
+ {
+ // Use object
+ $this->with($object);
+ }
+ }
+ }
+
+ if ( ! isset($this->db_applied['order_by']) AND ! empty($this->sorting))
+ {
+ $sorting = array();
+ foreach ($this->sorting as $column => $direction)
+ {
+ if (strpos($column, '.') === FALSE)
+ {
+ // Keeps sorting working properly when using JOINs on
+ // tables with columns of the same name
+ $column = $this->table_name.'.'.$column;
+ }
+
+ $sorting[$column] = $direction;
+ }
+
+ // Apply the user-defined sorting
+ $this->db_builder->order_by($sorting);
+ }
+
+ // Load the result
+ $result = $this->db_builder->execute($this->db);
+ $this->db_applied = array();
+
+ if ($array === TRUE)
+ {
+ // Return an iterated result
+ return new ORM_Iterator($this, $result);
+ }
+
+ if ($result->count() === 1)
+ {
+ // Load object values
+ $this->_load_values($result->as_array()->current(), $ignore_changed);
+ }
+ else
+ {
+ // Clear the object, nothing was found
+ $this->clear();
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return an array of all the primary keys of the related table.
+ *
+ * @param string table name
+ * @param object ORM model to find relations of
+ * @return array
+ */
+ protected function load_relations($table, ORM $model)
+ {
+ $result = db::select(array('id' => $model->foreign_key($table)))
+ ->from($table)
+ ->where($this->foreign_key($table, $table), '=', $this->primary_key_value)
+ ->execute($this->db)
+ ->as_object();
+
+ $relations = array();
+ foreach ($result as $row)
+ {
+ $relations[] = $row->id;
+ }
+
+ return $relations;
+ }
+
+ /**
+ * Returns whether or not primary key is empty
+ *
+ * @return bool
+ */
+ protected function empty_primary_key()
+ {
+ return (empty($this->object[$this->primary_key]) AND $this->object[$this->primary_key] !== '0');
+ }
+
+} // End ORM
diff --git a/system/libraries/ORM_Iterator.php b/system/libraries/ORM_Iterator.php
new file mode 100644
index 0000000..0bf2b47
--- /dev/null
+++ b/system/libraries/ORM_Iterator.php
@@ -0,0 +1,266 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Object Relational Mapping (ORM) result iterator.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class ORM_Iterator_Core implements Iterator, ArrayAccess, Countable {
+
+ // Class attributes
+ protected $class_name;
+ protected $primary_key;
+ protected $primary_val;
+
+ // Database result object
+ protected $result;
+
+ public function __construct(ORM $model, Database_Result $result)
+ {
+ // Class attributes
+ $this->class_name = get_class($model);
+ $this->primary_key = $model->primary_key;
+ $this->primary_val = $model->primary_val;
+
+ // Database result (make sure rows are returned as arrays)
+ $this->result = $result;
+ }
+
+ /**
+ * Returns an array of the results as ORM objects or a nested array
+ *
+ * @param bool TRUE to return an array of ORM objects, FALSE for an array of arrays
+ * @param string key column to index on, NULL to ignore
+ * @return array
+ */
+ public function as_array($objects = TRUE, $key = NULL)
+ {
+ $array = array();
+
+ // Import class name
+ $class = $this->class_name;
+
+ if ($objects)
+ {
+ // Generate an array of objects
+ foreach ($this->result as $data)
+ {
+ if ($key === NULL)
+ {
+ // No indexing
+ $array[] = new $class($data);
+ }
+ else
+ {
+ // Index on the given key
+ $array[$data->$key] = new $class($data);
+ }
+ }
+ }
+ else
+ {
+ // Generate an array of arrays (and the subarrays may be nested in the case of relationships)
+ // This could be done by creating a new ORM object and calling as_array on it, but this is much faster
+ foreach ($this->result as $data)
+ {
+ // Have to do a bit of magic here to handle any relationships and generate a nested array for them
+ $temp = array();
+
+ foreach ($data as $key => $val)
+ {
+ $ptr = & $temp;
+
+ foreach (explode(':', $key) as $subkey)
+ {
+ // Walk thru the relationships (separated in the key name by a ':')
+ // 'user:email:address' will be array['user']['email']['address']
+ $ptr = & $ptr[$subkey];
+ }
+
+ // Set the value
+ $ptr = $val;
+ }
+
+ // Append the result
+ $array[] = $temp;
+ }
+ }
+
+ return $array;
+ }
+
+ /**
+ * Return an array of all of the primary keys for this object.
+ *
+ * @return array
+ */
+ public function primary_key_array()
+ {
+ $ids = array();
+ foreach ($this->result as $row)
+ {
+ $ids[] = $row->{$this->primary_key};
+ }
+ return $ids;
+ }
+
+ /**
+ * Create a key/value array from the results.
+ *
+ * @param string key column
+ * @param string value column
+ * @return array
+ */
+ public function select_list($key = NULL, $val = NULL)
+ {
+ if ($key === NULL)
+ {
+ // Use the default key
+ $key = $this->primary_key;
+ }
+
+ if ($val === NULL)
+ {
+ // Use the default value
+ $val = $this->primary_val;
+ }
+
+ $array = array();
+ foreach ($this->result as $row)
+ {
+ $array[$row->$key] = $row->$val;
+ }
+ return $array;
+ }
+
+ /**
+ * Return a range of offsets.
+ *
+ * @param integer start
+ * @param integer end
+ * @return array
+ */
+ public function range($start, $end)
+ {
+ // Array of objects
+ $array = array();
+
+ if ($this->result->offsetExists($start))
+ {
+ // Import the class name
+ $class = $this->class_name;
+
+ // Set the end offset
+ $end = $this->result->offsetExists($end) ? $end : $this->count();
+
+ for ($i = $start; $i < $end; $i++)
+ {
+ // Insert each object in the range
+ $array[] = new $class($this->result->offsetGet($i));
+ }
+ }
+
+ return $array;
+ }
+
+ /**
+ * Countable: count
+ */
+ public function count()
+ {
+ return $this->result->count();
+ }
+
+ /**
+ * Iterator: current
+ */
+ public function current()
+ {
+ if ($row = $this->result->current())
+ {
+ // Import class name
+ $class = $this->class_name;
+
+ $row = new $class($row);
+ }
+
+ return $row;
+ }
+
+ /**
+ * Iterator: key
+ */
+ public function key()
+ {
+ return $this->result->key();
+ }
+
+ /**
+ * Iterator: next
+ */
+ public function next()
+ {
+ return $this->result->next();
+ }
+
+ /**
+ * Iterator: rewind
+ */
+ public function rewind()
+ {
+ $this->result->rewind();
+ }
+
+ /**
+ * Iterator: valid
+ */
+ public function valid()
+ {
+ return $this->result->valid();
+ }
+
+ /**
+ * ArrayAccess: offsetExists
+ */
+ public function offsetExists($offset)
+ {
+ return $this->result->offsetExists($offset);
+ }
+
+ /**
+ * ArrayAccess: offsetGet
+ */
+ public function offsetGet($offset)
+ {
+ if ($this->result->offsetExists($offset))
+ {
+ // Import class name
+ $class = $this->class_name;
+
+ return new $class($this->result->offsetGet($offset));
+ }
+ }
+
+ /**
+ * ArrayAccess: offsetSet
+ *
+ * @throws Kohana_Database_Exception
+ */
+ public function offsetSet($offset, $value)
+ {
+ throw new Kohana_Database_Exception('database.result_read_only');
+ }
+
+ /**
+ * ArrayAccess: offsetUnset
+ *
+ * @throws Kohana_Database_Exception
+ */
+ public function offsetUnset($offset)
+ {
+ throw new Kohana_Database_Exception('database.result_read_only');
+ }
+
+} // End ORM Iterator \ No newline at end of file
diff --git a/system/libraries/ORM_Validation_Exception.php b/system/libraries/ORM_Validation_Exception.php
new file mode 100644
index 0000000..9044aa6
--- /dev/null
+++ b/system/libraries/ORM_Validation_Exception.php
@@ -0,0 +1,24 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * ORM Validation exceptions.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2008-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class ORM_Validation_Exception_Core extends Database_Exception {
+
+ /**
+ * Handles Database Validation Exceptions
+ *
+ * @param Validation $array
+ * @return
+ */
+ public static function handle_validation($table, Validation $array)
+ {
+ $exception = new ORM_Validation_Exception('ORM Validation has failed for :table model',array(':table'=>$table));
+ $exception->validation = $array;
+ throw $exception;
+ }
+} // End ORM_Validation_Exception \ No newline at end of file
diff --git a/system/libraries/Profiler.php b/system/libraries/Profiler.php
new file mode 100644
index 0000000..3ac8707
--- /dev/null
+++ b/system/libraries/Profiler.php
@@ -0,0 +1,306 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Adds useful information to the bottom of the current page for debugging and optimization purposes.
+ *
+ * Benchmarks - The times and memory usage of benchmarks run by the Benchmark library.
+ * Database - The raw SQL and number of affected rows of Database queries.
+ * Session Data - Data stored in the current session if using the Session library.
+ * POST Data - The name and values of any POST data submitted to the current page.
+ * Cookie Data - All cookies sent for the current request.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Profiler_Core {
+
+ protected static $profiles = array();
+ protected static $show;
+
+ /**
+ * Enable the profiler.
+ *
+ * @return void
+ */
+ public static function enable()
+ {
+ // Add all built in profiles to event
+ Event::add('profiler.run', array('Profiler', 'benchmarks'));
+ Event::add('profiler.run', array('Profiler', 'database'));
+ Event::add('profiler.run', array('Profiler', 'session'));
+ Event::add('profiler.run', array('Profiler', 'post'));
+ Event::add('profiler.run', array('Profiler', 'cookies'));
+
+ // Add profiler to page output automatically
+ Event::add('system.display', array('Profiler', 'render'));
+
+ Kohana_Log::add('debug', 'Profiler library enabled');
+
+ }
+
+ /**
+ * Disables the profiler for this page only.
+ * Best used when profiler is autoloaded.
+ *
+ * @return void
+ */
+ public static function disable()
+ {
+ // Removes itself from the event queue
+ Event::clear('system.display', array('Profiler', 'render'));
+ }
+
+ /**
+ * Return whether a profile should be shown.
+ * Determined by the config setting or GET parameter.
+ *
+ * @param string profile name
+ * @return boolean
+ */
+ public static function show($name)
+ {
+ return (Profiler::$show === TRUE OR (is_array(Profiler::$show) AND in_array($name, Profiler::$show))) ? TRUE : FALSE;
+ }
+
+ /**
+ * Add a new profile.
+ *
+ * @param object profile object
+ * @return boolean
+ * @throws Kohana_Exception
+ */
+ public static function add($profile)
+ {
+ if (is_object($profile))
+ {
+ Profiler::$profiles[] = $profile;
+ return TRUE;
+ }
+
+ throw new Kohana_Exception('The profile must be an object');
+ }
+
+ /**
+ * Render the profiler.
+ *
+ * @param boolean return the output instead of adding it to bottom of page
+ * @return void|string
+ */
+ public static function render($return = FALSE)
+ {
+ $start = microtime(TRUE);
+
+ // Determine the profiles that should be shown
+ $get = isset($_GET['profiler']) ? explode(',', $_GET['profiler']) : array();
+ Profiler::$show = empty($get) ? Kohana::config('profiler.show') : $get;
+
+ Event::run('profiler.run');
+
+ // Don't display if there's no profiles
+ if (empty(Profiler::$profiles))
+ return Kohana::$output;
+
+ $styles = '';
+ foreach (Profiler::$profiles as $profile)
+ {
+ $styles .= $profile->styles();
+ }
+
+ // Load the profiler view
+ $data = array
+ (
+ 'profiles' => Profiler::$profiles,
+ 'styles' => $styles,
+ 'execution_time' => microtime(TRUE) - $start
+ );
+ $view = new View('profiler/profiler', $data);
+
+ // Return rendered view if $return is TRUE
+ if ($return === TRUE)
+ return $view->render();
+
+ // Add profiler data to the output
+ if (stripos(Kohana::$output, '</body>') !== FALSE)
+ {
+ // Closing body tag was found, insert the profiler data before it
+ Kohana::$output = str_ireplace('</body>', $view->render().'</body>', Kohana::$output);
+ }
+ else
+ {
+ // Append the profiler data to the output
+ Kohana::$output .= $view->render();
+ }
+ }
+
+ /**
+ * Benchmark times and memory usage from the Benchmark library.
+ *
+ * @return void
+ */
+ public static function benchmarks()
+ {
+ if ( ! Profiler::show('benchmarks'))
+ return;
+
+ $table = new Profiler_Table();
+ $table->add_column();
+ $table->add_column('kp-column kp-data');
+ $table->add_column('kp-column kp-data');
+ $table->add_column('kp-column kp-data');
+ $table->add_row(array(__('Benchmarks'), __('Count'), __('Time'), __('Memory')), 'kp-title', 'background-color: #FFE0E0');
+
+ $benchmarks = Benchmark::get(TRUE);
+
+ // Moves the first benchmark (total execution time) to the end of the array
+ $benchmarks = array_slice($benchmarks, 1) + array_slice($benchmarks, 0, 1);
+
+ text::alternate();
+ foreach ($benchmarks as $name => $benchmark)
+ {
+ // Clean unique id from system benchmark names
+ $name = ucwords(str_replace(array('_', '-'), ' ', str_replace(SYSTEM_BENCHMARK.'_', '', $name)));
+
+ $data = array(__($name), $benchmark['count'], number_format($benchmark['time'], Kohana::config('profiler.time_decimals')), number_format($benchmark['memory'] / 1024 / 1024, Kohana::config('profiler.memory_decimals')).'MB');
+ $class = text::alternate('', 'kp-altrow');
+
+ if ($name == 'Total Execution')
+ {
+ // Clear the count column
+ $data[1] = '';
+ $class = 'kp-totalrow';
+ }
+
+ $table->add_row($data, $class);
+ }
+
+ Profiler::add($table);
+ }
+
+ /**
+ * Database query benchmarks.
+ *
+ * @return void
+ */
+ public static function database()
+ {
+ if ( ! Profiler::show('database'))
+ return;
+
+ $queries = Database::$benchmarks;
+
+ // Don't show if there are no queries
+ if (empty($queries)) return;
+
+ $table = new Profiler_Table();
+ $table->add_column();
+ $table->add_column('kp-column kp-data');
+ $table->add_column('kp-column kp-data');
+ $table->add_row(array(__('Queries'), __('Time'), __('Rows')), 'kp-title', 'background-color: #E0FFE0');
+
+ text::alternate();
+ $total_time = $total_rows = 0;
+ foreach ($queries as $query)
+ {
+ $data = array($query['query'], number_format($query['time'], Kohana::config('profiler.time_decimals')), $query['rows']);
+ $class = text::alternate('', 'kp-altrow');
+ $table->add_row($data, $class);
+ $total_time += $query['time'];
+ $total_rows += $query['rows'];
+ }
+
+ $data = array(__('Total: ') . count($queries), number_format($total_time, Kohana::config('profiler.time_decimals')), $total_rows);
+ $table->add_row($data, 'kp-totalrow');
+
+ Profiler::add($table);
+ }
+
+ /**
+ * Session data.
+ *
+ * @return void
+ */
+ public static function session()
+ {
+ if (empty($_SESSION)) return;
+
+ if ( ! Profiler::show('session'))
+ return;
+
+ $table = new Profiler_Table();
+ $table->add_column('kp-name');
+ $table->add_column();
+ $table->add_row(array(__('Session'), __('Value')), 'kp-title', 'background-color: #CCE8FB');
+
+ text::alternate();
+ foreach($_SESSION as $name => $value)
+ {
+ if (is_object($value))
+ {
+ $value = get_class($value).' [object]';
+ }
+
+ $data = array($name, $value);
+ $class = text::alternate('', 'kp-altrow');
+ $table->add_row($data, $class);
+ }
+
+ Profiler::add($table);
+ }
+
+ /**
+ * POST data.
+ *
+ * @return void
+ */
+ public static function post()
+ {
+ if (empty($_POST)) return;
+
+ if ( ! Profiler::show('post'))
+ return;
+
+ $table = new Profiler_Table();
+ $table->add_column('kp-name');
+ $table->add_column();
+ $table->add_row(array(__('POST'), __('Value')), 'kp-title', 'background-color: #E0E0FF');
+
+ text::alternate();
+ foreach($_POST as $name => $value)
+ {
+ $data = array($name, $value);
+ $class = text::alternate('', 'kp-altrow');
+ $table->add_row($data, $class);
+ }
+
+ Profiler::add($table);
+ }
+
+ /**
+ * Cookie data.
+ *
+ * @return void
+ */
+ public static function cookies()
+ {
+ if (empty($_COOKIE)) return;
+
+ if ( ! Profiler::show('cookies'))
+ return;
+
+ $table = new Profiler_Table();
+ $table->add_column('kp-name');
+ $table->add_column();
+ $table->add_row(array(__('Cookies'), __('Value')), 'kp-title', 'background-color: #FFF4D7');
+
+ text::alternate();
+ foreach($_COOKIE as $name => $value)
+ {
+ $data = array($name, $value);
+ $class = text::alternate('', 'kp-altrow');
+ $table->add_row($data, $class);
+ }
+
+ Profiler::add($table);
+ }
+}
diff --git a/system/libraries/Profiler_Table.php b/system/libraries/Profiler_Table.php
new file mode 100644
index 0000000..e590ad7
--- /dev/null
+++ b/system/libraries/Profiler_Table.php
@@ -0,0 +1,67 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Provides a table layout for sections in the Profiler library.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Profiler_Table_Core {
+
+ protected $columns = array();
+ protected $rows = array();
+
+ /**
+ * Get styles for table.
+ *
+ * @return string
+ */
+ public function styles()
+ {
+ static $styles_output;
+
+ if ( ! $styles_output)
+ {
+ $styles_output = TRUE;
+ return file_get_contents(Kohana::find_file('views', 'profiler/table', FALSE, 'css'));
+ }
+
+ return '';
+ }
+
+ /**
+ * Add column to table.
+ *
+ * @param string CSS class
+ * @param string CSS style
+ */
+ public function add_column($class = '', $style = '')
+ {
+ $this->columns[] = array('class' => $class, 'style' => $style);
+ }
+
+ /**
+ * Add row to table.
+ *
+ * @param array data to go in table cells
+ * @param string CSS class
+ * @param string CSS style
+ */
+ public function add_row($data, $class = '', $style = '')
+ {
+ $this->rows[] = array('data' => $data, 'class' => $class, 'style' => $style);
+ }
+
+ /**
+ * Render table.
+ *
+ * @return string
+ */
+ public function render()
+ {
+ $data['rows'] = $this->rows;
+ $data['columns'] = $this->columns;
+ return View::factory('profiler/table', $data)->render();
+ }
+} \ No newline at end of file
diff --git a/system/libraries/Router.php b/system/libraries/Router.php
new file mode 100644
index 0000000..c36121d
--- /dev/null
+++ b/system/libraries/Router.php
@@ -0,0 +1,315 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Router
+ *
+ * $Id: Router.php 4729 2009-12-29 20:35:19Z isaiah $
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Router_Core {
+
+ protected static $routes;
+
+ public static $current_uri = '';
+ public static $query_string = '';
+ public static $complete_uri = '';
+ public static $routed_uri = '';
+ public static $url_suffix = '';
+
+ public static $segments;
+ public static $rsegments;
+
+ public static $controller;
+ public static $controller_path;
+
+ public static $method = 'index';
+ public static $arguments = array();
+
+ /**
+ * Router setup routine. Automatically called during Kohana setup process.
+ *
+ * @return void
+ */
+ public static function setup()
+ {
+ if ( ! empty($_SERVER['QUERY_STRING']))
+ {
+ // Set the query string to the current query string
+ Router::$query_string = '?'.urldecode(trim($_SERVER['QUERY_STRING'], '&'));
+ }
+
+ if (Router::$routes === NULL)
+ {
+ // Load routes
+ Router::$routes = Kohana::config('routes');
+ }
+
+ // Default route status
+ $default_route = FALSE;
+
+ if (Router::$current_uri === '')
+ {
+ // Make sure the default route is set
+ if (empty(Router::$routes['_default']))
+ throw new Kohana_Exception('Please set a default route in config/routes.php.');
+
+ // Use the default route when no segments exist
+ Router::$current_uri = Router::$routes['_default'];
+
+ // Default route is in use
+ $default_route = TRUE;
+ }
+
+ // Remove all dot-paths from the URI, they are not valid
+ Router::$current_uri = preg_replace('#\.[\s./]*/#', '', Router::$current_uri);
+
+ // At this point routed URI and current URI are the same
+ Router::$routed_uri = Router::$current_uri = trim(Router::$current_uri, '/');
+
+ if ($default_route === TRUE)
+ {
+ Router::$complete_uri = Router::$query_string;
+ Router::$current_uri = '';
+ Router::$segments = array();
+ }
+ else
+ {
+ Router::$complete_uri = Router::$current_uri.Router::$query_string;
+
+ // Explode the segments by slashes
+ Router::$segments = explode('/', Router::$current_uri);
+
+ if (count(Router::$routes) > 1)
+ {
+ // Custom routing
+ Router::$routed_uri = Router::routed_uri(Router::$current_uri);
+ }
+ }
+
+ // Explode the routed segments by slashes
+ Router::$rsegments = explode('/', Router::$routed_uri);
+
+ // Prepare to find the controller
+ $controller_path = '';
+ $method_segment = NULL;
+
+ // Paths to search
+ $paths = Kohana::include_paths();
+
+ foreach (Router::$rsegments as $key => $segment)
+ {
+ // Add the segment to the search path
+ $controller_path .= $segment;
+
+ $found = FALSE;
+ foreach ($paths as $dir)
+ {
+ // Search within controllers only
+ $dir .= 'controllers/';
+
+ if (is_dir($dir.$controller_path) OR is_file($dir.$controller_path.EXT))
+ {
+ // Valid path
+ $found = TRUE;
+
+ // The controller must be a file that exists with the search path
+ if ($c = str_replace('\\', '/', realpath($dir.$controller_path.EXT))
+ AND is_file($c) AND strpos($c, $dir) === 0)
+ {
+ // Set controller name
+ Router::$controller = $segment;
+
+ // Change controller path
+ Router::$controller_path = $c;
+
+ // Set the method segment
+ $method_segment = $key + 1;
+
+ // Stop searching
+ break;
+ }
+ }
+ }
+
+ if ($found === FALSE)
+ {
+ // Maximum depth has been reached, stop searching
+ break;
+ }
+
+ // Add another slash
+ $controller_path .= '/';
+ }
+
+ if ($method_segment !== NULL AND isset(Router::$rsegments[$method_segment]))
+ {
+ // Set method
+ Router::$method = Router::$rsegments[$method_segment];
+
+ if (isset(Router::$rsegments[$method_segment + 1]))
+ {
+ // Set arguments
+ Router::$arguments = array_slice(Router::$rsegments, $method_segment + 1);
+ }
+ }
+
+ // Last chance to set routing before a 404 is triggered
+ Event::run('system.post_routing');
+
+ if (Router::$controller === NULL)
+ {
+ // No controller was found, so no page can be rendered
+ Event::run('system.404');
+ }
+ }
+
+ /**
+ * Attempts to determine the current URI using CLI, GET, PATH_INFO, ORIG_PATH_INFO, or PHP_SELF.
+ *
+ * @return void
+ */
+ public static function find_uri()
+ {
+ if (Kohana::$server_api === 'cli')
+ {
+ // Command line requires a bit of hacking
+ if (isset($_SERVER['argv'][1]))
+ {
+ Router::$current_uri = $_SERVER['argv'][1];
+
+ // Remove GET string from segments
+ if (strpos(Router::$current_uri, '?') !== FALSE)
+ {
+ list(Router::$current_uri, $query) = explode('?', Router::$current_uri, 2);
+
+ // Parse the query string into $_GET
+ parse_str($query, $_GET);
+
+ // Convert $_GET to UTF-8
+ $_GET = Input::clean($_GET);
+ }
+ }
+ }
+ elseif (isset($_GET['kohana_uri']))
+ {
+ // Use the URI defined in the query string
+ Router::$current_uri = $_GET['kohana_uri'];
+
+ // Remove the URI from $_GET
+ unset($_GET['kohana_uri']);
+
+ // Remove the URI from $_SERVER['QUERY_STRING']
+ $_SERVER['QUERY_STRING'] = preg_replace('~\bkohana_uri\b[^&]*+&?~', '', $_SERVER['QUERY_STRING']);
+ }
+ else
+ {
+ if (isset($_SERVER['PATH_INFO']) AND $_SERVER['PATH_INFO'])
+ {
+ Router::$current_uri = $_SERVER['PATH_INFO'];
+ }
+ elseif (isset($_SERVER['ORIG_PATH_INFO']) AND $_SERVER['ORIG_PATH_INFO'])
+ {
+ Router::$current_uri = $_SERVER['ORIG_PATH_INFO'];
+ }
+ elseif (isset($_SERVER['PHP_SELF']) AND $_SERVER['PHP_SELF'])
+ {
+ // PATH_INFO is empty during requests to the front controller
+ Router::$current_uri = $_SERVER['PHP_SELF'];
+ }
+
+ if (isset($_SERVER['SCRIPT_NAME']) AND $_SERVER['SCRIPT_NAME'])
+ {
+ // Clean up PATH_INFO fallbacks
+ // PATH_INFO may be formatted for ISAPI instead of CGI on IIS
+ if (strncmp(Router::$current_uri, $_SERVER['SCRIPT_NAME'], strlen($_SERVER['SCRIPT_NAME'])) === 0)
+ {
+ // Remove the front controller from the current uri
+ Router::$current_uri = (string) substr(Router::$current_uri, strlen($_SERVER['SCRIPT_NAME']));
+ }
+ }
+ }
+
+ // Remove slashes from the start and end of the URI
+ Router::$current_uri = trim(Router::$current_uri, '/');
+
+ if (Router::$current_uri !== '')
+ {
+ if ($suffix = Kohana::config('core.url_suffix') AND strpos(Router::$current_uri, $suffix) !== FALSE)
+ {
+ // Remove the URL suffix
+ Router::$current_uri = preg_replace('#'.preg_quote($suffix).'$#u', '', Router::$current_uri);
+
+ // Set the URL suffix
+ Router::$url_suffix = $suffix;
+ }
+
+ // Reduce multiple slashes into single slashes
+ Router::$current_uri = preg_replace('#//+#', '/', Router::$current_uri);
+ }
+ }
+
+ /**
+ * Generates routed URI from given URI.
+ *
+ * @param string URI to convert
+ * @return string Routed uri
+ */
+ public static function routed_uri($uri)
+ {
+ if (Router::$routes === NULL)
+ {
+ // Load routes
+ Router::$routes = Kohana::config('routes');
+ }
+
+ // Prepare variables
+ $routed_uri = $uri = trim($uri, '/');
+
+ if (isset(Router::$routes[$uri]))
+ {
+ // Literal match, no need for regex
+ $routed_uri = Router::$routes[$uri];
+ }
+ else
+ {
+ // Loop through the routes and see if anything matches
+ foreach (Router::$routes as $key => $val)
+ {
+ if ($key === '_default') continue;
+
+ // Trim slashes
+ $key = trim($key, '/');
+ $val = trim($val, '/');
+
+ if (preg_match('#^'.$key.'$#u', $uri))
+ {
+ if (strpos($val, '$') !== FALSE)
+ {
+ // Use regex routing
+ $routed_uri = preg_replace('#^'.$key.'$#u', $val, $uri);
+ }
+ else
+ {
+ // Standard routing
+ $routed_uri = $val;
+ }
+
+ // A valid route has been found
+ break;
+ }
+ }
+ }
+
+ if (isset(Router::$routes[$routed_uri]))
+ {
+ // Check for double routing (without regex)
+ $routed_uri = Router::$routes[$routed_uri];
+ }
+
+ return trim($routed_uri, '/');
+ }
+
+} // End Router \ No newline at end of file
diff --git a/system/libraries/Session.php b/system/libraries/Session.php
new file mode 100644
index 0000000..e57908e
--- /dev/null
+++ b/system/libraries/Session.php
@@ -0,0 +1,500 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Session library.
+ *
+ * $Id: Session.php 4729 2009-12-29 20:35:19Z isaiah $
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Session_Core {
+
+ // Session singleton
+ protected static $instance;
+
+ // Protected key names (cannot be set by the user)
+ protected static $protect = array('session_id', 'user_agent', 'last_activity', 'ip_address', 'total_hits', '_kf_flash_');
+
+ // Configuration and driver
+ protected static $config;
+ protected static $driver;
+
+ // Flash variables
+ protected static $flash;
+
+ // Input library
+ protected $input;
+
+ // Automatically save the session by default
+ public static $should_save = true;
+
+ /**
+ * Singleton instance of Session.
+ *
+ * @param string Force a specific session_id
+ */
+ public static function instance($session_id = NULL)
+ {
+ if (Session::$instance == NULL)
+ {
+ // Create a new instance
+ new Session($session_id);
+ }
+ elseif( ! is_null($session_id) AND $session_id != session_id() )
+ {
+ throw new Kohana_Exception('A session (SID: :session:) is already open, cannot open the specified session (SID: :new_session:).', array(':session:' => session_id(), ':new_session:' => $session_id));
+ }
+
+ return Session::$instance;
+ }
+
+ /**
+ * Be sure to block the use of __clone.
+ */
+ private function __clone(){}
+
+ /**
+ * On first session instance creation, sets up the driver and creates session.
+ *
+ * @param string Force a specific session_id
+ */
+ protected function __construct($session_id = NULL)
+ {
+ $this->input = Input::instance();
+
+ // This part only needs to be run once
+ if (Session::$instance === NULL)
+ {
+ // Load config
+ Session::$config = Kohana::config('session');
+
+ // Makes a mirrored array, eg: foo=foo
+ Session::$protect = array_combine(Session::$protect, Session::$protect);
+
+ // Configure garbage collection
+ ini_set('session.gc_probability', (int) Session::$config['gc_probability']);
+ ini_set('session.gc_divisor', 100);
+ ini_set('session.gc_maxlifetime', (Session::$config['expiration'] == 0) ? 86400 : Session::$config['expiration']);
+
+ // Create a new session
+ $this->create(NULL, $session_id);
+
+ if (Session::$config['regenerate'] > 0 AND ($_SESSION['total_hits'] % Session::$config['regenerate']) === 0)
+ {
+ // Regenerate session id and update session cookie
+ $this->regenerate();
+ }
+ else
+ {
+ // Always update session cookie to keep the session alive
+ cookie::set(Session::$config['name'], $_SESSION['session_id'], Session::$config['expiration']);
+ }
+
+ // Close the session on system shutdown (run before sending the headers), so that
+ // the session cookie(s) can be written.
+ Event::add('system.shutdown', array($this, 'write_close'));
+
+ // Singleton instance
+ Session::$instance = $this;
+ }
+
+ Kohana_Log::add('debug', 'Session Library initialized');
+ }
+
+ /**
+ * Get the session id.
+ *
+ * @return string
+ */
+ public function id()
+ {
+ return $_SESSION['session_id'];
+ }
+
+ /**
+ * Create a new session.
+ *
+ * @param array variables to set after creation
+ * @param string Force a specific session_id
+ * @return void
+ */
+ public function create($vars = NULL, $session_id = NULL)
+ {
+ // Destroy any current sessions
+ $this->destroy();
+
+ if (Session::$config['driver'] !== 'native')
+ {
+ // Set driver name
+ $driver = 'Session_'.ucfirst(Session::$config['driver']).'_Driver';
+
+ // Load the driver
+ if ( ! Kohana::auto_load($driver))
+ throw new Kohana_Exception('The :driver: driver for the :library: library could not be found',
+ array(':driver:' => Session::$config['driver'], ':library:' => get_class($this)));
+
+ // Initialize the driver
+ Session::$driver = new $driver();
+
+ // Validate the driver
+ if ( ! (Session::$driver instanceof Session_Driver))
+ throw new Kohana_Exception('The :driver: driver for the :library: library must implement the :interface: interface',
+ array(':driver:' => Session::$config['driver'], ':library:' => get_class($this), ':interface:' => 'Session_Driver'));
+
+ // Register non-native driver as the session handler
+ session_set_save_handler
+ (
+ array(Session::$driver, 'open'),
+ array(Session::$driver, 'close'),
+ array(Session::$driver, 'read'),
+ array(Session::$driver, 'write'),
+ array(Session::$driver, 'destroy'),
+ array(Session::$driver, 'gc')
+ );
+ }
+
+ // Validate the session name
+ if ( ! preg_match('~^(?=.*[a-z])[a-z0-9_]++$~iD', Session::$config['name']))
+ throw new Kohana_Exception('The session_name, :session:, is invalid. It must contain only alphanumeric characters and underscores. Also at least one letter must be present.', array(':session:' => Session::$config['name']));
+
+ // Name the session, this will also be the name of the cookie
+ session_name(Session::$config['name']);
+
+ // Set the session cookie parameters
+ session_set_cookie_params
+ (
+ Session::$config['expiration'],
+ Kohana::config('cookie.path'),
+ Kohana::config('cookie.domain'),
+ Kohana::config('cookie.secure'),
+ Kohana::config('cookie.httponly')
+ );
+
+ $cookie = cookie::get(Session::$config['name']);
+
+ if ($session_id === NULL)
+ {
+ // Reopen session from signed cookie value.
+ $session_id = $cookie;
+ }
+
+ // Reopen an existing session if supplied
+ if ( ! is_null($session_id))
+ {
+ session_id($session_id);
+ }
+
+ // Start the session!
+ session_start();
+
+ // Put session_id in the session variable
+ $_SESSION['session_id'] = session_id();
+
+ // Set defaults
+ if ( ! isset($_SESSION['_kf_flash_']))
+ {
+ $_SESSION['total_hits'] = 0;
+ $_SESSION['_kf_flash_'] = array();
+
+ $_SESSION['user_agent'] = request::user_agent();
+ $_SESSION['ip_address'] = $this->input->ip_address();
+ }
+
+ // Set up flash variables
+ Session::$flash =& $_SESSION['_kf_flash_'];
+
+ // Increase total hits
+ $_SESSION['total_hits'] += 1;
+
+ // Validate data only on hits after one
+ if ($_SESSION['total_hits'] > 1)
+ {
+ // Validate the session
+ foreach (Session::$config['validate'] as $valid)
+ {
+ switch ($valid)
+ {
+ // Check user agent for consistency
+ case 'user_agent':
+ if ($_SESSION[$valid] !== request::user_agent())
+ return $this->create();
+ break;
+
+ // Check ip address for consistency
+ case 'ip_address':
+ if ($_SESSION[$valid] !== $this->input->$valid())
+ return $this->create();
+ break;
+
+ // Check expiration time to prevent users from manually modifying it
+ case 'expiration':
+ if (time() - $_SESSION['last_activity'] > ini_get('session.gc_maxlifetime'))
+ return $this->create();
+ break;
+ }
+ }
+ }
+
+ // Expire flash keys
+ $this->expire_flash();
+
+ // Update last activity
+ $_SESSION['last_activity'] = time();
+
+ // Set the new data
+ Session::set($vars);
+ }
+
+ /**
+ * Regenerates the global session id.
+ *
+ * @return void
+ */
+ public function regenerate()
+ {
+ if (Session::$config['driver'] === 'native')
+ {
+ // Generate a new session id
+ // Note: also sets a new session cookie with the updated id
+ session_regenerate_id(TRUE);
+
+ // Update session with new id
+ $_SESSION['session_id'] = session_id();
+ }
+ else
+ {
+ // Pass the regenerating off to the driver in case it wants to do anything special
+ $_SESSION['session_id'] = Session::$driver->regenerate();
+ }
+
+ // Get the session name
+ $name = session_name();
+
+ if (isset($_COOKIE[$name]))
+ {
+ // Change the cookie value to match the new session id to prevent "lag"
+ cookie::set($name, $_SESSION['session_id']);
+ }
+ }
+
+ /**
+ * Destroys the current session.
+ *
+ * @return void
+ */
+ public function destroy()
+ {
+ if (session_id() !== '')
+ {
+ // Get the session name
+ $name = session_name();
+
+ // Destroy the session
+ session_destroy();
+
+ // Re-initialize the array
+ $_SESSION = array();
+
+ // Delete the session cookie
+ cookie::delete($name);
+ }
+ }
+
+ /**
+ * Runs the system.session_write event, then calls session_write_close.
+ *
+ * @return void
+ */
+ public function write_close()
+ {
+ static $run;
+
+ if ($run === NULL)
+ {
+ $run = TRUE;
+
+ // Run the events that depend on the session being open
+ Event::run('system.session_write');
+
+ // Expire flash keys
+ $this->expire_flash();
+
+ // Close the session
+ session_write_close();
+ }
+ }
+
+ /**
+ * Set a session variable.
+ *
+ * @param string|array key, or array of values
+ * @param mixed value (if keys is not an array)
+ * @return void
+ */
+ public function set($keys, $val = FALSE)
+ {
+ if (empty($keys))
+ return FALSE;
+
+ if ( ! is_array($keys))
+ {
+ $keys = array($keys => $val);
+ }
+
+ foreach ($keys as $key => $val)
+ {
+ if (isset(Session::$protect[$key]))
+ continue;
+
+ // Set the key
+ $_SESSION[$key] = $val;
+ }
+ }
+
+ /**
+ * Set a flash variable.
+ *
+ * @param string|array key, or array of values
+ * @param mixed value (if keys is not an array)
+ * @return void
+ */
+ public function set_flash($keys, $val = FALSE)
+ {
+ if (empty($keys))
+ return FALSE;
+
+ if ( ! is_array($keys))
+ {
+ $keys = array($keys => $val);
+ }
+
+ foreach ($keys as $key => $val)
+ {
+ if ($key == FALSE)
+ continue;
+
+ Session::$flash[$key] = 'new';
+ Session::set($key, $val);
+ }
+ }
+
+ /**
+ * Freshen one, multiple or all flash variables.
+ *
+ * @param string variable key(s)
+ * @return void
+ */
+ public function keep_flash($keys = NULL)
+ {
+ $keys = ($keys === NULL) ? array_keys(Session::$flash) : func_get_args();
+
+ foreach ($keys as $key)
+ {
+ if (isset(Session::$flash[$key]))
+ {
+ Session::$flash[$key] = 'new';
+ }
+ }
+ }
+
+ /**
+ * Expires old flash data and removes it from the session.
+ *
+ * @return void
+ */
+ public function expire_flash()
+ {
+ static $run;
+
+ // Method can only be run once
+ if ($run === TRUE)
+ return;
+
+ if ( ! empty(Session::$flash))
+ {
+ foreach (Session::$flash as $key => $state)
+ {
+ if ($state === 'old')
+ {
+ // Flash has expired
+ unset(Session::$flash[$key], $_SESSION[$key]);
+ }
+ else
+ {
+ // Flash will expire
+ Session::$flash[$key] = 'old';
+ }
+ }
+ }
+
+ // Method has been run
+ $run = TRUE;
+ }
+
+ /**
+ * Get a variable. Access to sub-arrays is supported with key.subkey.
+ *
+ * @param string variable key
+ * @param mixed default value returned if variable does not exist
+ * @return mixed Variable data if key specified, otherwise array containing all session data.
+ */
+ public function get($key = FALSE, $default = FALSE)
+ {
+ if (empty($key))
+ return $_SESSION;
+
+ $result = isset($_SESSION[$key]) ? $_SESSION[$key] : Kohana::key_string($_SESSION, $key);
+
+ return ($result === NULL) ? $default : $result;
+ }
+
+ /**
+ * Get a variable, and delete it.
+ *
+ * @param string variable key
+ * @param mixed default value returned if variable does not exist
+ * @return mixed
+ */
+ public function get_once($key, $default = FALSE)
+ {
+ $return = Session::get($key, $default);
+ Session::delete($key);
+
+ return $return;
+ }
+
+ /**
+ * Delete one or more variables.
+ *
+ * @param string variable key(s)
+ * @return void
+ */
+ public function delete($keys)
+ {
+ $args = func_get_args();
+
+ foreach ($args as $key)
+ {
+ if (isset(Session::$protect[$key]))
+ continue;
+
+ // Unset the key
+ unset($_SESSION[$key]);
+ }
+ }
+
+ /**
+ * Do not save this session.
+ * This is a performance feature only, if using the native
+ * session "driver" the save will NOT be aborted.
+ *
+ * @return void
+ */
+ public function abort_save()
+ {
+ Session::$should_save = FALSE;
+ }
+
+} // End Session Class
diff --git a/system/libraries/URI.php b/system/libraries/URI.php
new file mode 100644
index 0000000..16d101a
--- /dev/null
+++ b/system/libraries/URI.php
@@ -0,0 +1,279 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * URI library.
+ *
+ * $Id: URI.php 4729 2009-12-29 20:35:19Z isaiah $
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class URI_Core extends Router {
+
+ /**
+ * Returns a singleton instance of URI.
+ *
+ * @return object
+ */
+ public static function instance()
+ {
+ static $instance;
+
+ if ($instance == NULL)
+ {
+ // Initialize the URI instance
+ $instance = new URI;
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Retrieve a specific URI segment.
+ *
+ * @param integer|string segment number or label
+ * @param mixed default value returned if segment does not exist
+ * @return string
+ */
+ public function segment($index = 1, $default = FALSE)
+ {
+ if (is_string($index))
+ {
+ if (($key = array_search($index, URI::$segments)) === FALSE)
+ return $default;
+
+ $index = $key + 2;
+ }
+
+ $index = (int) $index - 1;
+
+ return isset(URI::$segments[$index]) ? URI::$segments[$index] : $default;
+ }
+
+ /**
+ * Retrieve a specific routed URI segment.
+ *
+ * @param integer|string rsegment number or label
+ * @param mixed default value returned if segment does not exist
+ * @return string
+ */
+ public function rsegment($index = 1, $default = FALSE)
+ {
+ if (is_string($index))
+ {
+ if (($key = array_search($index, URI::$rsegments)) === FALSE)
+ return $default;
+
+ $index = $key + 2;
+ }
+
+ $index = (int) $index - 1;
+
+ return isset(URI::$rsegments[$index]) ? URI::$rsegments[$index] : $default;
+ }
+
+ /**
+ * Retrieve a specific URI argument.
+ * This is the part of the segments that does not indicate controller or method
+ *
+ * @param integer|string argument number or label
+ * @param mixed default value returned if segment does not exist
+ * @return string
+ */
+ public function argument($index = 1, $default = FALSE)
+ {
+ if (is_string($index))
+ {
+ if (($key = array_search($index, URI::$arguments)) === FALSE)
+ return $default;
+
+ $index = $key + 2;
+ }
+
+ $index = (int) $index - 1;
+
+ return isset(URI::$arguments[$index]) ? URI::$arguments[$index] : $default;
+ }
+
+ /**
+ * Returns an array containing all the URI segments.
+ *
+ * @param integer segment offset
+ * @param boolean return an associative array
+ * @return array
+ */
+ public function segment_array($offset = 0, $associative = FALSE)
+ {
+ return $this->build_array(URI::$segments, $offset, $associative);
+ }
+
+ /**
+ * Returns an array containing all the re-routed URI segments.
+ *
+ * @param integer rsegment offset
+ * @param boolean return an associative array
+ * @return array
+ */
+ public function rsegment_array($offset = 0, $associative = FALSE)
+ {
+ return $this->build_array(URI::$rsegments, $offset, $associative);
+ }
+
+ /**
+ * Returns an array containing all the URI arguments.
+ *
+ * @param integer segment offset
+ * @param boolean return an associative array
+ * @return array
+ */
+ public function argument_array($offset = 0, $associative = FALSE)
+ {
+ return $this->build_array(URI::$arguments, $offset, $associative);
+ }
+
+ /**
+ * Creates a simple or associative array from an array and an offset.
+ * Used as a helper for (r)segment_array and argument_array.
+ *
+ * @param array array to rebuild
+ * @param integer offset to start from
+ * @param boolean create an associative array
+ * @return array
+ */
+ public function build_array($array, $offset = 0, $associative = FALSE)
+ {
+ // Prevent the keys from being improperly indexed
+ array_unshift($array, 0);
+
+ // Slice the array, preserving the keys
+ $array = array_slice($array, $offset + 1, count($array) - 1, TRUE);
+
+ if ($associative === FALSE)
+ return $array;
+
+ $associative = array();
+ $pairs = array_chunk($array, 2);
+
+ foreach ($pairs as $pair)
+ {
+ // Add the key/value pair to the associative array
+ $associative[$pair[0]] = isset($pair[1]) ? $pair[1] : '';
+ }
+
+ return $associative;
+ }
+
+ /**
+ * Returns the complete URI as a string.
+ *
+ * @return string
+ */
+ public function string()
+ {
+ return URI::$current_uri;
+ }
+
+ /**
+ * Magic method for converting an object to a string.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return URI::$current_uri;
+ }
+
+ /**
+ * Returns the total number of URI segments.
+ *
+ * @return integer
+ */
+ public function total_segments()
+ {
+ return count(URI::$segments);
+ }
+
+ /**
+ * Returns the total number of re-routed URI segments.
+ *
+ * @return integer
+ */
+ public function total_rsegments()
+ {
+ return count(URI::$rsegments);
+ }
+
+ /**
+ * Returns the total number of URI arguments.
+ *
+ * @return integer
+ */
+ public function total_arguments()
+ {
+ return count(URI::$arguments);
+ }
+
+ /**
+ * Returns the last URI segment.
+ *
+ * @param mixed default value returned if segment does not exist
+ * @return string
+ */
+ public function last_segment($default = FALSE)
+ {
+ if (($end = $this->total_segments()) < 1)
+ return $default;
+
+ return URI::$segments[$end - 1];
+ }
+
+ /**
+ * Returns the last re-routed URI segment.
+ *
+ * @param mixed default value returned if segment does not exist
+ * @return string
+ */
+ public function last_rsegment($default = FALSE)
+ {
+ if (($end = $this->total_segments()) < 1)
+ return $default;
+
+ return URI::$rsegments[$end - 1];
+ }
+
+ /**
+ * Returns the path to the current controller (not including the actual
+ * controller), as a web path.
+ *
+ * @param boolean return a full url, or only the path specifically
+ * @return string
+ */
+ public function controller_path($full = TRUE)
+ {
+ return ($full) ? url::site(URI::$controller_path) : URI::$controller_path;
+ }
+
+ /**
+ * Returns the current controller, as a web path.
+ *
+ * @param boolean return a full url, or only the controller specifically
+ * @return string
+ */
+ public function controller($full = TRUE)
+ {
+ return ($full) ? url::site(URI::$controller_path.URI::$controller) : URI::$controller;
+ }
+
+ /**
+ * Returns the current method, as a web path.
+ *
+ * @param boolean return a full url, or only the method specifically
+ * @return string
+ */
+ public function method($full = TRUE)
+ {
+ return ($full) ? url::site(URI::$controller_path.URI::$controller.'/'.URI::$method) : URI::$method;
+ }
+
+} // End URI Class
diff --git a/system/libraries/Validation.php b/system/libraries/Validation.php
new file mode 100644
index 0000000..9917fbb
--- /dev/null
+++ b/system/libraries/Validation.php
@@ -0,0 +1,815 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Validation library.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Validation_Core extends ArrayObject {
+
+ // Filters
+ protected $pre_filters = array();
+ protected $post_filters = array();
+
+ // Rules and callbacks
+ protected $rules = array();
+ protected $callbacks = array();
+
+ // Rules that are allowed to run on empty fields
+ protected $empty_rules = array('required', 'matches');
+
+ // Errors
+ protected $errors = array();
+ protected $messages = array();
+
+ // Field labels
+ protected $labels = array();
+
+ // Fields that are expected to be arrays
+ protected $array_fields = array();
+
+ /**
+ * Creates a new Validation instance.
+ *
+ * @param array array to use for validation
+ * @return object
+ */
+ public static function factory(array $array)
+ {
+ return new Validation($array);
+ }
+
+ /**
+ * Sets the unique "any field" key and creates an ArrayObject from the
+ * passed array.
+ *
+ * @param array array to validate
+ * @return void
+ */
+ public function __construct(array $array)
+ {
+ parent::__construct($array, ArrayObject::ARRAY_AS_PROPS | ArrayObject::STD_PROP_LIST);
+ }
+
+ /**
+ * Magic clone method, clears errors and messages.
+ *
+ * @return void
+ */
+ public function __clone()
+ {
+ $this->errors = array();
+ $this->messages = array();
+ }
+
+ /**
+ * Create a copy of the current validation rules and change the array.
+ *
+ * @chainable
+ * @param array new array to validate
+ * @return Validation
+ */
+ public function copy(array $array)
+ {
+ $copy = clone $this;
+
+ $copy->exchangeArray($array);
+
+ return $copy;
+ }
+
+ /**
+ * Returns an array of all the field names that have filters, rules, or callbacks.
+ *
+ * @return array
+ */
+ public function field_names()
+ {
+ // All the fields that are being validated
+ $fields = array_keys(array_merge
+ (
+ $this->pre_filters,
+ $this->rules,
+ $this->callbacks,
+ $this->post_filters
+ ));
+
+ // Remove wildcard fields
+ $fields = array_diff($fields, array('*'));
+
+ return $fields;
+ }
+
+ /**
+ * Returns the array values of the current object.
+ *
+ * @return array
+ */
+ public function as_array()
+ {
+ return $this->getArrayCopy();
+ }
+
+ /**
+ * Returns the ArrayObject values, removing all inputs without rules.
+ * To choose specific inputs, list the field name as arguments.
+ *
+ * @param boolean return only fields with filters, rules, and callbacks
+ * @return array
+ */
+ public function safe_array()
+ {
+ // Load choices
+ $choices = func_get_args();
+ $choices = empty($choices) ? NULL : array_combine($choices, $choices);
+
+ // Get field names
+ $fields = $this->field_names();
+
+ $safe = array();
+ foreach ($fields as $field)
+ {
+ if ($choices === NULL OR isset($choices[$field]))
+ {
+ if (isset($this[$field]))
+ {
+ $value = $this[$field];
+
+ if (is_object($value))
+ {
+ // Convert the value back into an array
+ $value = $value->getArrayCopy();
+ }
+ }
+ else
+ {
+ // Even if the field is not in this array, it must be set
+ $value = NULL;
+ }
+
+ // Add the field to the array
+ $safe[$field] = $value;
+ }
+ }
+
+ return $safe;
+ }
+
+ /**
+ * Add additional rules that will forced, even for empty fields. All arguments
+ * passed will be appended to the list.
+ *
+ * @chainable
+ * @param string rule name
+ * @return object
+ */
+ public function allow_empty_rules($rules)
+ {
+ // Any number of args are supported
+ $rules = func_get_args();
+
+ // Merge the allowed rules
+ $this->empty_rules = array_merge($this->empty_rules, $rules);
+
+ return $this;
+ }
+
+ /**
+ * Sets or overwrites the label name for a field.
+ *
+ * @param string field name
+ * @param string label
+ * @return $this
+ */
+ public function label($field, $label = NULL)
+ {
+ if ($label === NULL AND ($field !== TRUE OR $field !== '*') AND ! isset($this->labels[$field]))
+ {
+ // Set the field label to the field name
+ $this->labels[$field] = ucfirst(preg_replace('/[^\pL]+/u', ' ', $field));
+ }
+ elseif ($label !== NULL)
+ {
+ // Set the label for this field
+ $this->labels[$field] = $label;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets labels using an array.
+ *
+ * @param array list of field => label names
+ * @return $this
+ */
+ public function labels(array $labels)
+ {
+ $this->labels = $labels + $this->labels;
+
+ return $this;
+ }
+
+ /**
+ * Converts a filter, rule, or callback into a fully-qualified callback array.
+ *
+ * @return mixed
+ */
+ protected function callback($callback)
+ {
+ if (is_string($callback))
+ {
+ if (strpos($callback, '::') !== FALSE)
+ {
+ $callback = explode('::', $callback);
+ }
+ elseif (function_exists($callback))
+ {
+ // No need to check if the callback is a method
+ $callback = $callback;
+ }
+ elseif (method_exists($this, $callback))
+ {
+ // The callback exists in Validation
+ $callback = array($this, $callback);
+ }
+ elseif (method_exists('valid', $callback))
+ {
+ // The callback exists in valid::
+ $callback = array('valid', $callback);
+ }
+ }
+
+ if ( ! is_callable($callback, FALSE))
+ {
+ if (is_array($callback))
+ {
+ if (is_object($callback[0]))
+ {
+ // Object instance syntax
+ $name = get_class($callback[0]).'->'.$callback[1];
+ }
+ else
+ {
+ // Static class syntax
+ $name = $callback[0].'::'.$callback[1];
+ }
+ }
+ else
+ {
+ // Function syntax
+ $name = $callback;
+ }
+
+ throw new Kohana_Exception('Callback %name% used for Validation is not callable', array('%name%' => $name));
+ }
+
+ return $callback;
+ }
+
+ /**
+ * Add a pre-filter to one or more inputs. Pre-filters are applied before
+ * rules or callbacks are executed.
+ *
+ * @chainable
+ * @param callback filter
+ * @param string fields to apply filter to, use TRUE for all fields
+ * @return object
+ */
+ public function pre_filter($filter, $field = TRUE)
+ {
+ if ($field === TRUE OR $field === '*')
+ {
+ // Use wildcard
+ $fields = array('*');
+ }
+ else
+ {
+ // Add the filter to specific inputs
+ $fields = func_get_args();
+ $fields = array_slice($fields, 1);
+ }
+
+ // Convert to a proper callback
+ $filter = $this->callback($filter);
+
+ foreach ($fields as $field)
+ {
+ // Add the filter to specified field
+ $this->pre_filters[$field][] = $filter;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add a post-filter to one or more inputs. Post-filters are applied after
+ * rules and callbacks have been executed.
+ *
+ * @chainable
+ * @param callback filter
+ * @param string fields to apply filter to, use TRUE for all fields
+ * @return object
+ */
+ public function post_filter($filter, $field = TRUE)
+ {
+ if ($field === TRUE)
+ {
+ // Use wildcard
+ $fields = array('*');
+ }
+ else
+ {
+ // Add the filter to specific inputs
+ $fields = func_get_args();
+ $fields = array_slice($fields, 1);
+ }
+
+ // Convert to a proper callback
+ $filter = $this->callback($filter);
+
+ foreach ($fields as $field)
+ {
+ // Add the filter to specified field
+ $this->post_filters[$field][] = $filter;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add rules to a field. Validation rules may only return TRUE or FALSE and
+ * can not manipulate the value of a field.
+ *
+ * @chainable
+ * @param string field name
+ * @param callback rules (one or more arguments)
+ * @return object
+ */
+ public function add_rules($field, $rules)
+ {
+ // Get the rules
+ $rules = func_get_args();
+ $rules = array_slice($rules, 1);
+
+ // Set a default field label
+ $this->label($field);
+
+ if ($field === TRUE)
+ {
+ // Use wildcard
+ $field = '*';
+ }
+
+ foreach ($rules as $rule)
+ {
+ // Arguments for rule
+ $args = NULL;
+
+ // False rule
+ $false_rule = FALSE;
+
+ $rule_tmp = trim(is_string($rule) ? $rule : $rule[1]);
+
+ // Should the rule return false?
+ if ($rule_tmp !== ($rule_name = ltrim($rule_tmp, '! ')))
+ {
+ $false_rule = TRUE;
+ }
+
+ if (is_string($rule))
+ {
+ // Use the updated rule name
+ $rule = $rule_name;
+
+ // Have arguments?
+ if (preg_match('/^([^\[]++)\[(.+)\]$/', $rule, $matches))
+ {
+ // Split the rule into the function and args
+ $rule = $matches[1];
+ $args = preg_split('/(?<!\\\\),\s*/', $matches[2]);
+
+ // Replace escaped comma with comma
+ $args = str_replace('\,', ',', $args);
+ }
+ }
+ else
+ {
+ $rule[1] = $rule_name;
+ }
+
+ if ($rule === 'is_array')
+ {
+ // This field is expected to be an array
+ $this->array_fields[$field] = $field;
+ }
+
+ // Convert to a proper callback
+ $rule = $this->callback($rule);
+
+ // Add the rule, with args, to the field
+ $this->rules[$field][] = array($rule, $args, $false_rule);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add callbacks to a field. Callbacks must accept the Validation object
+ * and the input name. Callback returns are not processed.
+ *
+ * @chainable
+ * @param string field name
+ * @param callbacks callbacks (unlimited number)
+ * @return object
+ */
+ public function add_callbacks($field, $callbacks)
+ {
+ // Get all callbacks as an array
+ $callbacks = func_get_args();
+ $callbacks = array_slice($callbacks, 1);
+
+ // Set a default field label
+ $this->label($field);
+
+ if ($field === TRUE)
+ {
+ // Use wildcard
+ $field = '*';
+ }
+
+ foreach ($callbacks as $callback)
+ {
+ // Convert to a proper callback
+ $callback = $this->callback($callback);
+
+ // Add the callback to specified field
+ $this->callbacks[$field][] = $callback;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Validate by processing pre-filters, rules, callbacks, and post-filters.
+ * All fields that have filters, rules, or callbacks will be initialized if
+ * they are undefined. Validation will only be run if there is data already
+ * in the array.
+ *
+ * @param object Validation object, used only for recursion
+ * @param object name of field for errors
+ * @return bool
+ */
+ public function validate($object = NULL, $field_name = NULL)
+ {
+ if ($object === NULL)
+ {
+ // Use the current object
+ $object = $this;
+ }
+
+ $array = $this->safe_array();
+
+ // Get all defined field names
+ $fields = array_keys($array);
+
+ foreach ($this->pre_filters as $field => $callbacks)
+ {
+ foreach ($callbacks as $callback)
+ {
+ if ($field === '*')
+ {
+ foreach ($fields as $f)
+ {
+ $array[$f] = is_array($array[$f]) ? array_map($callback, $array[$f]) : call_user_func($callback, $array[$f]);
+ }
+ }
+ else
+ {
+ $array[$field] = is_array($array[$field]) ? array_map($callback, $array[$field]) : call_user_func($callback, $array[$field]);
+ }
+ }
+ }
+
+ foreach ($this->rules as $field => $callbacks)
+ {
+ foreach ($callbacks as $callback)
+ {
+ // Separate the callback, arguments and is false bool
+ list ($callback, $args, $is_false) = $callback;
+
+ // Function or method name of the rule
+ $rule = is_array($callback) ? $callback[1] : $callback;
+
+ if ($field === '*')
+ {
+ foreach ($fields as $f)
+ {
+ // Note that continue, instead of break, is used when
+ // applying rules using a wildcard, so that all fields
+ // will be validated.
+
+ if (isset($this->errors[$f]))
+ {
+ // Prevent other rules from being evaluated if an error has occurred
+ continue;
+ }
+
+ if (empty($array[$f]) AND ! in_array($rule, $this->empty_rules))
+ {
+ // This rule does not need to be processed on empty fields
+ continue;
+ }
+
+ $result = ($args === NULL) ? call_user_func($callback, $array[$f]) : call_user_func($callback, $array[$f], $args);
+
+ if (($result == $is_false))
+ {
+ $this->add_error($f, $rule, $args);
+
+ // Stop validating this field when an error is found
+ continue;
+ }
+ }
+ }
+ else
+ {
+ if (isset($this->errors[$field]))
+ {
+ // Prevent other rules from being evaluated if an error has occurred
+ break;
+ }
+
+ if ( ! in_array($rule, $this->empty_rules) AND ! $this->required($array[$field]))
+ {
+ // This rule does not need to be processed on empty fields
+ continue;
+ }
+
+ // Results of our test
+ $result = ($args === NULL) ? call_user_func($callback, $array[$field]) : call_user_func($callback, $array[$field], $args);
+
+ if (($result == $is_false))
+ {
+ $rule = $is_false ? '!'.$rule : $rule;
+ $this->add_error($field, $rule, $args);
+
+ // Stop validating this field when an error is found
+ break;
+ }
+ }
+ }
+ }
+
+ foreach ($this->callbacks as $field => $callbacks)
+ {
+ foreach ($callbacks as $callback)
+ {
+ if ($field === '*')
+ {
+ foreach ($fields as $f)
+ {
+ // Note that continue, instead of break, is used when
+ // applying rules using a wildcard, so that all fields
+ // will be validated.
+
+ if (isset($this->errors[$f]))
+ {
+ // Stop validating this field when an error is found
+ continue;
+ }
+
+ call_user_func($callback, $this, $f);
+ }
+ }
+ else
+ {
+ if (isset($this->errors[$field]))
+ {
+ // Stop validating this field when an error is found
+ break;
+ }
+
+ call_user_func($callback, $this, $field);
+ }
+ }
+ }
+
+ foreach ($this->post_filters as $field => $callbacks)
+ {
+ foreach ($callbacks as $callback)
+ {
+ if ($field === '*')
+ {
+ foreach ($fields as $f)
+ {
+ $array[$f] = is_array($array[$f]) ? array_map($callback, $array[$f]) : call_user_func($callback, $array[$f]);
+ }
+ }
+ else
+ {
+ $array[$field] = is_array($array[$field]) ? array_map($callback, $array[$field]) : call_user_func($callback, $array[$field]);
+ }
+ }
+ }
+
+ // Swap the array back into the object
+ $this->exchangeArray($array);
+
+ // Return TRUE if there are no errors
+ return $this->errors === array();
+ }
+
+ /**
+ * Add an error to an input.
+ *
+ * @chainable
+ * @param string input name
+ * @param string unique error name
+ * @param string arguments to pass to lang file
+ * @return object
+ */
+ public function add_error($field, $name, $args = NULL)
+ {
+ $this->errors[$field] = array($name, $args);
+
+ return $this;
+ }
+
+ /**
+ * Return the errors array.
+ *
+ * @param boolean load errors from a message file
+ * @return array
+ */
+ public function errors($file = NULL)
+ {
+ if ($file === NULL)
+ {
+ $errors = array();
+ foreach($this->errors as $field => $error)
+ {
+ $errors[$field] = $error[0];
+ }
+ return $errors;
+ }
+ else
+ {
+ $errors = array();
+ foreach ($this->errors as $input => $error)
+ {
+ // Locations to check for error messages
+ $error_locations = array
+ (
+ "validation/{$file}.{$input}.{$error[0]}",
+ "validation/{$file}.{$input}.default",
+ "validation/default.{$error[0]}"
+ );
+
+ if (($message = Kohana::message($error_locations[0])) !== $error_locations[0])
+ {
+ // Found a message for this field and error
+ }
+ elseif (($message = Kohana::message($error_locations[1])) !== $error_locations[1])
+ {
+ // Found a default message for this field
+ }
+ elseif (($message = Kohana::message($error_locations[2])) !== $error_locations[2])
+ {
+ // Found a default message for this error
+ }
+ else
+ {
+ // No message exists, display the path expected
+ $message = "validation/{$file}.{$input}.{$error[0]}";
+ }
+
+ // Start the translation values list
+ $values = array(':field' => __($this->labels[$input]));
+
+ if ( ! empty($error[1]))
+ {
+ foreach ($error[1] as $key => $value)
+ {
+ // Add each parameter as a numbered value, starting from 1
+ $values[':param'.($key + 1)] = __($value);
+ }
+ }
+
+ // Translate the message using the default language
+ $errors[$input] = __($message, $values);
+ }
+
+ return $errors;
+ }
+ }
+
+ /**
+ * Rule: required. Generates an error if the field has an empty value.
+ *
+ * @param mixed input value
+ * @return bool
+ */
+ public function required($str)
+ {
+ if (is_object($str) AND $str instanceof ArrayObject)
+ {
+ // Get the array from the ArrayObject
+ $str = $str->getArrayCopy();
+ }
+
+ if (is_array($str))
+ {
+ return ! empty($str);
+ }
+ else
+ {
+ return ! ($str === '' OR $str === NULL OR $str === FALSE);
+ }
+ }
+
+ /**
+ * Rule: matches. Generates an error if the field does not match one or more
+ * other fields.
+ *
+ * @param mixed input value
+ * @param array input names to match against
+ * @return bool
+ */
+ public function matches($str, array $inputs)
+ {
+ foreach ($inputs as $key)
+ {
+ if ($str !== (isset($this[$key]) ? $this[$key] : NULL))
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ /**
+ * Rule: length. Generates an error if the field is too long or too short.
+ *
+ * @param mixed input value
+ * @param array minimum, maximum, or exact length to match
+ * @return bool
+ */
+ public function length($str, array $length)
+ {
+ if ( ! is_string($str))
+ return FALSE;
+
+ $size = mb_strlen($str);
+ $status = FALSE;
+
+ if (count($length) > 1)
+ {
+ list ($min, $max) = $length;
+
+ if ($size >= $min AND $size <= $max)
+ {
+ $status = TRUE;
+ }
+ }
+ else
+ {
+ $status = ($size === (int) $length[0]);
+ }
+
+ return $status;
+ }
+
+ /**
+ * Rule: depends_on. Generates an error if the field does not depend on one
+ * or more other fields.
+ *
+ * @param mixed field name
+ * @param array field names to check dependency
+ * @return bool
+ */
+ public function depends_on($field, array $fields)
+ {
+ foreach ($fields as $depends_on)
+ {
+ if ( ! isset($this[$depends_on]) OR $this[$depends_on] == NULL)
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ /**
+ * Rule: chars. Generates an error if the field contains characters outside of the list.
+ *
+ * @param string field value
+ * @param array allowed characters
+ * @return bool
+ */
+ public function chars($value, array $chars)
+ {
+ return ! preg_match('![^'.implode('', $chars).']!u', $value);
+ }
+
+} // End Validation \ No newline at end of file
diff --git a/system/libraries/View.php b/system/libraries/View.php
new file mode 100644
index 0000000..ba482bb
--- /dev/null
+++ b/system/libraries/View.php
@@ -0,0 +1,329 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Loads and displays Kohana view files. Can also handle output of some binary
+ * files, such as image, Javascript, and CSS files.
+ *
+ * $Id: View.php 4729 2009-12-29 20:35:19Z isaiah $
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class View_Core {
+
+ // The view file name and type
+ protected $kohana_filename = FALSE;
+ protected $kohana_filetype = FALSE;
+
+ // View variable storage
+ protected $kohana_local_data = array();
+
+ /**
+ * Creates a new View using the given parameters.
+ *
+ * @param string view name
+ * @param array pre-load data
+ * @param string type of file: html, css, js, etc.
+ * @return object
+ */
+ public static function factory($name = NULL, $data = NULL, $type = NULL)
+ {
+ return new View($name, $data, $type);
+ }
+
+ /**
+ * Attempts to load a view and pre-load view data.
+ *
+ * @throws Kohana_Exception if the requested view cannot be found
+ * @param string view name
+ * @param array pre-load data
+ * @param string type of file: html, css, js, etc.
+ * @return void
+ */
+ public function __construct($name = NULL, $data = NULL, $type = NULL)
+ {
+ if (is_string($name) AND $name !== '')
+ {
+ // Set the filename
+ $this->set_filename($name, $type);
+ }
+
+ if (is_array($data) AND ! empty($data))
+ {
+ // Preload data using array_merge, to allow user extensions
+ $this->kohana_local_data = array_merge($this->kohana_local_data, $data);
+ }
+ }
+
+ /**
+ * Magic method access to test for view property
+ *
+ * @param string View property to test for
+ * @return boolean
+ */
+ public function __isset($key = NULL)
+ {
+ return $this->is_set($key);
+ }
+
+ /**
+ * Sets the view filename.
+ *
+ * @chainable
+ * @param string view filename
+ * @param string view file type
+ * @return object
+ */
+ public function set_filename($name, $type = NULL)
+ {
+ if ($type == NULL)
+ {
+ // Load the filename and set the content type
+ $this->kohana_filename = Kohana::find_file('views', $name, TRUE);
+ $this->kohana_filetype = EXT;
+ }
+ else
+ {
+ // Check if the filetype is allowed by the configuration
+ if ( ! in_array($type, Kohana::config('view.allowed_filetypes')))
+ throw new Kohana_Exception('The requested filetype, .:type:, is not allowed in your view configuration file', array(':type:' => $type));
+
+ // Load the filename and set the content type
+ $this->kohana_filename = Kohana::find_file('views', $name, TRUE, $type);
+ $this->kohana_filetype = Kohana::config('mimes.'.$type);
+
+ if ($this->kohana_filetype == NULL)
+ {
+ // Use the specified type
+ $this->kohana_filetype = $type;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets a view variable.
+ *
+ * @param string|array name of variable or an array of variables
+ * @param mixed value when using a named variable
+ * @return object
+ */
+ public function set($name, $value = NULL)
+ {
+ if (is_array($name))
+ {
+ foreach ($name as $key => $value)
+ {
+ $this->__set($key, $value);
+ }
+ }
+ else
+ {
+ $this->__set($name, $value);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Checks for a property existence in the view locally or globally. Unlike the built in __isset(),
+ * this method can take an array of properties to test simultaneously.
+ *
+ * @param string $key property name to test for
+ * @param array $key array of property names to test for
+ * @return boolean property test result
+ * @return array associative array of keys and boolean test result
+ */
+ public function is_set( $key = FALSE )
+ {
+ // Setup result;
+ $result = FALSE;
+
+ // If key is an array
+ if (is_array($key))
+ {
+ // Set the result to an array
+ $result = array();
+
+ // Foreach key
+ foreach ($key as $property)
+ {
+ // Set the result to an associative array
+ $result[$property] = (array_key_exists($property, $this->kohana_local_data)) ? TRUE : FALSE;
+ }
+ }
+ else
+ {
+ // Otherwise just check one property
+ $result = (array_key_exists($key, $this->kohana_local_data)) ? TRUE : FALSE;
+ }
+
+ // Return the result
+ return $result;
+ }
+
+ /**
+ * Sets a bound variable by reference.
+ *
+ * @param string name of variable
+ * @param mixed variable to assign by reference
+ * @return object
+ */
+ public function bind($name, & $var)
+ {
+ $this->kohana_local_data[$name] =& $var;
+
+ return $this;
+ }
+
+ /**
+ * Magically sets a view variable.
+ *
+ * @param string variable key
+ * @param string variable value
+ * @return void
+ */
+ public function __set($key, $value)
+ {
+ $this->kohana_local_data[$key] = $value;
+ }
+
+ /**
+ * Magically gets a view variable.
+ *
+ * @param string variable key
+ * @return mixed variable value if the key is found
+ * @return void if the key is not found
+ */
+ public function &__get($key)
+ {
+ if (isset($this->kohana_local_data[$key]))
+ {
+ return $this->kohana_local_data[$key];
+ }
+ elseif (isset($this->$key))
+ {
+ return $this->$key;
+ }
+ else
+ {
+ throw new Kohana_Exception('Undefined view variable: :var',
+ array(':var' => $key));
+ }
+ }
+
+ /**
+ * Magically converts view object to string.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ try
+ {
+ return $this->render();
+ }
+ catch (Exception $e)
+ {
+ Kohana_Exception::handle($e);
+ return (string) '';
+ }
+ }
+
+ /**
+ * Renders a view.
+ *
+ * @param boolean set to TRUE to echo the output instead of returning it
+ * @param callback special renderer to pass the output through
+ * @param callback modifier to pass the data through before rendering
+ * @return string if print is FALSE
+ * @return void if print is TRUE
+ */
+ public function render($print = FALSE, $renderer = FALSE, $modifier = FALSE)
+ {
+ if (empty($this->kohana_filename))
+ throw new Kohana_Exception('You must set the the view filename before calling render');
+
+ if (is_string($this->kohana_filetype))
+ {
+ // Merge global and local data, local overrides global with the same name
+ $data = $this->kohana_local_data;
+
+ if ($modifier !== FALSE AND is_callable($modifier, TRUE))
+ {
+ // Pass the data through the user defined modifier
+ $data = call_user_func($modifier, $data);
+ }
+
+ $output = $this->load_view($this->kohana_filename, $data);
+
+ if ($renderer !== FALSE AND is_callable($renderer, TRUE))
+ {
+ // Pass the output through the user defined renderer
+ $output = call_user_func($renderer, $output);
+ }
+
+ if ($print === TRUE)
+ {
+ // Display the output
+ echo $output;
+ return;
+ }
+ }
+ else
+ {
+ // Set the content type and size
+ header('Content-Type: '.$this->kohana_filetype[0]);
+
+ if ($print === TRUE)
+ {
+ if ($file = fopen($this->kohana_filename, 'rb'))
+ {
+ // Display the output
+ fpassthru($file);
+ fclose($file);
+ }
+ return;
+ }
+
+ // Fetch the file contents
+ $output = file_get_contents($this->kohana_filename);
+ }
+
+ return $output;
+ }
+
+ /**
+ * Includes a View within the controller scope.
+ *
+ * @param string view filename
+ * @param array array of view variables
+ * @return string
+ */
+ public function load_view($kohana_view_filename, $kohana_input_data)
+ {
+ if ($kohana_view_filename == '')
+ return;
+
+ // Buffering on
+ ob_start();
+
+ // Import the view variables to local namespace
+ extract($kohana_input_data, EXTR_SKIP);
+
+ try
+ {
+ include $kohana_view_filename;
+ }
+ catch (Exception $e)
+ {
+ ob_end_clean();
+ throw $e;
+ }
+
+ // Fetch the output and close the buffer
+ return ob_get_clean();
+ }
+} // End View
diff --git a/system/libraries/drivers/Cache.php b/system/libraries/drivers/Cache.php
new file mode 100644
index 0000000..9741509
--- /dev/null
+++ b/system/libraries/drivers/Cache.php
@@ -0,0 +1,42 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Cache driver abstract class.
+ *
+ * $Id$
+ *
+ * @package Cache
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+abstract class Cache_Driver {
+ /**
+ * Set cache items
+ */
+ abstract public function set($items, $tags = NULL, $lifetime = NULL);
+
+ /**
+ * Get a cache items by key
+ */
+ abstract public function get($keys, $single = FALSE);
+
+ /**
+ * Get cache items by tag
+ */
+ abstract public function get_tag($tags);
+
+ /**
+ * Delete cache item by key
+ */
+ abstract public function delete($keys);
+
+ /**
+ * Delete cache items by tag
+ */
+ abstract public function delete_tag($tags);
+
+ /**
+ * Empty the cache
+ */
+ abstract public function delete_all();
+} // End Cache Driver \ No newline at end of file
diff --git a/system/libraries/drivers/Cache/File.php b/system/libraries/drivers/Cache/File.php
new file mode 100644
index 0000000..d6ec037
--- /dev/null
+++ b/system/libraries/drivers/Cache/File.php
@@ -0,0 +1,255 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Memcache-based Cache driver.
+ *
+ * $Id: File.php 4605 2009-09-14 17:22:21Z kiall $
+ *
+ * @package Cache
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Cache_File_Driver extends Cache_Driver {
+ protected $config;
+ protected $backend;
+
+ public function __construct($config)
+ {
+ $this->config = $config;
+ $this->config['directory'] = str_replace('\\', '/', realpath($this->config['directory'])).'/';
+
+ if ( ! is_dir($this->config['directory']) OR ! is_writable($this->config['directory']))
+ throw new Cache_Exception('The configured cache directory, :directory:, is not writable.', array(':directory:' => $this->config['directory']));
+ }
+
+ /**
+ * Finds an array of files matching the given id or tag.
+ *
+ * @param string cache key or tag
+ * @param bool search for tags
+ * @return array of filenames matching the id or tag
+ */
+ public function exists($keys, $tag = FALSE)
+ {
+ if ($keys === TRUE)
+ {
+ // Find all the files
+ return glob($this->config['directory'].'*~*~*');
+ }
+ elseif ($tag === TRUE)
+ {
+ // Find all the files that have the tag name
+ $paths = array();
+
+ foreach ( (array) $keys as $tag)
+ {
+ $paths = array_merge($paths, glob($this->config['directory'].'*~*'.$tag.'*~*'));
+ }
+
+ // Find all tags matching the given tag
+ $files = array();
+
+ foreach ($paths as $path)
+ {
+ // Split the files
+ $tags = explode('~', basename($path));
+
+ // Find valid tags
+ if (count($tags) !== 3 OR empty($tags[1]))
+ continue;
+
+ // Split the tags by plus signs, used to separate tags
+ $item_tags = explode('+', $tags[1]);
+
+ // Check each supplied tag, and match aginst the tags on each item.
+ foreach ($keys as $tag)
+ {
+ if (in_array($tag, $item_tags))
+ {
+ // Add the file to the array, it has the requested tag
+ $files[] = $path;
+ }
+ }
+ }
+
+ return $files;
+ }
+ else
+ {
+ $paths = array();
+
+ foreach ( (array) $keys as $key)
+ {
+ // Find the file matching the given key
+ $paths = array_merge($paths, glob($this->config['directory'].str_replace(array('/', '\\', ' '), '_', $key).'~*'));
+ }
+
+ return $paths;
+ }
+ }
+
+ public function set($items, $tags = NULL, $lifetime = NULL)
+ {
+ if ($lifetime !== 0)
+ {
+ // File driver expects unix timestamp
+ $lifetime += time();
+ }
+
+
+ if ( ! is_null($tags) AND ! empty($tags))
+ {
+ // Convert the tags into a string list
+ $tags = implode('+', (array) $tags);
+ }
+
+ $success = TRUE;
+
+ foreach ($items as $key => $value)
+ {
+ if (is_resource($value))
+ throw new Cache_Exception('Caching of resources is impossible, because resources cannot be serialised.');
+
+ // Remove old cache file
+ $this->delete($key);
+
+ if ( ! (bool) file_put_contents($this->config['directory'].str_replace(array('/', '\\', ' '), '_', $key).'~'.$tags.'~'.$lifetime, serialize($value)))
+ {
+ $success = FALSE;
+ }
+ }
+
+ return $success;
+ }
+
+ public function get($keys, $single = FALSE)
+ {
+ $items = array();
+
+ if ($files = $this->exists($keys))
+ {
+ // Turn off errors while reading the files
+ $ER = error_reporting(0);
+
+ foreach ($files as $file)
+ {
+ // Validate that the item has not expired
+ if ($this->expired($file))
+ continue;
+
+ list($key, $junk) = explode('~', basename($file), 2);
+
+ if (($data = file_get_contents($file)) !== FALSE)
+ {
+ // Unserialize the data
+ $data = unserialize($data);
+ }
+ else
+ {
+ $data = NULL;
+ }
+
+ $items[$key] = $data;
+ }
+
+ // Turn errors back on
+ error_reporting($ER);
+ }
+
+ // Reutrn a single item if only one key was requested
+ if ($single)
+ {
+ return (count($items) > 0) ? current($items) : NULL;
+ }
+ else
+ {
+ return $items;
+ }
+ }
+
+ /**
+ * Get cache items by tag
+ */
+ public function get_tag($tags)
+ {
+ // An array will always be returned
+ $result = array();
+
+ if ($paths = $this->exists($tags, TRUE))
+ {
+ // Find all the files with the given tag
+ foreach ($paths as $path)
+ {
+ // Get the id from the filename
+ list($key, $junk) = explode('~', basename($path), 2);
+
+ if (($data = $this->get($key, TRUE)) !== FALSE)
+ {
+ // Add the result to the array
+ $result[$key] = $data;
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Delete cache items by keys or tags
+ */
+ public function delete($keys, $tag = FALSE)
+ {
+ $success = TRUE;
+
+ $paths = $this->exists($keys, $tag);
+
+ // Disable all error reporting while deleting
+ $ER = error_reporting(0);
+
+ foreach ($paths as $path)
+ {
+ // Remove the cache file
+ if ( ! unlink($path))
+ {
+ Kohana_Log::add('error', 'Cache: Unable to delete cache file: '.$path);
+ $success = FALSE;
+ }
+ }
+
+ // Turn on error reporting again
+ error_reporting($ER);
+
+ return $success;
+ }
+
+ /**
+ * Delete cache items by tag
+ */
+ public function delete_tag($tags)
+ {
+ return $this->delete($tags, TRUE);
+ }
+
+ /**
+ * Empty the cache
+ */
+ public function delete_all()
+ {
+ return $this->delete(TRUE);
+ }
+
+ /**
+ * Check if a cache file has expired by filename.
+ *
+ * @param string|array array of filenames
+ * @return bool
+ */
+ protected function expired($file)
+ {
+ // Get the expiration time
+ $expires = (int) substr($file, strrpos($file, '~') + 1);
+
+ // Expirations of 0 are "never expire"
+ return ($expires !== 0 AND $expires <= time());
+ }
+} // End Cache Memcache Driver
diff --git a/system/libraries/drivers/Cache/Memcache.php b/system/libraries/drivers/Cache/Memcache.php
new file mode 100644
index 0000000..13d61d8
--- /dev/null
+++ b/system/libraries/drivers/Cache/Memcache.php
@@ -0,0 +1,132 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Memcache-based Cache driver.
+ *
+ * $Id$
+ *
+ * @package Cache
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Cache_Memcache_Driver extends Cache_Driver {
+ protected $config;
+ protected $backend;
+ protected $flags;
+
+ public function __construct($config)
+ {
+ if ( ! extension_loaded('memcache'))
+ throw new Cache_Exception('The memcache PHP extension must be loaded to use this driver.');
+
+ ini_set('memcache.allow_failover', (isset($config['allow_failover']) AND $config['allow_failover']) ? TRUE : FALSE);
+
+ $this->config = $config;
+ $this->backend = new Memcache;
+
+ $this->flags = (isset($config['compression']) AND $config['compression']) ? MEMCACHE_COMPRESSED : FALSE;
+
+ foreach ($config['servers'] as $server)
+ {
+ // Make sure all required keys are set
+ $server += array('host' => '127.0.0.1',
+ 'port' => 11211,
+ 'persistent' => FALSE,
+ 'weight' => 1,
+ 'timeout' => 1,
+ 'retry_interval' => 15
+ );
+
+ // Add the server to the pool
+ $this->backend->addServer($server['host'], $server['port'], (bool) $server['persistent'], (int) $server['weight'], (int) $server['timeout'], (int) $server['retry_interval'], TRUE, array($this,'_memcache_failure_callback'));
+ }
+ }
+
+ public function _memcache_failure_callback($host, $port)
+ {
+ $this->backend->setServerParams($host, $port, 1, -1, FALSE);
+ Kohana_Log::add('error', __('Cache: Memcache server down: :host:::port:',array(':host:' => $host,':port:' => $port)));
+ }
+
+ public function set($items, $tags = NULL, $lifetime = NULL)
+ {
+ if ($lifetime !== 0)
+ {
+ // Memcache driver expects unix timestamp
+ $lifetime += time();
+ }
+
+ if ($tags !== NULL)
+ throw new Cache_Exception('Memcache driver does not support tags');
+
+ foreach ($items as $key => $value)
+ {
+ if (is_resource($value))
+ throw new Cache_Exception('Caching of resources is impossible, because resources cannot be serialised.');
+
+ if ( ! $this->backend->set($key, $value, $this->flags, $lifetime))
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+ }
+
+ public function get($keys, $single = FALSE)
+ {
+ $items = $this->backend->get($keys);
+
+ if ($single)
+ {
+ if ($items === FALSE)
+ return NULL;
+
+ return (count($items) > 0) ? current($items) : NULL;
+ }
+ else
+ {
+ return ($items === FALSE) ? array() : $items;
+ }
+ }
+
+ /**
+ * Get cache items by tag
+ */
+ public function get_tag($tags)
+ {
+ throw new Cache_Exception('Memcache driver does not support tags');
+ }
+
+ /**
+ * Delete cache item by key
+ */
+ public function delete($keys)
+ {
+ foreach ($keys as $key)
+ {
+ if ( ! $this->backend->delete($key))
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+ }
+
+ /**
+ * Delete cache items by tag
+ */
+ public function delete_tag($tags)
+ {
+ throw new Cache_Exception('Memcache driver does not support tags');
+ }
+
+ /**
+ * Empty the cache
+ */
+ public function delete_all()
+ {
+ return $this->backend->flush();
+ }
+} // End Cache Memcache Driver
diff --git a/system/libraries/drivers/Cache/Xcache.php b/system/libraries/drivers/Cache/Xcache.php
new file mode 100644
index 0000000..6761983
--- /dev/null
+++ b/system/libraries/drivers/Cache/Xcache.php
@@ -0,0 +1,161 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * XCache-based Cache driver.
+ *
+ * $Id: Memcache.php 4605 2009-09-14 17:22:21Z kiall $
+ *
+ * @package Cache
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ * @TODO Check if XCache cleans its own keys.
+ */
+class Cache_Xcache_Driver extends Cache_Driver {
+ protected $config;
+
+ public function __construct($config)
+ {
+ if ( ! extension_loaded('xcache'))
+ throw new Cache_Exception('The xcache PHP extension must be loaded to use this driver.');
+
+ $this->config = $config;
+ }
+
+ public function set($items, $tags = NULL, $lifetime = NULL)
+ {
+ if ($tags !== NULL)
+ {
+ Kohana_Log::add('debug', __('Cache: XCache driver does not support tags'));
+ }
+
+ foreach ($items as $key => $value)
+ {
+ if (is_resource($value))
+ throw new Cache_Exception('Caching of resources is impossible, because resources cannot be serialised.');
+
+ if ( ! xcache_set($key, $value, $lifetime))
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+ }
+
+ public function get($keys, $single = FALSE)
+ {
+ $items = array();
+
+ foreach ($keys as $key)
+ {
+ if (xcache_isset($key))
+ {
+ $items[$key] = xcache_get($key);
+ }
+ else
+ {
+ $items[$key] = NULL;
+ }
+ }
+
+ if ($single)
+ {
+ return ($items === FALSE OR count($items) > 0) ? current($items) : NULL;
+ }
+ else
+ {
+ return ($items === FALSE) ? array() : $items;
+ }
+ }
+
+ /**
+ * Get cache items by tag
+ */
+ public function get_tag($tags)
+ {
+ Kohana_Log::add('debug', __('Cache: XCache driver does not support tags'));
+ return NULL;
+ }
+
+ /**
+ * Delete cache item by key
+ */
+ public function delete($keys)
+ {
+ foreach ($keys as $key)
+ {
+ if ( ! xcache_unset($key))
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+ }
+
+ /**
+ * Delete cache items by tag
+ */
+ public function delete_tag($tags)
+ {
+ Kohana_Log::add('debug', __('Cache: XCache driver does not support tags'));
+ return NULL;
+ }
+
+ /**
+ * Empty the cache
+ */
+ public function delete_all()
+ {
+ $this->auth();
+ $result = TRUE;
+
+ for ($i = 0, $max = xcache_count(XC_TYPE_VAR); $i < $max; $i++)
+ {
+ if (xcache_clear_cache(XC_TYPE_VAR, $i) !== NULL)
+ {
+ $result = FALSE;
+ break;
+ }
+ }
+
+ // Undo the login
+ $this->auth(TRUE);
+
+ return $result;
+ }
+
+ private function auth($reverse = FALSE)
+ {
+ static $backup = array();
+
+ $keys = array('PHP_AUTH_USER', 'PHP_AUTH_PW');
+
+ foreach ($keys as $key)
+ {
+ if ($reverse)
+ {
+ if (isset($backup[$key]))
+ {
+ $_SERVER[$key] = $backup[$key];
+ unset($backup[$key]);
+ }
+ else
+ {
+ unset($_SERVER[$key]);
+ }
+ }
+ else
+ {
+ $value = getenv($key);
+
+ if ( ! empty($value))
+ {
+ $backup[$key] = $value;
+ }
+
+ $_SERVER[$key] = $this->config->{$key};
+ }
+ }
+ }
+} // End Cache XCache Driver
diff --git a/system/libraries/drivers/Config.php b/system/libraries/drivers/Config.php
new file mode 100644
index 0000000..a82684b
--- /dev/null
+++ b/system/libraries/drivers/Config.php
@@ -0,0 +1,257 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Kohana_Config abstract driver to get and set
+ * configuration options.
+ *
+ * Specific drivers should implement caching and encryption
+ * as they deem appropriate.
+ *
+ * $Id: Config.php 4679 2009-11-10 01:45:52Z isaiah $
+ *
+ * @package Kohana_Config
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ * @abstract
+ */
+abstract class Config_Driver {
+
+ /**
+ * Internal caching
+ *
+ * @var Cache
+ */
+ protected $cache;
+
+ /**
+ * The name of the internal cache
+ *
+ * @var string
+ */
+ protected $cache_name = 'Kohana_Config_Cache';
+
+ /**
+ * Cache Lifetime
+ *
+ * @var mixed
+ */
+ protected $cache_lifetime = FALSE;
+
+ /**
+ * The Encryption library
+ *
+ * @var Encrypt
+ */
+ protected $encrypt;
+
+ /**
+ * The config loaded
+ *
+ * @var array
+ */
+ protected $config = array();
+
+ /**
+ * The changed status of configuration values,
+ * current state versus the stored state.
+ *
+ * @var bool
+ */
+ protected $changed = FALSE;
+
+ /**
+ * Determines if any config has been loaded yet
+ */
+ public $loaded = FALSE;
+
+ /**
+ * Array driver constructor. Sets up the PHP array
+ * driver, including caching and encryption if
+ * required
+ *
+ * @access public
+ */
+ public function __construct($config)
+ {
+
+ if (($cache_setting = $config['internal_cache']) !== FALSE)
+ {
+ $this->cache_lifetime = $cache_setting;
+ // Restore the cached configuration
+ $this->config = $this->load_cache();
+
+ if (count($this->config) > 0)
+ $this->loaded = TRUE;
+
+ // Add the save cache method to system.shutshut event
+ Event::add('system.shutdown', array($this, 'save_cache'));
+ }
+
+ }
+
+ /**
+ * Gets a value from config. If required is TRUE
+ * then get will throw an exception if value cannot
+ * be loaded.
+ *
+ * @param string key the setting to get
+ * @param bool slash remove trailing slashes
+ * @param bool required is setting required?
+ * @return mixed
+ * @access public
+ */
+ public function get($key, $slash = FALSE, $required = FALSE)
+ {
+ // Get the group name from the key
+ $group = explode('.', $key, 2);
+ $group = $group[0];
+
+ // Check for existing value and load it dynamically if required
+ if ( ! isset($this->config[$group]))
+ $this->config[$group] = $this->load($group, $required);
+
+ // Get the value of the key string
+ $value = Kohana::key_string($this->config, $key);
+
+ if ($slash === TRUE AND is_string($value) AND $value !== '')
+ {
+ // Force the value to end with "/"
+ $value = rtrim($value, '/').'/';
+ }
+
+ if (($required === TRUE) AND ($value === null))
+ throw new Kohana_Config_Exception('Value not found in config driver');
+
+ $this->loaded = TRUE;
+ return $value;
+ }
+
+ /**
+ * Sets a new value to the configuration
+ *
+ * @param string key
+ * @param mixed value
+ * @return bool
+ * @access public
+ */
+ public function set($key, $value)
+ {
+ // Do this to make sure that the config array is already loaded
+ $this->get($key);
+
+ if (substr($key, 0, 7) === 'routes.')
+ {
+ // Routes cannot contain sub keys due to possible dots in regex
+ $keys = explode('.', $key, 2);
+ }
+ else
+ {
+ // Convert dot-noted key string to an array
+ $keys = explode('.', $key);
+ }
+
+ // Used for recursion
+ $conf =& $this->config;
+ $last = count($keys) - 1;
+
+ foreach ($keys as $i => $k)
+ {
+ if ($i === $last)
+ {
+ $conf[$k] = $value;
+ }
+ else
+ {
+ $conf =& $conf[$k];
+ }
+ }
+
+ if (substr($key,0,12) === 'core.modules')
+ {
+ // Reprocess the include paths
+ Kohana::include_paths(TRUE);
+ }
+
+ // Set config to changed
+ return $this->changed = TRUE;
+ }
+
+ /**
+ * Clear the configuration
+ *
+ * @param string group
+ * @return bool
+ * @access public
+ */
+ public function clear($group)
+ {
+ // Remove the group from config
+ unset($this->config[$group]);
+
+ // Set config to changed
+ return $this->changed = TRUE;
+ }
+
+ /**
+ * Checks whether the setting exists in
+ * config
+ *
+ * @param string $key
+ * @return bool
+ * @access public
+ */
+ public function setting_exists($key)
+ {
+ return $this->get($key) === NULL;
+ }
+
+ /**
+ * Loads a configuration group based on the setting
+ *
+ * @param string group
+ * @param bool required
+ * @return array
+ * @access public
+ * @abstract
+ */
+ abstract public function load($group, $required = FALSE);
+
+ /**
+ * Loads the cached version of this configuration driver
+ *
+ * @return array
+ * @access public
+ */
+ public function load_cache()
+ {
+ // Load the cache for this configuration
+ $cached_config = Kohana::cache($this->cache_name, $this->cache_lifetime);
+
+ // If the configuration wasn't loaded from the cache
+ if ($cached_config === NULL)
+ $cached_config = array();
+
+ // Return the cached config
+ return $cached_config;
+ }
+
+ /**
+ * Saves a cached version of this configuration driver
+ *
+ * @return bool
+ * @access public
+ */
+ public function save_cache()
+ {
+ // If this configuration has changed
+ if ($this->get('core.internal_cache') !== FALSE AND $this->changed)
+ {
+ $data = $this->config;
+
+ // Save the cache
+ return Kohana::cache_save($this->cache_name, $data, $this->cache_lifetime);
+ }
+
+ return TRUE;
+ }
+} // End Kohana_Config_Driver \ No newline at end of file
diff --git a/system/libraries/drivers/Config/Array.php b/system/libraries/drivers/Config/Array.php
new file mode 100644
index 0000000..b2ca19b
--- /dev/null
+++ b/system/libraries/drivers/Config/Array.php
@@ -0,0 +1,83 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Kohana_Config Array driver to get and set
+ * configuration options using PHP arrays.
+ *
+ * This driver can cache and encrypt settings
+ * if required.
+ *
+ * $Id: Array.php 4679 2009-11-10 01:45:52Z isaiah $
+ *
+ * @package Kohana_Config
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Config_Array_Driver extends Config_Driver {
+
+ /**
+ * Internal caching
+ *
+ * @var Cache
+ */
+ protected $cache;
+
+ /**
+ * The name of the internal cache
+ *
+ * @var string
+ */
+ protected $cache_name = 'Kohana_Config_Array_Cache';
+
+ /**
+ * The Encryption library
+ *
+ * @var Encrypt
+ */
+ protected $encrypt;
+
+ /**
+ * Loads a configuration group based on the setting
+ *
+ * @param string group
+ * @param bool required
+ * @return array
+ * @access public
+ */
+ public function load($group, $required = FALSE)
+ {
+ if ($group === 'core')
+ {
+ // Load the application configuration file
+ require APPPATH.'config/config'.EXT;
+
+ if ( ! isset($config['site_domain']))
+ {
+ // Invalid config file
+ throw new Kohana_Config_Exception('Your Kohana application configuration file is not valid.');
+ }
+
+ return $config;
+ }
+
+ // Load matching configs
+ $configuration = array();
+
+ if ($files = Kohana::find_file('config', $group, $required))
+ {
+ foreach ($files as $file)
+ {
+ require $file;
+
+ if (isset($config) AND is_array($config))
+ {
+ // Merge in configuration
+ $configuration = array_merge($configuration, $config);
+ }
+ }
+ }
+
+ // Return merged configuration
+ return $configuration;
+ }
+} // End Config_Array_Driver \ No newline at end of file
diff --git a/system/libraries/drivers/Image.php b/system/libraries/drivers/Image.php
new file mode 100644
index 0000000..39936c3
--- /dev/null
+++ b/system/libraries/drivers/Image.php
@@ -0,0 +1,158 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Image API driver.
+ *
+ * $Id: Image.php 4679 2009-11-10 01:45:52Z isaiah $
+ *
+ * @package Image
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+abstract class Image_Driver {
+
+ // Reference to the current image
+ protected $image;
+
+ // Reference to the temporary processing image
+ protected $tmp_image;
+
+ // Processing errors
+ protected $errors = array();
+
+ /**
+ * Executes a set of actions, defined in pairs.
+ *
+ * @param array actions
+ * @return boolean
+ */
+ public function execute($actions)
+ {
+ foreach ($actions as $func => $args)
+ {
+ if ( ! $this->$func($args))
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ /**
+ * Sanitize and normalize a geometry array based on the temporary image
+ * width and height. Valid properties are: width, height, top, left.
+ *
+ * @param array geometry properties
+ * @return void
+ */
+ protected function sanitize_geometry( & $geometry)
+ {
+ list($width, $height) = $this->properties();
+
+ // Turn off error reporting
+ $reporting = error_reporting(0);
+
+ // Width and height cannot exceed current image size
+ $geometry['width'] = min($geometry['width'], $width);
+ $geometry['height'] = min($geometry['height'], $height);
+
+ // Set standard coordinates if given, otherwise use pixel values
+ if ($geometry['top'] === 'center')
+ {
+ $geometry['top'] = floor(($height / 2) - ($geometry['height'] / 2));
+ }
+ elseif ($geometry['top'] === 'top')
+ {
+ $geometry['top'] = 0;
+ }
+ elseif ($geometry['top'] === 'bottom')
+ {
+ $geometry['top'] = $height - $geometry['height'];
+ }
+
+ // Set standard coordinates if given, otherwise use pixel values
+ if ($geometry['left'] === 'center')
+ {
+ $geometry['left'] = floor(($width / 2) - ($geometry['width'] / 2));
+ }
+ elseif ($geometry['left'] === 'left')
+ {
+ $geometry['left'] = 0;
+ }
+ elseif ($geometry['left'] === 'right')
+ {
+ $geometry['left'] = $width - $geometry['height'];
+ }
+
+ // Restore error reporting
+ error_reporting($reporting);
+ }
+
+ /**
+ * Return the current width and height of the temporary image. This is mainly
+ * needed for sanitizing the geometry.
+ *
+ * @return array width, height
+ */
+ abstract protected function properties();
+
+ /**
+ * Process an image with a set of actions.
+ *
+ * @param string image filename
+ * @param array actions to execute
+ * @param string destination directory path
+ * @param string destination filename
+ * @param boolean render the image
+ * @param string background color
+ * @return boolean
+ */
+ abstract public function process($image, $actions, $dir, $file, $render = FALSE, $background = NULL);
+
+ /**
+ * Flip an image. Valid directions are horizontal and vertical.
+ *
+ * @param integer direction to flip
+ * @return boolean
+ */
+ abstract function flip($direction);
+
+ /**
+ * Crop an image. Valid properties are: width, height, top, left.
+ *
+ * @param array new properties
+ * @return boolean
+ */
+ abstract function crop($properties);
+
+ /**
+ * Resize an image. Valid properties are: width, height, and master.
+ *
+ * @param array new properties
+ * @return boolean
+ */
+ abstract public function resize($properties);
+
+ /**
+ * Rotate an image. Valid amounts are -180 to 180.
+ *
+ * @param integer amount to rotate
+ * @return boolean
+ */
+ abstract public function rotate($amount);
+
+ /**
+ * Sharpen and image. Valid amounts are 1 to 100.
+ *
+ * @param integer amount to sharpen
+ * @return boolean
+ */
+ abstract public function sharpen($amount);
+
+ /**
+ * Overlay a second image. Valid properties are: overlay_file, mime, x, y and transparency.
+ *
+ * @return boolean
+ */
+ abstract public function composite($properties);
+
+} // End Image Driver \ No newline at end of file
diff --git a/system/libraries/drivers/Image/GD.php b/system/libraries/drivers/Image/GD.php
new file mode 100644
index 0000000..6ffffe8
--- /dev/null
+++ b/system/libraries/drivers/Image/GD.php
@@ -0,0 +1,440 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * GD Image Driver.
+ *
+ * $Id: GD.php 4679 2009-11-10 01:45:52Z isaiah $
+ *
+ * @package Image
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Image_GD_Driver extends Image_Driver {
+
+ // A transparent PNG as a string
+ protected static $blank_png;
+ protected static $blank_png_width;
+ protected static $blank_png_height;
+
+ public function __construct()
+ {
+ // Make sure that GD2 is available
+ if ( ! function_exists('gd_info'))
+ throw new Kohana_Exception('The Image library requires GD2. Please see http://php.net/gd_info for more information.');
+
+ // Get the GD information
+ $info = gd_info();
+
+ // Make sure that the GD2 is installed
+ if (strpos($info['GD Version'], '2.') === FALSE)
+ throw new Kohana_Exception('The Image library requires GD2. Please see http://php.net/gd_info for more information.');
+ }
+
+ public function process($image, $actions, $dir, $file, $render = FALSE, $background = NULL)
+ {
+ // Set the "create" function
+ switch ($image['type'])
+ {
+ case IMAGETYPE_JPEG:
+ $create = 'imagecreatefromjpeg';
+ break;
+ case IMAGETYPE_GIF:
+ $create = 'imagecreatefromgif';
+ break;
+ case IMAGETYPE_PNG:
+ $create = 'imagecreatefrompng';
+ break;
+ }
+
+ // Set the "save" function
+ switch (strtolower(substr(strrchr($file, '.'), 1)))
+ {
+ case 'jpg':
+ case 'jpeg':
+ $save = 'imagejpeg';
+ break;
+ case 'gif':
+ $save = 'imagegif';
+ break;
+ case 'png':
+ $save = 'imagepng';
+ break;
+ }
+
+ // Make sure the image type is supported for import
+ if (empty($create) OR ! function_exists($create))
+ throw new Kohana_Exception('The specified image, :type:, is not an allowed image type.', array(':type:' => $image['file']));
+
+ // Make sure the image type is supported for saving
+ if (empty($save) OR ! function_exists($save))
+ throw new Kohana_Exception('The specified image, :type:, is not an allowed image type.', array(':type:' => $dir.$file));
+
+ // Load the image
+ $this->image = $image;
+
+ // Create the GD image resource
+ $this->tmp_image = $create($image['file']);
+
+ // Get the quality setting from the actions
+ $quality = arr::remove('quality', $actions);
+
+ if ($status = $this->execute($actions))
+ {
+ // Prevent the alpha from being lost
+ imagealphablending($this->tmp_image, TRUE);
+ imagesavealpha($this->tmp_image, TRUE);
+
+ switch ($save)
+ {
+ case 'imagejpeg':
+ // Default the quality to 95
+ ($quality === NULL) and $quality = 95;
+ break;
+ case 'imagegif':
+ // Remove the quality setting, GIF doesn't use it
+ unset($quality);
+ break;
+ case 'imagepng':
+ // Always use a compression level of 9 for PNGs. This does not
+ // affect quality, it only increases the level of compression!
+ $quality = 9;
+ break;
+ }
+
+ if ($render === FALSE)
+ {
+ // Set the status to the save return value, saving with the quality requested
+ $status = isset($quality) ? $save($this->tmp_image, $dir.$file, $quality) : $save($this->tmp_image, $dir.$file);
+ }
+ else
+ {
+ // Output the image directly to the browser
+ switch ($save)
+ {
+ case 'imagejpeg':
+ header('Content-Type: image/jpeg');
+ break;
+ case 'imagegif':
+ header('Content-Type: image/gif');
+ break;
+ case 'imagepng':
+ header('Content-Type: image/png');
+ break;
+ }
+
+ $status = isset($quality) ? $save($this->tmp_image, NULL, $quality) : $save($this->tmp_image);
+ }
+
+ // Destroy the temporary image
+ imagedestroy($this->tmp_image);
+ }
+
+ return $status;
+ }
+
+ public function flip($direction)
+ {
+ // Get the current width and height
+ $width = imagesx($this->tmp_image);
+ $height = imagesy($this->tmp_image);
+
+ // Create the flipped image
+ $flipped = $this->imagecreatetransparent($width, $height);
+
+ if ($direction === Image::HORIZONTAL)
+ {
+ for ($x = 0; $x < $width; $x++)
+ {
+ $status = imagecopy($flipped, $this->tmp_image, $x, 0, $width - $x - 1, 0, 1, $height);
+ }
+ }
+ elseif ($direction === Image::VERTICAL)
+ {
+ for ($y = 0; $y < $height; $y++)
+ {
+ $status = imagecopy($flipped, $this->tmp_image, 0, $y, 0, $height - $y - 1, $width, 1);
+ }
+ }
+ else
+ {
+ // Do nothing
+ return TRUE;
+ }
+
+ if ($status === TRUE)
+ {
+ // Swap the new image for the old one
+ imagedestroy($this->tmp_image);
+ $this->tmp_image = $flipped;
+ }
+
+ return $status;
+ }
+
+ public function crop($properties)
+ {
+ // Sanitize the cropping settings
+ $this->sanitize_geometry($properties);
+
+ // Get the current width and height
+ $width = imagesx($this->tmp_image);
+ $height = imagesy($this->tmp_image);
+
+ // Create the temporary image to copy to
+ $img = $this->imagecreatetransparent($properties['width'], $properties['height']);
+
+ // Execute the crop
+ if ($status = imagecopyresampled($img, $this->tmp_image, 0, 0, $properties['left'], $properties['top'], $width, $height, $width, $height))
+ {
+ // Swap the new image for the old one
+ imagedestroy($this->tmp_image);
+ $this->tmp_image = $img;
+ }
+
+ return $status;
+ }
+
+ public function resize($properties)
+ {
+ // Get the current width and height
+ $width = imagesx($this->tmp_image);
+ $height = imagesy($this->tmp_image);
+
+ if (substr($properties['width'], -1) === '%')
+ {
+ // Recalculate the percentage to a pixel size
+ $properties['width'] = round($width * (substr($properties['width'], 0, -1) / 100));
+ }
+
+ if (substr($properties['height'], -1) === '%')
+ {
+ // Recalculate the percentage to a pixel size
+ $properties['height'] = round($height * (substr($properties['height'], 0, -1) / 100));
+ }
+
+ // Recalculate the width and height, if they are missing
+ empty($properties['width']) and $properties['width'] = round($width * $properties['height'] / $height);
+ empty($properties['height']) and $properties['height'] = round($height * $properties['width'] / $width);
+
+ if ($properties['master'] === Image::AUTO)
+ {
+ // Change an automatic master dim to the correct type
+ $properties['master'] = (($width / $properties['width']) > ($height / $properties['height'])) ? Image::WIDTH : Image::HEIGHT;
+ }
+
+ if (empty($properties['height']) OR $properties['master'] === Image::WIDTH)
+ {
+ // Recalculate the height based on the width
+ $properties['height'] = round($height * $properties['width'] / $width);
+ }
+
+ if (empty($properties['width']) OR $properties['master'] === Image::HEIGHT)
+ {
+ // Recalculate the width based on the height
+ $properties['width'] = round($width * $properties['height'] / $height);
+ }
+
+ // Test if we can do a resize without resampling to speed up the final resize
+ if ($properties['width'] > $width / 2 AND $properties['height'] > $height / 2)
+ {
+ // Presize width and height
+ $pre_width = $width;
+ $pre_height = $height;
+
+ // The maximum reduction is 10% greater than the final size
+ $max_reduction_width = round($properties['width'] * 1.1);
+ $max_reduction_height = round($properties['height'] * 1.1);
+
+ // Reduce the size using an O(2n) algorithm, until it reaches the maximum reduction
+ while ($pre_width / 2 > $max_reduction_width AND $pre_height / 2 > $max_reduction_height)
+ {
+ $pre_width /= 2;
+ $pre_height /= 2;
+ }
+
+ // Create the temporary image to copy to
+ $img = $this->imagecreatetransparent($pre_width, $pre_height);
+
+ if ($status = imagecopyresized($img, $this->tmp_image, 0, 0, 0, 0, $pre_width, $pre_height, $width, $height))
+ {
+ // Swap the new image for the old one
+ imagedestroy($this->tmp_image);
+ $this->tmp_image = $img;
+ }
+
+ // Set the width and height to the presize
+ $width = $pre_width;
+ $height = $pre_height;
+ }
+
+ // Create the temporary image to copy to
+ $img = $this->imagecreatetransparent($properties['width'], $properties['height']);
+
+ // Execute the resize
+ if ($status = imagecopyresampled($img, $this->tmp_image, 0, 0, 0, 0, $properties['width'], $properties['height'], $width, $height))
+ {
+ // Swap the new image for the old one
+ imagedestroy($this->tmp_image);
+ $this->tmp_image = $img;
+ }
+
+ return $status;
+ }
+
+ public function rotate($amount)
+ {
+ // Use current image to rotate
+ $img = $this->tmp_image;
+
+ // White, with an alpha of 0
+ $transparent = imagecolorallocatealpha($img, 255, 255, 255, 127);
+
+ // Rotate, setting the transparent color
+ $img = imagerotate($img, 360 - $amount, $transparent, -1);
+
+ // Fill the background with the transparent "color"
+ imagecolortransparent($img, $transparent);
+
+ // Merge the images
+ if ($status = imagecopymerge($this->tmp_image, $img, 0, 0, 0, 0, imagesx($this->tmp_image), imagesy($this->tmp_image), 100))
+ {
+ // Prevent the alpha from being lost
+ imagealphablending($img, TRUE);
+ imagesavealpha($img, TRUE);
+
+ // Swap the new image for the old one
+ imagedestroy($this->tmp_image);
+ $this->tmp_image = $img;
+ }
+
+ return $status;
+ }
+
+ public function sharpen($amount)
+ {
+ // Make sure that the sharpening function is available
+ if ( ! function_exists('imageconvolution'))
+ throw new Kohana_Exception('Your configured driver does not support the :method: image transformation.', array(':method:' => __FUNCTION__));
+
+ // Amount should be in the range of 18-10
+ $amount = round(abs(-18 + ($amount * 0.08)), 2);
+
+ // Gaussian blur matrix
+ $matrix = array
+ (
+ array(-1, -1, -1),
+ array(-1, $amount, -1),
+ array(-1, -1, -1),
+ );
+
+ // Perform the sharpen
+ return imageconvolution($this->tmp_image, $matrix, $amount - 8, 0);
+ }
+
+ public function composite($properties)
+ {
+ switch($properties['mime'])
+ {
+ case "image/jpeg":
+ $overlay_img = imagecreatefromjpeg($properties['overlay_file']);
+ break;
+
+ case "image/gif":
+ $overlay_img = imagecreatefromgif($properties['overlay_file']);
+ break;
+
+ case "image/png":
+ $overlay_img = imagecreatefrompng($properties['overlay_file']);
+ break;
+ }
+
+ $this->imagecopymerge_alpha($this->tmp_image, $overlay_img, $properties['x'], $properties['y'], 0, 0, imagesx($overlay_img), imagesy($overlay_img), $properties['transparency']);
+
+ imagedestroy($overlay_img);
+
+ return TRUE;
+ }
+
+ /**
+ * A replacement for php's imagecopymerge() function that supports the alpha channel
+ * See php bug #23815: http://bugs.php.net/bug.php?id=23815
+ *
+ * @param resource $dst_im Destination image link resource
+ * @param resource $src_im Source image link resource
+ * @param integer $dst_x x-coordinate of destination point
+ * @param integer $dst_y y-coordinate of destination point
+ * @param integer $src_x x-coordinate of source point
+ * @param integer $src_y y-coordinate of source point
+ * @param integer $src_w Source width
+ * @param integer $src_h Source height
+ * @param integer $pct Transparency percent (0 to 100)
+ */
+ protected function imagecopymerge_alpha($dst_im, $src_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $pct)
+ {
+ // Create a new blank image the site of our source image
+ $cut = imagecreatetruecolor($src_w, $src_h);
+
+ // Copy the blank image into the destination image where the source goes
+ imagecopy($cut, $dst_im, 0, 0, $dst_x, $dst_y, $src_w, $src_h);
+
+ // Place the source image in the destination image
+ imagecopy($cut, $src_im, 0, 0, $src_x, $src_y, $src_w, $src_h);
+ imagecopymerge($dst_im, $cut, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $pct);
+ }
+
+ protected function properties()
+ {
+ return array(imagesx($this->tmp_image), imagesy($this->tmp_image));
+ }
+
+ /**
+ * Returns an image with a transparent background. Used for rotating to
+ * prevent unfilled backgrounds.
+ *
+ * @param integer image width
+ * @param integer image height
+ * @return resource
+ */
+ protected function imagecreatetransparent($width, $height)
+ {
+ if ($width < 1)
+ {
+ $width = 1;
+ }
+
+ if ($height < 1)
+ {
+ $height = 1;
+ }
+
+ if (self::$blank_png === NULL)
+ {
+ // Decode the blank PNG if it has not been done already
+ self::$blank_png = imagecreatefromstring(base64_decode
+ (
+ 'iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29'.
+ 'mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAADqSURBVHjaYvz//z/DYAYAAcTEMMgBQAANegcCBN'.
+ 'CgdyBAAA16BwIE0KB3IEAADXoHAgTQoHcgQAANegcCBNCgdyBAAA16BwIE0KB3IEAADXoHAgTQoHcgQ'.
+ 'AANegcCBNCgdyBAAA16BwIE0KB3IEAADXoHAgTQoHcgQAANegcCBNCgdyBAAA16BwIE0KB3IEAADXoH'.
+ 'AgTQoHcgQAANegcCBNCgdyBAAA16BwIE0KB3IEAADXoHAgTQoHcgQAANegcCBNCgdyBAAA16BwIE0KB'.
+ '3IEAADXoHAgTQoHcgQAANegcCBNCgdyBAgAEAMpcDTTQWJVEAAAAASUVORK5CYII='
+ ));
+
+ // Set the blank PNG width and height
+ self::$blank_png_width = imagesx(self::$blank_png);
+ self::$blank_png_height = imagesy(self::$blank_png);
+ }
+
+ $img = imagecreatetruecolor($width, $height);
+
+ // Resize the blank image
+ imagecopyresized($img, self::$blank_png, 0, 0, 0, 0, $width, $height, self::$blank_png_width, self::$blank_png_height);
+
+ // Prevent the alpha from being lost
+ imagealphablending($img, FALSE);
+ imagesavealpha($img, TRUE);
+
+ return $img;
+ }
+
+} // End Image GD Driver \ No newline at end of file
diff --git a/system/libraries/drivers/Image/GraphicsMagick.php b/system/libraries/drivers/Image/GraphicsMagick.php
new file mode 100644
index 0000000..89b40b4
--- /dev/null
+++ b/system/libraries/drivers/Image/GraphicsMagick.php
@@ -0,0 +1,225 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * GraphicsMagick Image Driver.
+ *
+ * @package Image
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Image_GraphicsMagick_Driver extends Image_Driver {
+
+ // Directory that GM is installed in
+ protected $dir = '';
+
+ // Command extension (exe for windows)
+ protected $ext = '';
+
+ // Temporary image filename
+ protected $tmp_image;
+
+ /**
+ * Attempts to detect the GraphicsMagick installation directory.
+ *
+ * @throws Kohana_Exception
+ * @param array configuration
+ * @return void
+ */
+ public function __construct($config)
+ {
+ if (empty($config['directory']))
+ {
+ // Attempt to locate GM by using "which" (only works for *nix!)
+ if ( ! is_file($path = exec('which gm')))
+ throw new Kohana_Exception('The GraphicsMagick directory specified does not contain a required program.');
+
+ $config['directory'] = dirname($path);
+ }
+
+ // Set the command extension
+ $this->ext = (PHP_SHLIB_SUFFIX === 'dll') ? '.exe' : '';
+
+ // Check to make sure the provided path is correct
+ if ( ! is_file(realpath($config['directory']).'/gm'.$this->ext))
+ throw new Kohana_Exception('The GraphicsMagick directory specified does not contain a required program, :gm:.', array(':gm:' => 'gm'.$this->ext));
+
+
+ // Set the installation directory
+ $this->dir = str_replace('\\', '/', realpath($config['directory'])).'/';
+ }
+
+ /**
+ * Creates a temporary image and executes the given actions. By creating a
+ * temporary copy of the image before manipulating it, this process is atomic.
+ */
+ public function process($image, $actions, $dir, $file, $render = FALSE, $background = NULL)
+ {
+ // Need to implement $background support
+ if ($background !== NULL)
+ throw new Kohana_Exception('The GraphicsMagick driver does not support setting a background color');
+
+ // We only need the filename
+ $image = $image['file'];
+
+ // Unique temporary filename
+ $this->tmp_image = $dir.'k2img--'.sha1(time().$dir.$file).substr($file, strrpos($file, '.'));
+
+ // Copy the image to the temporary file
+ copy($image, $this->tmp_image);
+
+ // Quality change is done last
+ $quality = (int) arr::remove('quality', $actions);
+
+ // Use 95 for the default quality
+ empty($quality) and $quality = 95;
+
+ // All calls to these will need to be escaped, so do it now
+ $this->cmd_image = escapeshellarg($this->tmp_image);
+ $this->new_image = ($render)? $this->cmd_image : escapeshellarg($dir.$file);
+
+ if ($status = $this->execute($actions))
+ {
+ // Use convert to change the image into its final version. This is
+ // done to allow the file type to change correctly, and to handle
+ // the quality conversion in the most effective way possible.
+ if ($error = exec(escapeshellcmd($this->dir.'gm'.$this->ext.' convert').' -quality '.$quality.'% '.$this->cmd_image.' '.$this->new_image))
+ {
+ $this->errors[] = $error;
+ }
+ else
+ {
+ // Output the image directly to the browser
+ if ($render !== FALSE)
+ {
+ $contents = file_get_contents($this->tmp_image);
+ switch (substr($file, strrpos($file, '.') + 1))
+ {
+ case 'jpg':
+ case 'jpeg':
+ header('Content-Type: image/jpeg');
+ break;
+ case 'gif':
+ header('Content-Type: image/gif');
+ break;
+ case 'png':
+ header('Content-Type: image/png');
+ break;
+ }
+ echo $contents;
+ }
+ }
+ }
+
+ // Remove the temporary image
+ unlink($this->tmp_image);
+ $this->tmp_image = '';
+
+ return $status;
+ }
+
+ public function crop($prop)
+ {
+ // Sanitize and normalize the properties into geometry
+ $this->sanitize_geometry($prop);
+
+ // Set the IM geometry based on the properties
+ $geometry = escapeshellarg($prop['width'].'x'.$prop['height'].'+'.$prop['left'].'+'.$prop['top']);
+
+ if ($error = exec(escapeshellcmd($this->dir.'gm'.$this->ext.' convert').' -crop '.$geometry.' '.$this->cmd_image.' '.$this->cmd_image))
+ {
+ $this->errors[] = $error;
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ public function flip($dir)
+ {
+ // Convert the direction into a GM command
+ $dir = ($dir === Image::HORIZONTAL) ? '-flop' : '-flip';
+
+ if ($error = exec(escapeshellcmd($this->dir.'gm'.$this->ext.' convert').' '.$dir.' '.$this->cmd_image.' '.$this->cmd_image))
+ {
+ $this->errors[] = $error;
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ public function resize($prop)
+ {
+ switch ($prop['master'])
+ {
+ case Image::WIDTH: // Wx
+ $dim = escapeshellarg($prop['width'].'x');
+ break;
+ case Image::HEIGHT: // xH
+ $dim = escapeshellarg('x'.$prop['height']);
+ break;
+ case Image::AUTO: // WxH
+ $dim = escapeshellarg($prop['width'].'x'.$prop['height']);
+ break;
+ case Image::NONE: // WxH!
+ $dim = escapeshellarg($prop['width'].'x'.$prop['height'].'!');
+ break;
+ }
+
+ // Use "convert" to change the width and height
+ if ($error = exec(escapeshellcmd($this->dir.'gm'.$this->ext.' convert').' -resize '.$dim.' '.$this->cmd_image.' '.$this->cmd_image))
+ {
+ $this->errors[] = $error;
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ public function rotate($amt)
+ {
+ if ($error = exec(escapeshellcmd($this->dir.'gm'.$this->ext.' convert').' -rotate '.escapeshellarg($amt).' -background transparent '.$this->cmd_image.' '.$this->cmd_image))
+ {
+ $this->errors[] = $error;
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ public function sharpen($amount)
+ {
+ // Set the sigma, radius, and amount. The amount formula allows a nice
+ // spread between 1 and 100 without pixelizing the image badly.
+ $sigma = 0.5;
+ $radius = $sigma * 2;
+ $amount = round(($amount / 80) * 3.14, 2);
+
+ // Convert the amount to an GM command
+ $sharpen = escapeshellarg($radius.'x'.$sigma.'+'.$amount.'+0');
+
+ if ($error = exec(escapeshellcmd($this->dir.'gm'.$this->ext.' convert').' -unsharp '.$sharpen.' '.$this->cmd_image.' '.$this->cmd_image))
+ {
+ $this->errors[] = $error;
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ public function composite($properties)
+ {
+ if ($error = exec(escapeshellcmd($this->dir.'gm'.$this->ext.' composite').' -geometry ' . escapeshellarg('+'.$properties['x'].'+'.$properties['y']).' -dissolve '.escapeshellarg($properties['transparency']).' '.escapeshellarg($properties['overlay_file']).' '.$this->cmd_image.' '.$this->cmd_image))
+ {
+ $this->errors[] = $error;
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ protected function properties()
+ {
+ return array_slice(getimagesize($this->tmp_image), 0, 2, FALSE);
+ }
+
+} // End Image GraphicsMagick Driver \ No newline at end of file
diff --git a/system/libraries/drivers/Image/ImageMagick.php b/system/libraries/drivers/Image/ImageMagick.php
new file mode 100644
index 0000000..55c0ba2
--- /dev/null
+++ b/system/libraries/drivers/Image/ImageMagick.php
@@ -0,0 +1,233 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * ImageMagick Image Driver.
+ *
+ * $Id: ImageMagick.php 4679 2009-11-10 01:45:52Z isaiah $
+ *
+ * @package Image
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Image_ImageMagick_Driver extends Image_Driver {
+
+ // Directory that IM is installed in
+ protected $dir = '';
+
+ // Command extension (exe for windows)
+ protected $ext = '';
+
+ // Temporary image filename
+ protected $tmp_image;
+
+ /**
+ * Attempts to detect the ImageMagick installation directory.
+ *
+ * @throws Kohana_Exception
+ * @param array configuration
+ * @return void
+ */
+ public function __construct($config)
+ {
+ if (empty($config['directory']))
+ {
+ // Attempt to locate IM by using "which" (only works for *nix!)
+ if ( ! is_file($path = exec('which convert')))
+ throw new Kohana_Exception('The ImageMagick directory specified does not contain a required program.');
+
+ $config['directory'] = dirname($path);
+ }
+
+ // Set the command extension
+ $this->ext = (PHP_SHLIB_SUFFIX === 'dll') ? '.exe' : '';
+
+ // Check to make sure the provided path is correct
+ if ( ! is_file(realpath($config['directory']).'/convert'.$this->ext))
+ throw new Kohana_Exception('The ImageMagick directory specified does not contain a required program, :im:', array(':im:' => 'convert'.$this->ext));
+
+ // Set the installation directory
+ $this->dir = str_replace('\\', '/', realpath($config['directory'])).'/';
+ }
+
+ /**
+ * Creates a temporary image and executes the given actions. By creating a
+ * temporary copy of the image before manipulating it, this process is atomic.
+ */
+ public function process($image, $actions, $dir, $file, $render = FALSE, $background = NULL)
+ {
+ // We only need the filename
+ $image = $image['file'];
+
+ // Unique temporary filename
+ $this->tmp_image = $dir.'k2img--'.sha1(time().$dir.$file).substr($file, strrpos($file, '.'));
+
+ // Copy the image to the temporary file
+ copy($image, $this->tmp_image);
+
+ // Quality change is done last
+ $quality = (int) arr::remove('quality', $actions);
+
+ // Use 95 for the default quality
+ empty($quality) and $quality = 95;
+
+ if (is_string($background))
+ {
+ // Set the background color
+ $this->background = escapeshellarg($background);
+ }
+ else
+ {
+ // Use a transparent background
+ $this->background = 'transparent';
+ }
+
+ // All calls to these will need to be escaped, so do it now
+ $this->cmd_image = escapeshellarg($this->tmp_image);
+ $this->new_image = $render ? $this->cmd_image : escapeshellarg($dir.$file);
+
+ if ($status = $this->execute($actions))
+ {
+ // Use convert to change the image into its final version. This is
+ // done to allow the file type to change correctly, and to handle
+ // the quality conversion in the most effective way possible.
+ if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -background '.$this->background.' -flatten -quality '.$quality.'% '.$this->cmd_image.' '.$this->new_image))
+ {
+ $this->errors[] = $error;
+ }
+ else
+ {
+ // Output the image directly to the browser
+ if ($render === TRUE)
+ {
+ $contents = file_get_contents($this->tmp_image);
+ switch (substr($file, strrpos($file, '.') + 1))
+ {
+ case 'jpg':
+ case 'jpeg':
+ header('Content-Type: image/jpeg');
+ break;
+ case 'gif':
+ header('Content-Type: image/gif');
+ break;
+ case 'png':
+ header('Content-Type: image/png');
+ break;
+ }
+ echo $contents;
+ }
+ }
+ }
+
+ // Remove the temporary image
+ unlink($this->tmp_image);
+ $this->tmp_image = '';
+
+ return $status;
+ }
+
+ public function crop($prop)
+ {
+ // Sanitize and normalize the properties into geometry
+ $this->sanitize_geometry($prop);
+
+ // Set the IM geometry based on the properties
+ $geometry = escapeshellarg($prop['width'].'x'.$prop['height'].'+'.$prop['left'].'+'.$prop['top']);
+
+ if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -background '.$this->background.' -flatten -crop '.$geometry.'! '.$this->cmd_image.' '.$this->cmd_image))
+ {
+ $this->errors[] = $error;
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ public function flip($dir)
+ {
+ // Convert the direction into a IM command
+ $dir = ($dir === Image::HORIZONTAL) ? '-flop' : '-flip';
+
+ if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -background '.$this->background.' -flatten '.$dir.' '.$this->cmd_image.' '.$this->cmd_image))
+ {
+ $this->errors[] = $error;
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ public function resize($prop)
+ {
+ switch ($prop['master'])
+ {
+ case Image::WIDTH: // Wx
+ $dim = escapeshellarg($prop['width'].'x');
+ break;
+ case Image::HEIGHT: // xH
+ $dim = escapeshellarg('x'.$prop['height']);
+ break;
+ case Image::AUTO: // WxH
+ $dim = escapeshellarg($prop['width'].'x'.$prop['height']);
+ break;
+ case Image::NONE: // WxH!
+ $dim = escapeshellarg($prop['width'].'x'.$prop['height'].'!');
+ break;
+ }
+
+ // Use "convert" to change the width and height
+ if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -background '.$this->background.' -flatten -resize '.$dim.' '.$this->cmd_image.' '.$this->cmd_image))
+ {
+ $this->errors[] = $error;
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ public function rotate($amt)
+ {
+ if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -background '.$this->background.' -flatten -rotate '.escapeshellarg($amt).' -background transparent '.$this->cmd_image.' '.$this->cmd_image))
+ {
+ $this->errors[] = $error;
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ public function sharpen($amount)
+ {
+ // Set the sigma, radius, and amount. The amount formula allows a nice
+ // spread between 1 and 100 without pixelizing the image badly.
+ $sigma = 0.5;
+ $radius = $sigma * 2;
+ $amount = round(($amount / 80) * 3.14, 2);
+
+ // Convert the amount to an IM command
+ $sharpen = escapeshellarg($radius.'x'.$sigma.'+'.$amount.'+0');
+
+ if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -background '.$this->background.' -flatten -unsharp '.$sharpen.' '.$this->cmd_image.' '.$this->cmd_image))
+ {
+ $this->errors[] = $error;
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ public function composite($properties)
+ {
+ if ($error = exec(escapeshellcmd($this->dir.'composite'.$this->ext).' -geometry ' . escapeshellarg('+'.$properties['x'].'+'.$properties['y']).' -dissolve '.escapeshellarg($properties['transparency']).' '.escapeshellarg($properties['overlay_file']).' '.$this->cmd_image.' '.$this->cmd_image))
+ {
+ $this->errors[] = $error;
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ protected function properties()
+ {
+ return array_slice(getimagesize($this->tmp_image), 0, 2, FALSE);
+ }
+
+} // End Image ImageMagick Driver \ No newline at end of file
diff --git a/system/libraries/drivers/Log.php b/system/libraries/drivers/Log.php
new file mode 100644
index 0000000..cd6dba7
--- /dev/null
+++ b/system/libraries/drivers/Log.php
@@ -0,0 +1,22 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Log API driver.
+ *
+ * $Id: Log.php 4679 2009-11-10 01:45:52Z isaiah $
+ *
+ * @package Kohana_Log
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+abstract class Log_Driver {
+
+ protected $config = array();
+
+ public function __construct(array $config)
+ {
+ $this->config = $config;
+ }
+
+ abstract public function save(array $messages);
+} \ No newline at end of file
diff --git a/system/libraries/drivers/Log/Database.php b/system/libraries/drivers/Log/Database.php
new file mode 100644
index 0000000..19db974
--- /dev/null
+++ b/system/libraries/drivers/Log/Database.php
@@ -0,0 +1,40 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Log API driver.
+ *
+ * $Id: Database.php 4679 2009-11-10 01:45:52Z isaiah $
+ *
+ * @package Kohana_Log
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Log_Database_Driver extends Log_Driver {
+
+ public function save(array $messages)
+ {
+ $insert = db::build($this->config['group'])
+ ->insert($this->config['table'])
+ ->columns(array('date', 'level', 'message'));
+
+ $run_insert = FALSE;
+
+ foreach ($messages AS $message)
+ {
+ if ($this->config['log_levels'][$message['type']] <= $this->config['log_threshold'])
+ {
+ // Add new message to database
+ $insert->values($message);
+
+ // There is data to insert
+ $run_insert = TRUE;
+ }
+ }
+
+ // Update the database
+ if ($run_insert)
+ {
+ $insert->execute();
+ }
+ }
+} \ No newline at end of file
diff --git a/system/libraries/drivers/Log/File.php b/system/libraries/drivers/Log/File.php
new file mode 100644
index 0000000..6ad565b
--- /dev/null
+++ b/system/libraries/drivers/Log/File.php
@@ -0,0 +1,44 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Log API driver.
+ *
+ * $Id: File.php 4679 2009-11-10 01:45:52Z isaiah $
+ *
+ * @package Kohana_Log
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Log_File_Driver extends Log_Driver {
+
+ public function save(array $messages)
+ {
+ // Filename of the log
+ $filename = $this->config['log_directory'].'/'.date('Y-m-d').'.log'.EXT;
+
+ if ( ! is_file($filename))
+ {
+ // Write the SYSPATH checking header
+ file_put_contents($filename,
+ '<?php defined(\'SYSPATH\') or die(\'No direct script access.\'); ?>'.PHP_EOL.PHP_EOL);
+
+ // Prevent external writes
+ chmod($filename, $this->config['posix_permissions']);
+ }
+
+ foreach ($messages AS $message)
+ {
+ if ($this->config['log_levels'][$message['type']] <= $this->config['log_threshold'])
+ {
+ // Add a new message line
+ $messages_to_write[] = date($this->config['date_format'], $message['date']).' --- '.$message['type'].': '.$message['message'];
+ }
+ }
+
+ if ( ! empty($messages_to_write))
+ {
+ // Write messages to log file
+ file_put_contents($filename, implode(PHP_EOL, $messages_to_write).PHP_EOL, FILE_APPEND);
+ }
+ }
+} \ No newline at end of file
diff --git a/system/libraries/drivers/Log/Syslog.php b/system/libraries/drivers/Log/Syslog.php
new file mode 100644
index 0000000..5da5d25
--- /dev/null
+++ b/system/libraries/drivers/Log/Syslog.php
@@ -0,0 +1,34 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Log API driver.
+ *
+ * @package Kohana_Log
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Log_Syslog_Driver extends Log_Driver {
+
+ protected $syslog_levels = array('error' => LOG_ERR,
+ 'alert' => LOG_WARNING,
+ 'info' => LOG_INFO,
+ 'debug' => LOG_DEBUG);
+
+ public function save(array $messages)
+ {
+ // Open the connection to syslog
+ openlog($this->config['ident'], LOG_CONS, LOG_USER);
+
+ do
+ {
+ // Load the next message
+ list ($date, $type, $text) = array_shift($messages);
+
+ syslog($this->syslog_levels[$type], $text);
+ }
+ while ( ! empty($messages));
+
+ // Close connection to syslog
+ closelog();
+ }
+} \ No newline at end of file
diff --git a/system/libraries/drivers/Session.php b/system/libraries/drivers/Session.php
new file mode 100644
index 0000000..e591b91
--- /dev/null
+++ b/system/libraries/drivers/Session.php
@@ -0,0 +1,70 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Session driver interface
+ *
+ * $Id: Session.php 4729 2009-12-29 20:35:19Z isaiah $
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+interface Session_Driver {
+
+ /**
+ * Opens a session.
+ *
+ * @param string save path
+ * @param string session name
+ * @return boolean
+ */
+ public function open($path, $name);
+
+ /**
+ * Closes a session.
+ *
+ * @return boolean
+ */
+ public function close();
+
+ /**
+ * Reads a session.
+ *
+ * @param string session id
+ * @return string
+ */
+ public function read($id);
+
+ /**
+ * Writes a session.
+ *
+ * @param string session id
+ * @param string session data
+ * @return boolean
+ */
+ public function write($id, $data);
+
+ /**
+ * Destroys a session.
+ *
+ * @param string session id
+ * @return boolean
+ */
+ public function destroy($id);
+
+ /**
+ * Regenerates the session id.
+ *
+ * @return string
+ */
+ public function regenerate();
+
+ /**
+ * Garbage collection.
+ *
+ * @param integer session expiration period
+ * @return boolean
+ */
+ public function gc($maxlifetime);
+
+} // End Session Driver Interface \ No newline at end of file
diff --git a/system/libraries/drivers/Session/Cache.php b/system/libraries/drivers/Session/Cache.php
new file mode 100644
index 0000000..459f8b0
--- /dev/null
+++ b/system/libraries/drivers/Session/Cache.php
@@ -0,0 +1,108 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Session cache driver.
+ *
+ * Cache library config goes in the session.storage config entry:
+ * $config['storage'] = array(
+ * 'driver' => 'apc',
+ * 'requests' => 10000
+ * );
+ * Lifetime does not need to be set as it is
+ * overridden by the session expiration setting.
+ *
+ * $Id: Cache.php 4729 2009-12-29 20:35:19Z isaiah $
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Session_Cache_Driver implements Session_Driver {
+
+ protected $cache;
+ protected $encrypt;
+
+ public function __construct()
+ {
+ // Load Encrypt library
+ if (Kohana::config('session.encryption'))
+ {
+ $this->encrypt = new Encrypt;
+ }
+
+ Kohana_Log::add('debug', 'Session Cache Driver Initialized');
+ }
+
+ public function open($path, $name)
+ {
+ $config = Kohana::config('session.storage');
+
+ if (empty($config))
+ {
+ // Load the default group
+ $config = Kohana::config('cache.default');
+ }
+ elseif (is_string($config))
+ {
+ $name = $config;
+
+ // Test the config group name
+ if (($config = Kohana::config('cache.'.$config)) === NULL)
+ throw new Kohana_Exception('The :group: group is not defined in your configuration.', array(':group:' => $name));
+ }
+
+ $config['lifetime'] = (Kohana::config('session.expiration') == 0) ? 86400 : Kohana::config('session.expiration');
+ $this->cache = new Cache($config);
+
+ return is_object($this->cache);
+ }
+
+ public function close()
+ {
+ return TRUE;
+ }
+
+ public function read($id)
+ {
+ $id = 'session_'.$id;
+ if ($data = $this->cache->get($id))
+ {
+ return Kohana::config('session.encryption') ? $this->encrypt->decode($data) : $data;
+ }
+
+ // Return value must be string, NOT a boolean
+ return '';
+ }
+
+ public function write($id, $data)
+ {
+ if ( ! Session::$should_save)
+ return TRUE;
+
+ $id = 'session_'.$id;
+ $data = Kohana::config('session.encryption') ? $this->encrypt->encode($data) : $data;
+
+ return $this->cache->set($id, $data);
+ }
+
+ public function destroy($id)
+ {
+ $id = 'session_'.$id;
+ return $this->cache->delete($id);
+ }
+
+ public function regenerate()
+ {
+ session_regenerate_id(TRUE);
+
+ // Return new session id
+ return session_id();
+ }
+
+ public function gc($maxlifetime)
+ {
+ // Just return, caches are automatically cleaned up
+ return TRUE;
+ }
+
+} // End Session Cache Driver
diff --git a/system/libraries/drivers/Session/Cookie.php b/system/libraries/drivers/Session/Cookie.php
new file mode 100644
index 0000000..88f5e21
--- /dev/null
+++ b/system/libraries/drivers/Session/Cookie.php
@@ -0,0 +1,83 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Session cookie driver.
+ *
+ * $Id: Cookie.php 4729 2009-12-29 20:35:19Z isaiah $
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Session_Cookie_Driver implements Session_Driver {
+
+ protected $cookie_name;
+ protected $encrypt; // Library
+
+ public function __construct()
+ {
+ $this->cookie_name = Kohana::config('session.name').'_data';
+
+ if (Kohana::config('session.encryption'))
+ {
+ $this->encrypt = Encrypt::instance();
+ }
+
+ Kohana_Log::add('debug', 'Session Cookie Driver Initialized');
+ }
+
+ public function open($path, $name)
+ {
+ return TRUE;
+ }
+
+ public function close()
+ {
+ return TRUE;
+ }
+
+ public function read($id)
+ {
+ $data = (string) cookie::get($this->cookie_name);
+
+ if ($data == '')
+ return $data;
+
+ return empty($this->encrypt) ? base64_decode($data) : $this->encrypt->decode($data);
+ }
+
+ public function write($id, $data)
+ {
+ if ( ! Session::$should_save)
+ return TRUE;
+
+ $data = empty($this->encrypt) ? base64_encode($data) : $this->encrypt->encode($data);
+
+ if (strlen($data) > 4048)
+ {
+ Kohana_Log::add('error', 'Session ('.$id.') data exceeds the 4KB limit, ignoring write.');
+ return FALSE;
+ }
+
+ return cookie::set($this->cookie_name, $data, Kohana::config('session.expiration'));
+ }
+
+ public function destroy($id)
+ {
+ return cookie::delete($this->cookie_name);
+ }
+
+ public function regenerate()
+ {
+ session_regenerate_id(TRUE);
+
+ // Return new id
+ return session_id();
+ }
+
+ public function gc($maxlifetime)
+ {
+ return TRUE;
+ }
+
+} // End Session Cookie Driver Class \ No newline at end of file
diff --git a/system/libraries/drivers/Session/Database.php b/system/libraries/drivers/Session/Database.php
new file mode 100644
index 0000000..7b372d2
--- /dev/null
+++ b/system/libraries/drivers/Session/Database.php
@@ -0,0 +1,178 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Session database driver.
+ *
+ * $Id: Database.php 4729 2009-12-29 20:35:19Z isaiah $
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Session_Database_Driver implements Session_Driver {
+
+ /*
+ CREATE TABLE sessions
+ (
+ session_id VARCHAR(127) NOT NULL,
+ last_activity INT(10) UNSIGNED NOT NULL,
+ data TEXT NOT NULL,
+ PRIMARY KEY (session_id)
+ );
+ */
+
+ // Database settings
+ protected $db = 'default';
+ protected $table = 'sessions';
+
+ // Encryption
+ protected $encrypt;
+
+ // Session settings
+ protected $session_id;
+ protected $written = FALSE;
+
+ public function __construct()
+ {
+ // Load configuration
+ $config = Kohana::config('session');
+
+ if ( ! empty($config['encryption']))
+ {
+ // Load encryption
+ $this->encrypt = Encrypt::instance();
+ }
+
+ if (is_array($config['storage']))
+ {
+ if ( ! empty($config['storage']['group']))
+ {
+ // Set the group name
+ $this->db = $config['storage']['group'];
+ }
+
+ if ( ! empty($config['storage']['table']))
+ {
+ // Set the table name
+ $this->table = $config['storage']['table'];
+ }
+ }
+
+ Kohana_Log::add('debug', 'Session Database Driver Initialized');
+ }
+
+ public function open($path, $name)
+ {
+ return TRUE;
+ }
+
+ public function close()
+ {
+ return TRUE;
+ }
+
+ public function read($id)
+ {
+ // Load the session
+ $query = db::select('data')
+ ->from($this->table)
+ ->where('session_id', '=', $id)
+ ->limit(1)
+ ->execute($this->db);
+
+ if ($query->count() === 0)
+ {
+ // No current session
+ $this->session_id = NULL;
+
+ return '';
+ }
+
+ // Set the current session id
+ $this->session_id = $id;
+
+ // Load the data
+ $data = $query->current()->data;
+
+ return ($this->encrypt === NULL) ? base64_decode($data) : $this->encrypt->decode($data);
+ }
+
+ public function write($id, $data)
+ {
+ if ( ! Session::$should_save)
+ return TRUE;
+
+ $data = array
+ (
+ 'session_id' => $id,
+ 'last_activity' => time(),
+ 'data' => ($this->encrypt === NULL) ? base64_encode($data) : $this->encrypt->encode($data)
+ );
+
+ if ($this->session_id === NULL)
+ {
+ // Insert a new session
+ $query = db::insert($this->table, $data)
+ ->execute($this->db);
+ }
+ elseif ($id === $this->session_id)
+ {
+ // Do not update the session_id
+ unset($data['session_id']);
+
+ // Update the existing session
+ $query = db::update($this->table)
+ ->set($data)
+ ->where('session_id', '=', $id)
+ ->execute($this->db);
+ }
+ else
+ {
+ // Update the session and id
+ $query = db::update($this->table)
+ ->set($data)
+ ->where('session_id', '=', $this->session_id)
+ ->execute($this->db);
+
+ // Set the new session id
+ $this->session_id = $id;
+ }
+
+ return (bool) $query->count();
+ }
+
+ public function destroy($id)
+ {
+ // Delete the requested session
+ db::delete($this->table)
+ ->where('session_id', '=', $id)
+ ->execute($this->db);
+
+ // Session id is no longer valid
+ $this->session_id = NULL;
+
+ return TRUE;
+ }
+
+ public function regenerate()
+ {
+ // Generate a new session id
+ session_regenerate_id();
+
+ // Return new session id
+ return session_id();
+ }
+
+ public function gc($maxlifetime)
+ {
+ // Delete all expired sessions
+ $query = db::delete($this->table)
+ ->where('last_activity', '<', time() - $maxlifetime)
+ ->execute($this->db);
+
+ Kohana_Log::add('debug', 'Session garbage collected: '.$query->count().' row(s) deleted.');
+
+ return TRUE;
+ }
+
+} // End Session Database Driver
diff --git a/system/messages/kohana/core.php b/system/messages/kohana/core.php
new file mode 100644
index 0000000..0fa66b0
--- /dev/null
+++ b/system/messages/kohana/core.php
@@ -0,0 +1,37 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Core Kohana messages
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+$messages = array
+(
+ 'errors' => array
+ (
+ E_KOHANA => 'Framework Error',
+ E_PAGE_NOT_FOUND => 'Page Not Found',
+ E_DATABASE_ERROR => 'Database Error',
+ E_RECOVERABLE_ERROR => 'Recoverable Error',
+ E_ERROR => 'Fatal Error',
+ E_COMPILE_ERROR => 'Fatal Error',
+ E_CORE_ERROR => 'Fatal Error',
+ E_USER_ERROR => 'Fatal Error',
+ E_PARSE => 'Syntax Error',
+ E_WARNING => 'Warning Message',
+ E_COMPILE_WARNING => 'Warning Message',
+ E_CORE_WARNING => 'Warning Message',
+ E_USER_WARNING => 'Warning Message',
+ E_STRICT => 'Strict Mode Error',
+ E_NOTICE => 'Runtime Message',
+ E_USER_NOTICE => 'Runtime Message',
+ ),
+);
+
+// E_DEPRECATED is only defined in PHP >= 5.3.0
+if (defined('E_DEPRECATED'))
+{
+ $messages['errors'][E_DEPRECATED] = 'Deprecated';
+} \ No newline at end of file
diff --git a/system/messages/validation/default.php b/system/messages/validation/default.php
new file mode 100644
index 0000000..88580a6
--- /dev/null
+++ b/system/messages/validation/default.php
@@ -0,0 +1,25 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Default validation messages
+ *
+ * @package Validation
+ * @author Kohana Team
+ * @copyright (c) 2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+
+$messages = array(
+ 'required' => 'The :field field is required',
+ 'length' => 'The :field field must be between :param1 and :param2 characters long',
+ 'depends_on' => 'The :field field requires the :param1 field',
+ 'matches' => 'The :field field must be the same as :param1',
+ 'email' => 'The :field field must be a valid email address',
+ 'decimal' => 'The :field field must be a decimal with :param1 places',
+ 'digit' => 'The :field field must be a digit',
+ 'in_array' => 'The :field field must be one of the available options',
+ 'alpha_numeric' => 'The :field field must consist only of alphabetical or numeric characters',
+ 'alpha_dash ' => 'The :field field must consist only of alphabetical, numeric, underscore and dash characters',
+ 'numeric ' => 'The :field field must be a valid number',
+ 'url' => 'The :field field must be a valid url',
+ 'phone' => 'The :field field must be a valid phone number',
+);
diff --git a/system/views/kohana/error.php b/system/views/kohana/error.php
new file mode 100644
index 0000000..aa6770c
--- /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>
+ &raquo;
+ <?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 0000000..1024eb1
--- /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
new file mode 100644
index 0000000..84ddbff
--- /dev/null
+++ b/system/views/kohana/template.php
@@ -0,0 +1,36 @@
+<?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 html::chars(__($title)) ?></title>
+
+ <style type="text/css">
+ html { background: #83c018 url(<?php echo url::base(FALSE) ?>kohana.png) 50% 0 no-repeat; }
+ body { width: 52em; margin: 200px auto 2em; font-size: 76%; font-family: Arial, sans-serif; color: #273907; line-height: 1.5; text-align: center; }
+ h1 { font-size: 3em; font-weight: normal; text-transform: uppercase; color: #fff; }
+ a { color: inherit; }
+ code { font-size: 1.3em; }
+ ul { list-style: none; padding: 2em 0; }
+ ul li { display: inline; padding-right: 1em; text-transform: uppercase; }
+ ul li a { padding: 0.5em 1em; background: #69ad0f; border: 1px solid #569f09; color: #fff; text-decoration: none; }
+ ul li a:hover { background: #569f09; }
+ .box { padding: 2em; background: #98cc2b; border: 1px solid #569f09; }
+ .copyright { font-size: 0.9em; text-transform: uppercase; color: #557d10; }
+ </style>
+
+</head>
+<body>
+
+ <h1><?php echo html::chars(__($title)) ?></h1>
+ <?php echo $content ?>
+
+ <p class="copyright">
+ <?php echo __('Rendered in {execution_time} seconds, using {memory_usage} of memory')?><br />
+ Copyright ©2007–2008 Kohana Team
+ </p>
+
+</body>
+</html> \ No newline at end of file
diff --git a/system/views/profiler/profiler.php b/system/views/profiler/profiler.php
new file mode 100644
index 0000000..7b9ae95
--- /dev/null
+++ b/system/views/profiler/profiler.php
@@ -0,0 +1,37 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.'); ?>
+<style type="text/css">
+#kohana-profiler
+{
+ font-family: Monaco, 'Courier New';
+ background-color: #F8FFF8;
+ margin-top: 20px;
+ clear: both;
+ padding: 10px 10px 0;
+ border: 1px solid #E5EFF8;
+ text-align: left;
+}
+#kohana-profiler pre
+{
+ margin: 0;
+ font: inherit;
+}
+#kohana-profiler .kp-meta
+{
+ margin: 0 0 10px;
+ padding: 4px;
+ background: #FFF;
+ border: 1px solid #E5EFF8;
+ color: #A6B0B8;
+ text-align: center;
+}
+<?php echo $styles ?>
+</style>
+<div id="kohana-profiler">
+<?php
+foreach ($profiles as $profile)
+{
+ echo $profile->render();
+}
+?>
+<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/profiler/table.css b/system/views/profiler/table.css
new file mode 100644
index 0000000..41a1c9a
--- /dev/null
+++ b/system/views/profiler/table.css
@@ -0,0 +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;
+} \ No newline at end of file
diff --git a/system/views/profiler/table.php b/system/views/profiler/table.php
new file mode 100644
index 0000000..7cdf79d
--- /dev/null
+++ b/system/views/profiler/table.php
@@ -0,0 +1,24 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.'); ?>
+<table class="kp-table">
+<?php
+foreach ($rows as $row):
+
+$class = empty($row['class']) ? '' : ' class="'.$row['class'].'"';
+$style = empty($row['style']) ? '' : ' style="'.$row['style'].'"';
+?>
+ <tr<?php echo $class; echo $style; ?>>
+ <?php
+ foreach ($columns as $index => $column)
+ {
+ $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>'.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>