Initial Kohana install
authorArtem Daniliants <artem@daniliants.com>
Fri, 5 Mar 2010 11:30:48 +0000 (13:30 +0200)
committerArtem Daniliants <artem@daniliants.com>
Fri, 5 Mar 2010 11:30:48 +0000 (13:30 +0200)
165 files changed:
Server/.htaccess [new file with mode: 0644]
Server/application/config/config.php [new file with mode: 0644]
Server/application/controllers/examples.php [new file with mode: 0644]
Server/application/controllers/welcome.php [new file with mode: 0644]
Server/application/views/welcome_content.php [new file with mode: 0644]
Server/index.php [new file with mode: 0644]
Server/install.php [new file with mode: 0644]
Server/system/config/cache.php [new file with mode: 0644]
Server/system/config/cache_memcache.php [new file with mode: 0644]
Server/system/config/cache_sqlite.php [new file with mode: 0644]
Server/system/config/cache_xcache.php [new file with mode: 0644]
Server/system/config/captcha.php [new file with mode: 0644]
Server/system/config/cookie.php [new file with mode: 0644]
Server/system/config/credit_cards.php [new file with mode: 0644]
Server/system/config/database.php [new file with mode: 0644]
Server/system/config/email.php [new file with mode: 0644]
Server/system/config/encryption.php [new file with mode: 0644]
Server/system/config/http.php [new file with mode: 0644]
Server/system/config/image.php [new file with mode: 0644]
Server/system/config/inflector.php [new file with mode: 0644]
Server/system/config/locale.php [new file with mode: 0644]
Server/system/config/mimes.php [new file with mode: 0644]
Server/system/config/pagination.php [new file with mode: 0644]
Server/system/config/profiler.php [new file with mode: 0644]
Server/system/config/routes.php [new file with mode: 0644]
Server/system/config/session.php [new file with mode: 0644]
Server/system/config/sql_types.php [new file with mode: 0644]
Server/system/config/upload.php [new file with mode: 0644]
Server/system/config/user_agents.php [new file with mode: 0644]
Server/system/config/view.php [new file with mode: 0644]
Server/system/controllers/captcha.php [new file with mode: 0644]
Server/system/controllers/template.php [new file with mode: 0644]
Server/system/core/Benchmark.php [new file with mode: 0644]
Server/system/core/Bootstrap.php [new file with mode: 0644]
Server/system/core/Event.php [new file with mode: 0644]
Server/system/core/Kohana.php [new file with mode: 0644]
Server/system/core/utf8.php [new file with mode: 0644]
Server/system/core/utf8/from_unicode.php [new file with mode: 0644]
Server/system/core/utf8/ltrim.php [new file with mode: 0644]
Server/system/core/utf8/ord.php [new file with mode: 0644]
Server/system/core/utf8/rtrim.php [new file with mode: 0644]
Server/system/core/utf8/str_ireplace.php [new file with mode: 0644]
Server/system/core/utf8/str_pad.php [new file with mode: 0644]
Server/system/core/utf8/str_split.php [new file with mode: 0644]
Server/system/core/utf8/strcasecmp.php [new file with mode: 0644]
Server/system/core/utf8/strcspn.php [new file with mode: 0644]
Server/system/core/utf8/stristr.php [new file with mode: 0644]
Server/system/core/utf8/strlen.php [new file with mode: 0644]
Server/system/core/utf8/strpos.php [new file with mode: 0644]
Server/system/core/utf8/strrev.php [new file with mode: 0644]
Server/system/core/utf8/strrpos.php [new file with mode: 0644]
Server/system/core/utf8/strspn.php [new file with mode: 0644]
Server/system/core/utf8/strtolower.php [new file with mode: 0644]
Server/system/core/utf8/strtoupper.php [new file with mode: 0644]
Server/system/core/utf8/substr.php [new file with mode: 0644]
Server/system/core/utf8/substr_replace.php [new file with mode: 0644]
Server/system/core/utf8/to_unicode.php [new file with mode: 0644]
Server/system/core/utf8/transliterate_to_ascii.php [new file with mode: 0644]
Server/system/core/utf8/trim.php [new file with mode: 0644]
Server/system/core/utf8/ucfirst.php [new file with mode: 0644]
Server/system/core/utf8/ucwords.php [new file with mode: 0644]
Server/system/fonts/DejaVuSerif.ttf [new file with mode: 0644]
Server/system/fonts/LICENSE [new file with mode: 0644]
Server/system/helpers/arr.php [new file with mode: 0644]
Server/system/helpers/cookie.php [new file with mode: 0644]
Server/system/helpers/date.php [new file with mode: 0644]
Server/system/helpers/download.php [new file with mode: 0644]
Server/system/helpers/email.php [new file with mode: 0644]
Server/system/helpers/expires.php [new file with mode: 0644]
Server/system/helpers/feed.php [new file with mode: 0644]
Server/system/helpers/file.php [new file with mode: 0644]
Server/system/helpers/form.php [new file with mode: 0644]
Server/system/helpers/format.php [new file with mode: 0644]
Server/system/helpers/html.php [new file with mode: 0644]
Server/system/helpers/inflector.php [new file with mode: 0644]
Server/system/helpers/num.php [new file with mode: 0644]
Server/system/helpers/remote.php [new file with mode: 0644]
Server/system/helpers/request.php [new file with mode: 0644]
Server/system/helpers/security.php [new file with mode: 0644]
Server/system/helpers/text.php [new file with mode: 0644]
Server/system/helpers/upload.php [new file with mode: 0644]
Server/system/helpers/url.php [new file with mode: 0644]
Server/system/helpers/valid.php [new file with mode: 0644]
Server/system/i18n/en_US/cache.php [new file with mode: 0644]
Server/system/i18n/en_US/calendar.php [new file with mode: 0644]
Server/system/i18n/en_US/captcha.php [new file with mode: 0644]
Server/system/i18n/en_US/core.php [new file with mode: 0644]
Server/system/i18n/en_US/database.php [new file with mode: 0644]
Server/system/i18n/en_US/encrypt.php [new file with mode: 0644]
Server/system/i18n/en_US/errors.php [new file with mode: 0644]
Server/system/i18n/en_US/event.php [new file with mode: 0644]
Server/system/i18n/en_US/image.php [new file with mode: 0644]
Server/system/i18n/en_US/orm.php [new file with mode: 0644]
Server/system/i18n/en_US/pagination.php [new file with mode: 0644]
Server/system/i18n/en_US/profiler.php [new file with mode: 0644]
Server/system/i18n/en_US/session.php [new file with mode: 0644]
Server/system/i18n/en_US/swift.php [new file with mode: 0644]
Server/system/i18n/en_US/upload.php [new file with mode: 0644]
Server/system/i18n/en_US/validation.php [new file with mode: 0644]
Server/system/libraries/Cache.php [new file with mode: 0644]
Server/system/libraries/Calendar.php [new file with mode: 0644]
Server/system/libraries/Calendar_Event.php [new file with mode: 0644]
Server/system/libraries/Captcha.php [new file with mode: 0644]
Server/system/libraries/Controller.php [new file with mode: 0644]
Server/system/libraries/Database.php [new file with mode: 0644]
Server/system/libraries/Database_Expression.php [new file with mode: 0644]
Server/system/libraries/Encrypt.php [new file with mode: 0644]
Server/system/libraries/Event_Observer.php [new file with mode: 0644]
Server/system/libraries/Event_Subject.php [new file with mode: 0644]
Server/system/libraries/Image.php [new file with mode: 0644]
Server/system/libraries/Input.php [new file with mode: 0644]
Server/system/libraries/Model.php [new file with mode: 0644]
Server/system/libraries/ORM.php [new file with mode: 0644]
Server/system/libraries/ORM_Iterator.php [new file with mode: 0644]
Server/system/libraries/ORM_Tree.php [new file with mode: 0644]
Server/system/libraries/ORM_Versioned.php [new file with mode: 0644]
Server/system/libraries/Pagination.php [new file with mode: 0644]
Server/system/libraries/Profiler.php [new file with mode: 0644]
Server/system/libraries/Profiler_Table.php [new file with mode: 0644]
Server/system/libraries/Router.php [new file with mode: 0644]
Server/system/libraries/Session.php [new file with mode: 0644]
Server/system/libraries/Tagcloud.php [new file with mode: 0644]
Server/system/libraries/URI.php [new file with mode: 0644]
Server/system/libraries/Validation.php [new file with mode: 0644]
Server/system/libraries/View.php [new file with mode: 0644]
Server/system/libraries/drivers/Cache.php [new file with mode: 0644]
Server/system/libraries/drivers/Cache/Apc.php [new file with mode: 0644]
Server/system/libraries/drivers/Cache/Eaccelerator.php [new file with mode: 0644]
Server/system/libraries/drivers/Cache/File.php [new file with mode: 0644]
Server/system/libraries/drivers/Cache/Memcache.php [new file with mode: 0644]
Server/system/libraries/drivers/Cache/Sqlite.php [new file with mode: 0644]
Server/system/libraries/drivers/Cache/Xcache.php [new file with mode: 0644]
Server/system/libraries/drivers/Captcha.php [new file with mode: 0644]
Server/system/libraries/drivers/Captcha/Alpha.php [new file with mode: 0644]
Server/system/libraries/drivers/Captcha/Basic.php [new file with mode: 0644]
Server/system/libraries/drivers/Captcha/Black.php [new file with mode: 0644]
Server/system/libraries/drivers/Captcha/Math.php [new file with mode: 0644]
Server/system/libraries/drivers/Captcha/Riddle.php [new file with mode: 0644]
Server/system/libraries/drivers/Captcha/Word.php [new file with mode: 0644]
Server/system/libraries/drivers/Database.php [new file with mode: 0644]
Server/system/libraries/drivers/Database/Mssql.php [new file with mode: 0644]
Server/system/libraries/drivers/Database/Mysql.php [new file with mode: 0644]
Server/system/libraries/drivers/Database/Mysqli.php [new file with mode: 0644]
Server/system/libraries/drivers/Database/Pdosqlite.php [new file with mode: 0644]
Server/system/libraries/drivers/Database/Pgsql.php [new file with mode: 0644]
Server/system/libraries/drivers/Image.php [new file with mode: 0644]
Server/system/libraries/drivers/Image/GD.php [new file with mode: 0644]
Server/system/libraries/drivers/Image/GraphicsMagick.php [new file with mode: 0644]
Server/system/libraries/drivers/Image/ImageMagick.php [new file with mode: 0644]
Server/system/libraries/drivers/Session.php [new file with mode: 0644]
Server/system/libraries/drivers/Session/Cache.php [new file with mode: 0644]
Server/system/libraries/drivers/Session/Cookie.php [new file with mode: 0644]
Server/system/libraries/drivers/Session/Database.php [new file with mode: 0644]
Server/system/views/kohana/template.php [new file with mode: 0644]
Server/system/views/kohana_calendar.php [new file with mode: 0644]
Server/system/views/kohana_error_disabled.php [new file with mode: 0644]
Server/system/views/kohana_error_page.php [new file with mode: 0644]
Server/system/views/kohana_errors.css [new file with mode: 0644]
Server/system/views/kohana_profiler.php [new file with mode: 0644]
Server/system/views/kohana_profiler_table.css [new file with mode: 0644]
Server/system/views/kohana_profiler_table.php [new file with mode: 0644]
Server/system/views/pagination/classic.php [new file with mode: 0644]
Server/system/views/pagination/digg.php [new file with mode: 0644]
Server/system/views/pagination/extended.php [new file with mode: 0644]
Server/system/views/pagination/punbb.php [new file with mode: 0644]

diff --git a/Server/.htaccess b/Server/.htaccess
new file mode 100644 (file)
index 0000000..b79e25f
--- /dev/null
@@ -0,0 +1,15 @@
+# Turn on URL rewriting
+RewriteEngine On
+
+# Installation directory
+RewriteBase /
+
+# Protect application and system files from being viewed
+RewriteRule ^(application|modules|system) - [F,L]
+
+# Allow any files or directories that exist to be displayed directly
+RewriteCond %{REQUEST_FILENAME} !-f
+RewriteCond %{REQUEST_FILENAME} !-d
+
+# Rewrite all other URLs to index.php/URL
+RewriteRule .* index.php/$0 [PT,L]
diff --git a/Server/application/config/config.php b/Server/application/config/config.php
new file mode 100644 (file)
index 0000000..8c45192
--- /dev/null
@@ -0,0 +1,125 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Base path of the web site. If this includes a domain, eg: localhost/kohana/
+ * then a full URL will be used, eg: http://localhost/kohana/. If it only includes
+ * the path, and a site_protocol is specified, the domain will be auto-detected.
+ */
+$config['site_domain'] = '/kohana/';
+
+/**
+ * Force a default protocol to be used by the site. If no site_protocol is
+ * specified, then the current protocol is used, or when possible, only an
+ * absolute path (with no protocol/domain) is used.
+ */
+$config['site_protocol'] = '';
+
+/**
+ * Name of the front controller for this application. Default: index.php
+ *
+ * This can be removed by using URL rewriting.
+ */
+$config['index_page'] = 'index.php';
+
+/**
+ * Fake file extension that will be added to all generated URLs. Example: .html
+ */
+$config['url_suffix'] = '';
+
+/**
+ * Length of time of the internal cache in seconds. 0 or FALSE means no caching.
+ * The internal cache stores file paths and config entries across requests and
+ * can give significant speed improvements at the expense of delayed updating.
+ */
+$config['internal_cache'] = FALSE;
+
+/**
+ * Internal cache directory.
+ */
+$config['internal_cache_path'] = APPPATH.'cache/';
+
+/**
+ * Enable internal cache encryption - speed/processing loss
+ * is neglible when this is turned on. Can be turned off
+ * if application directory is not in the webroot.
+ */
+$config['internal_cache_encrypt'] = FALSE;
+
+/**
+ * Encryption key for the internal cache, only used
+ * if internal_cache_encrypt is TRUE.
+ *
+ * Make sure you specify your own key here!
+ *
+ * The cache is deleted when/if the key changes.
+ */
+$config['internal_cache_key'] = 'foobar-changeme';
+
+/**
+ * Enable or disable gzip output compression. This can dramatically decrease
+ * server bandwidth usage, at the cost of slightly higher CPU usage. Set to
+ * the compression level (1-9) that you want to use, or FALSE to disable.
+ *
+ * Do not enable this option if you are using output compression in php.ini!
+ */
+$config['output_compression'] = FALSE;
+
+/**
+ * Enable or disable global XSS filtering of GET, POST, and SERVER data. This
+ * option also accepts a string to specify a specific XSS filtering tool.
+ */
+$config['global_xss_filtering'] = TRUE;
+
+/**
+ * Enable or disable hooks.
+ */
+$config['enable_hooks'] = FALSE;
+
+/**
+ * Log thresholds:
+ *  0 - Disable logging
+ *  1 - Errors and exceptions
+ *  2 - Warnings
+ *  3 - Notices
+ *  4 - Debugging
+ */
+$config['log_threshold'] = 1;
+
+/**
+ * Message logging directory.
+ */
+$config['log_directory'] = APPPATH.'logs';
+
+/**
+ * Enable or disable displaying of Kohana error pages. This will not affect
+ * logging. Turning this off will disable ALL error pages.
+ */
+$config['display_errors'] = TRUE;
+
+/**
+ * Enable or disable statistics in the final output. Stats are replaced via
+ * specific strings, such as {execution_time}.
+ *
+ * @see http://docs.kohanaphp.com/general/configuration
+ */
+$config['render_stats'] = TRUE;
+
+/**
+ * Filename prefixed used to determine extensions. For example, an
+ * extension to the Controller class would be named MY_Controller.php.
+ */
+$config['extension_prefix'] = 'MY_';
+
+/**
+ * Additional resource paths, or "modules". Each path can either be absolute
+ * or relative to the docroot. Modules can include any resource that can exist
+ * in your application directory, configuration files, controllers, views, etc.
+ */
+$config['modules'] = array
+(
+       // MODPATH.'auth',      // Authentication
+       // MODPATH.'kodoc',     // Self-generating documentation
+       // MODPATH.'gmaps',     // Google Maps integration
+       // MODPATH.'archive',   // Archive utility
+       // MODPATH.'payment',   // Online payments
+       // MODPATH.'unit_test', // Unit testing
+);
diff --git a/Server/application/controllers/examples.php b/Server/application/controllers/examples.php
new file mode 100644 (file)
index 0000000..849418b
--- /dev/null
@@ -0,0 +1,470 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Contains examples of various Kohana library examples. You can access these
+ * samples in your own installation of Kohana by going to ROOT_URL/examples.
+ * This controller should NOT be used in production. It is for demonstration
+ * purposes only!
+ *
+ * $Id: examples.php 4298 2009-04-30 17:06:05Z kiall $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Examples_Controller extends Controller {
+
+       // Do not allow to run in production
+       const ALLOW_PRODUCTION = FALSE;
+
+       /**
+        * Displays a list of available examples
+        */
+       function index()
+       {
+               // Get the methods that are only in this class and not the parent class.
+               $examples = array_diff
+               (
+                       get_class_methods(__CLASS__),
+                       get_class_methods(get_parent_class($this))
+               );
+
+               sort($examples);
+
+               echo "<strong>Examples:</strong>\n";
+               echo "<ul>\n";
+
+               foreach ($examples as $method)
+               {
+                       if ($method == __FUNCTION__)
+                               continue;
+
+                       echo '<li>'.html::anchor('examples/'.$method, $method)."</li>\n";
+               }
+
+               echo "</ul>\n";
+               echo '<p>'.Kohana::lang('core.stats_footer')."</p>\n";
+       }
+
+       /**
+        * Demonstrates how to archive a directory. First enable the archive module
+        */
+       //public function archive($build = FALSE)
+       //{
+       //      if ($build === 'build')
+       //      {
+       //              // Load archive
+       //              $archive = new Archive('zip');
+
+       //              // Download the application/views directory
+       //              $archive->add(APPPATH.'views/', 'app_views/', TRUE);
+
+       //              // Download the built archive
+       //              $archive->download('test.zip');
+       //      }
+       //      else
+       //      {
+       //              echo html::anchor(Router::$current_uri.'/build', 'Download views');
+       //      }
+       //}
+
+       /**
+        * Demonstrates how to parse RSS feeds by using DOMDocument.
+        */
+       function rss()
+       {
+               // Parse an external atom feed
+               $feed = feed::parse('http://dev.kohanaphp.com/projects/kohana2/activity.atom');
+
+               // Show debug info
+               echo Kohana::debug($feed);
+
+               echo Kohana::lang('core.stats_footer');
+       }
+
+       /**
+        * Demonstrates the Session library and using session data.
+        */
+       function session()
+       {
+               // Gets the singleton instance of the Session library
+               $s = Session::instance();
+
+               echo 'SESSID: <pre>'.session_id()."</pre>\n";
+
+               echo '<pre>'.print_r($_SESSION, TRUE)."</pre>\n";
+
+               echo '<br/>{execution_time} seconds';
+       }
+
+       /**
+        * Demonstrates how to use the form helper with the Validation libraryfor file uploads .
+        */
+       function form()
+       {
+               // Anything submitted?
+               if ($_POST)
+               {
+                       // Merge the globals into our validation object.
+                       $post = Validation::factory(array_merge($_POST, $_FILES));
+
+                       // Ensure upload helper is correctly configured, config/upload.php contains default entries.
+                       // Uploads can be required or optional, but should be valid
+                       $post->add_rules('imageup1', 'upload::required', 'upload::valid', 'upload::type[gif,jpg,png]', 'upload::size[1M]');
+                       $post->add_rules('imageup2', 'upload::required', 'upload::valid', 'upload::type[gif,jpg,png]', 'upload::size[1M]');
+
+                       // Alternative syntax for multiple file upload validation rules
+                       //$post->add_rules('imageup.*', 'upload::required', 'upload::valid', 'upload::type[gif,jpg,png]', 'upload::size[1M]');
+
+                       if ($post->validate() )
+                       {
+                               // It worked!
+                               // Move (and rename) the files from php upload folder to configured application folder
+                               upload::save('imageup1');
+                               upload::save('imageup2');
+                               echo 'Validation successfull, check your upload folder!';
+                       }
+                       else
+                       {
+                               // You got validation errors
+                               echo '<p>validation errors: '.var_export($post->errors(), TRUE).'</p>';
+                               echo Kohana::debug($post);
+                       }
+               }
+
+               // Display the form
+               echo form::open('examples/form', array('enctype' => 'multipart/form-data'));
+               echo form::label('imageup', 'Image Uploads').':<br/>';
+               // Use discrete upload fields
+               // Alternative syntax for multiple file uploads
+               // echo form::upload('imageup[]').'<br/>';
+
+               echo form::upload('imageup1').'<br/>';
+               echo form::upload('imageup2').'<br/>';
+               echo form::submit('upload', 'Upload!');
+               echo form::close();
+
+       }
+
+       /**
+        * Demontrates how to use the Validation library to validate an arbitrary array.
+        */
+       function validation()
+       {
+               // To demonstrate Validation being able to validate any array, I will
+               // be using a pre-built array. When you load validation with no arguments
+               // it will default to validating the POST array.
+               $data = array
+               (
+                       'user' => 'hello',
+                       'pass' => 'bigsecret',
+                       'reme' => '1'
+               );
+
+               $validation = new Validation($data);
+
+               $validation->add_rules('user', 'required', 'length[1,12]')->pre_filter('trim', 'user');
+               $validation->add_rules('pass', 'required')->post_filter('sha1', 'pass');
+               $validation->add_rules('reme', 'required');
+
+               $result = $validation->validate();
+
+               var_dump($validation->errors());
+               var_dump($validation->as_array());
+
+               // Yay!
+               echo '{execution_time} ALL DONE!';
+       }
+
+       /**
+        * Demontrates how to use the Captcha library.
+        */
+       public function captcha()
+       {
+               // Look at the counters for valid and invalid
+               // responses in the Session Profiler.
+               new Profiler;
+
+               // Load Captcha library, you can supply the name
+               // of the config group you would like to use.
+               $captcha = new Captcha;
+
+               // Ban bots (that accept session cookies) after 50 invalid responses.
+               // Be careful not to ban real people though! Set the threshold high enough.
+               if ($captcha->invalid_count() > 49)
+                       exit('Bye! Stupid bot.');
+
+               // Form submitted
+               if ($_POST)
+               {
+                       // Captcha::valid() is a static method that can be used as a Validation rule also.
+                       if (Captcha::valid($this->input->post('captcha_response')))
+                       {
+                               echo '<p style="color:green">Good answer!</p>';
+                       }
+                       else
+                       {
+                               echo '<p style="color:red">Wrong answer!</p>';
+                       }
+
+                       // Validate other fields here
+               }
+
+               // Show form
+               echo form::open();
+               echo '<p>Other form fields here...</p>';
+
+               // Don't show Captcha anymore after the user has given enough valid
+               // responses. The "enough" count is set in the captcha config.
+               if ( ! $captcha->promoted())
+               {
+                       echo '<p>';
+                       echo $captcha->render(); // Shows the Captcha challenge (image/riddle/etc)
+                       echo '</p>';
+                       echo form::input('captcha_response');
+               }
+               else
+               {
+                       echo '<p>You have been promoted to human.</p>';
+               }
+
+               // Close form
+               echo form::submit(array('value' => 'Check'));
+               echo form::close();
+       }
+
+       /**
+        * Demonstrates the features of the Database library.
+        *
+        * Table Structure:
+        *  CREATE TABLE `pages` (
+        *  `id` mediumint( 9 ) NOT NULL AUTO_INCREMENT ,
+        *  `page_name` varchar( 100 ) NOT NULL ,
+        *  `title` varchar( 255 ) NOT NULL ,
+        *  `content` longtext NOT NULL ,
+        *  `menu` tinyint( 1 ) NOT NULL default '0',
+        *  `filename` varchar( 255 ) NOT NULL ,
+        *  `order` mediumint( 9 ) NOT NULL ,
+        *  `date` int( 11 ) NOT NULL ,
+        *  `child_of` mediumint( 9 ) NOT NULL default '0',
+        *  PRIMARY KEY ( `id` ) ,
+        *  UNIQUE KEY `filename` ( `filename` )
+        *  ) ENGINE = MYISAM DEFAULT CHARSET = utf8 PACK_KEYS =0;
+        *
+       */
+       function database()
+       {
+               $db = new Database;
+
+               $table = 'pages';
+               echo 'Does the '.$table.' table exist? ';
+               if ($db->table_exists($table))
+               {
+                       echo '<p>YES! Lets do some work =)</p>';
+
+                       $query = $db->select('DISTINCT pages.*')->from($table)->get();
+                       echo $db->last_query();
+                       echo '<h3>Iterate through the result:</h3>';
+                       foreach ($query as $item)
+                       {
+                               echo '<p>'.$item->title.'</p>';
+                       }
+                       echo '<h3>Numrows using count(): '.count($query).'</h3>';
+                       echo 'Table Listing:<pre>'.print_r($db->list_tables(), TRUE).'</pre>';
+
+                       echo '<h3>Try Query Binding with objects:</h3>';
+                       $sql = 'SELECT * FROM '.$table.' WHERE id = ?';
+                       $query = $db->query($sql, array(1));
+                       echo '<p>'.$db->last_query().'</p>';
+                       $query->result(TRUE);
+                       foreach ($query as $item)
+                       {
+                               echo '<pre>'.print_r($item, true).'</pre>';
+                       }
+
+                       echo '<h3>Try Query Binding with arrays (returns both associative and numeric because I pass MYSQL_BOTH to result():</h3>';
+                       $sql = 'SELECT * FROM '.$table.' WHERE id = ?';
+                       $query = $db->query($sql, array(1));
+                       echo '<p>'.$db->last_query().'</p>';
+                       $query->result(FALSE, MYSQL_BOTH);
+                       foreach ($query as $item)
+                       {
+                               echo '<pre>'.print_r($item, true).'</pre>';
+                       }
+
+                       echo '<h3>Look, we can also manually advance the result pointer!</h3>';
+                       $query = $db->select('title')->from($table)->get();
+                       echo 'First:<pre>'.print_r($query->current(), true).'</pre><br />';
+                       $query->next();
+                       echo 'Second:<pre>'.print_r($query->current(), true).'</pre><br />';
+                       $query->next();
+                       echo 'Third:<pre>'.print_r($query->current(), true).'</pre>';
+                       echo '<h3>And we can reset it to the beginning:</h3>';
+                       $query->rewind();
+                       echo 'Rewound:<pre>'.print_r($query->current(), true).'</pre>';
+
+                       echo '<p>Number of rows using count_records(): '.$db->count_records('pages').'</p>';
+               }
+               else
+               {
+                       echo 'NO! The '.$table.' table doesn\'t exist, so we can\'t continue =( ';
+               }
+               echo "<br/><br/>\n";
+               echo 'done in {execution_time} seconds';
+       }
+
+       /**
+        * Demonstrates how to use the Pagination library and Pagination styles.
+        */
+       function pagination()
+       {
+               $pagination = new Pagination(array(
+                       // Base_url will default to the current URI
+                       // 'base_url'    => 'welcome/pagination_example/page/x',
+
+                       // The URI segment (integer) in which the pagination number can be found
+                       // The URI segment (string) that precedes the pagination number (aka "label")
+                       'uri_segment'    => 'page',
+
+                       // You could also use the query string for pagination instead of the URI segments
+                       // Just set this to the $_GET key that contains the page number
+                       // 'query_string'   => 'page',
+
+                       // The total items to paginate through (probably need to use a database COUNT query here)
+                       'total_items'    => 254,
+
+                       // The amount of items you want to display per page
+                       'items_per_page' => 10,
+
+                       // The pagination style: classic (default), digg, extended or punbb
+                       // Easily add your own styles to views/pagination and point to the view name here
+                       'style'          => 'classic',
+
+                       // If there is only one page, completely hide all pagination elements
+                       // Pagination->render() will return an empty string
+                       'auto_hide'      => TRUE,
+               ));
+
+               // Just echo to display the links (__toString() rocks!)
+               echo 'Classic style: '.$pagination;
+
+               // You can also use the render() method and pick a style on the fly if you want
+               echo '<hr /> Digg style:     ', $pagination->render('digg');
+               echo '<hr /> Extended style: ', $pagination->render('extended');
+               echo '<hr /> PunBB style:    ', $pagination->render('punbb');
+               echo 'done in {execution_time} seconds';
+       }
+
+       /**
+        * Demonstrates the User_Agent library.
+        */
+       function user_agent()
+       {
+               foreach (array('agent', 'browser', 'version') as $key)
+               {
+                       echo $key.': '.Kohana::user_agent($key).'<br/>'."\n";
+               }
+
+               echo "<br/><br/>\n";
+               echo 'done in {execution_time} seconds';
+       }
+
+       /**
+        * Demonstrates the Payment library.
+        */
+       /*function payment()
+       {
+               $credit_card = new Payment;
+
+               // You can also pass the driver name to the library to use multiple ones:
+               $credit_card = new Payment('Paypal');
+               $credit_card = new Payment('Authorize');
+
+               // You can specify one parameter at a time:
+               $credit_card->login = 'this';
+               $credit_card->first_name = 'Jeremy';
+               $credit_card->last_name = 'Bush';
+               $credit_card->card_num = '1234567890';
+               $credit_card->exp_date = '0910';
+               $credit_card->amount = '478.41';
+
+               // Or you can also set fields with an array and the <Payment.set_fields> method:
+               $credit_card->set_fields(array('login' => 'test',
+                                              'first_name' => 'Jeremy',
+                                              'last_name' => 'Bush',
+                                              'card_num' => '1234567890',
+                                              'exp_date' => '0910',
+                                              'amount' => '487.41'));
+
+               echo '<pre>'.print_r($credit_card, true).'</pre>';
+
+               echo 'Success? ';
+               echo ($response = $credit_card->process() == TRUE) ? 'YES!' : $response;
+       }*/
+
+       function calendar()
+       {
+               $profiler = new Profiler;
+
+               $calendar = new Calendar($this->input->get('month', date('m')), $this->input->get('year', date('Y')));
+               $calendar->attach($calendar->event()
+                               ->condition('year', 2008)
+                               ->condition('month', 8)
+                               ->condition('day', 8)
+                               ->output(html::anchor('http://forum.kohanaphp.com/comments.php?DiscussionID=275', 'Learning about Kohana Calendar')));
+
+               echo $calendar->render();
+       }
+
+       /**
+        * Demonstrates how to use the Image libarary..
+        */
+       function image()
+       {
+               // For testing only, save the new image in DOCROOT
+               $dir = realpath(DOCROOT);
+
+               // Original Image filename
+               $image = DOCROOT.'kohana.png';
+
+               // Create an instance of Image, with file
+               // The orginal image is not affected
+               $image = new Image($image);
+
+               // Most methods are chainable
+               // Resize the image, crop the center left
+               $image->resize(200, 100)->crop(150, 50, 'center', 'left');
+
+               // Display image in browser.
+               // Keep the actions, to be applied when saving the image.
+               $image->render($keep_actions = TRUE);
+
+               // Save the image, as a jpeg
+               // Here the actions will be discarded, by default.
+               $image->save($dir.'/mypic_thumb.jpg');
+
+               //echo Kohana::debug($image);
+       }
+
+       /**
+        * Demonstrates how to use vendor software with Kohana.
+        */
+       function vendor()
+       {
+               // Let's do a little Markdown shall we.
+               $br = "\n\n";
+               $output = '#Marked Down!#'.$br;
+               $output .= 'This **_markup_** is created *on-the-fly*, by ';
+               $output .= '[php-markdown-extra](http://michelf.com/projects/php-markdown/extra)'.$br;
+               $output .= 'It\'s *great* for user <input> & writing about `<HTML>`'.$br;
+               $output .= 'It\'s also good at footnotes :-) [^1]'.$br;
+               $output .= '[^1]: A footnote.';
+
+               // looks in system/vendor for Markdown.php
+               require Kohana::find_file('vendor', 'Markdown');
+
+               echo Markdown($output);
+
+               echo 'done in {execution_time} seconds';
+       }
+} // End Examples
diff --git a/Server/application/controllers/welcome.php b/Server/application/controllers/welcome.php
new file mode 100644 (file)
index 0000000..8941953
--- /dev/null
@@ -0,0 +1,54 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Default Kohana controller. This controller should NOT be used in production.
+ * It is for demonstration purposes only!
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Welcome_Controller extends Template_Controller {
+
+       // Disable this controller when Kohana is set to production mode.
+       // See http://docs.kohanaphp.com/installation/deployment for more details.
+       const ALLOW_PRODUCTION = FALSE;
+
+       // Set the name of the template to use
+       public $template = 'kohana/template';
+
+       public function index()
+       {
+               // In Kohana, all views are loaded and treated as objects.
+               $this->template->content = new View('welcome_content');
+
+               // You can assign anything variable to a view by using standard OOP
+               // methods. In my welcome view, the $title variable will be assigned
+               // the value I give it here.
+               $this->template->title = 'Welcome to Kohana!';
+
+               // An array of links to display. Assiging variables to views is completely
+               // asyncronous. Variables can be set in any order, and can be any type
+               // of data, including objects.
+               $this->template->content->links = array
+               (
+                       'Home Page'     => 'http://kohanaphp.com/',
+                       'Documentation' => 'http://docs.kohanaphp.com/',
+                       'Forum'         => 'http://forum.kohanaphp.com/',
+                       'License'       => 'Kohana License.html',
+                       'Donate'        => 'http://kohanaphp.com/donate',
+               );
+       }
+
+       public function __call($method, $arguments)
+       {
+               // Disable auto-rendering
+               $this->auto_render = FALSE;
+
+               // By defining a __call method, all pages routed to this controller
+               // that result in 404 errors will be handled by this method, instead of
+               // being displayed as "Page Not Found" errors.
+               echo 'This text is generated by __call. If you expected the index page, you need to use: welcome/index/'.substr(Router::$current_uri, 8);
+       }
+
+} // End Welcome Controller
\ No newline at end of file
diff --git a/Server/application/views/welcome_content.php b/Server/application/views/welcome_content.php
new file mode 100644 (file)
index 0000000..0adcab6
--- /dev/null
@@ -0,0 +1,15 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.'); ?>
+<div class="box">
+       <p>This is the default Kohana index page. You may also access this page as <code><?php echo html::anchor('welcome/index', 'welcome/index') ?></code>.</p>
+
+       <p>
+               To change what gets displayed for this page, edit <code>application/controllers/welcome.php</code>.<br />
+               To change this text, edit <code>application/views/welcome_content.php</code>.
+       </p>
+</div>
+
+<ul>
+<?php foreach ($links as $title => $url): ?>
+       <li><?php echo ($title === 'License') ? html::file_anchor($url, html::specialchars($title)) : html::anchor($url, html::specialchars($title)) ?></li>
+<?php endforeach ?>
+</ul>
\ No newline at end of file
diff --git a/Server/index.php b/Server/index.php
new file mode 100644 (file)
index 0000000..4217a28
--- /dev/null
@@ -0,0 +1,107 @@
+<?php
+/**
+ * This file acts as the "front controller" to your application. You can
+ * configure your application, modules, and system directories here.
+ * PHP error_reporting level may also be changed.
+ *
+ * @see http://kohanaphp.com
+ */
+
+/**
+ * Define the website environment status. When this flag is set to TRUE, some
+ * module demonstration controllers will result in 404 errors. For more information
+ * about this option, read the documentation about deploying Kohana.
+ *
+ * @see http://docs.kohanaphp.com/installation/deployment
+ */
+define('IN_PRODUCTION', FALSE);
+
+/**
+ * Website application directory. This directory should contain your application
+ * configuration, controllers, models, views, and other resources.
+ *
+ * This path can be absolute or relative to this file.
+ */
+$kohana_application = 'application';
+
+/**
+ * Kohana modules directory. This directory should contain all the modules used
+ * by your application. Modules are enabled and disabled by the application
+ * configuration file.
+ *
+ * This path can be absolute or relative to this file.
+ */
+$kohana_modules = 'modules';
+
+/**
+ * Kohana system directory. This directory should contain the core/ directory,
+ * and the resources you included in your download of Kohana.
+ *
+ * This path can be absolute or relative to this file.
+ */
+$kohana_system = 'system';
+
+/**
+ * Test to make sure that Kohana is running on PHP 5.2 or newer. Once you are
+ * sure that your environment is compatible with Kohana, you can comment this
+ * line out. When running an application on a new server, uncomment this line
+ * to check the PHP version quickly.
+ */
+version_compare(PHP_VERSION, '5.2', '<') and exit('Kohana requires PHP 5.2 or newer.');
+
+/**
+ * Set the error reporting level. Unless you have a special need, E_ALL is a
+ * good level for error reporting.
+ */
+error_reporting(E_ALL & ~E_STRICT);
+
+/**
+ * Turning off display_errors will effectively disable Kohana error display
+ * and logging. You can turn off Kohana errors in application/config/config.php
+ */
+ini_set('display_errors', TRUE);
+
+/**
+ * If you rename all of your .php files to a different extension, set the new
+ * extension here. This option can left to .php, even if this file has a
+ * different extension.
+ */
+define('EXT', '.php');
+
+//
+// DO NOT EDIT BELOW THIS LINE, UNLESS YOU FULLY UNDERSTAND THE IMPLICATIONS.
+// ----------------------------------------------------------------------------
+// $Id: index.php 3915 2009-01-20 20:52:20Z zombor $
+//
+
+$kohana_pathinfo = pathinfo(__FILE__);
+// Define the front controller name and docroot
+define('DOCROOT', $kohana_pathinfo['dirname'].DIRECTORY_SEPARATOR);
+define('KOHANA',  $kohana_pathinfo['basename']);
+
+// If the front controller is a symlink, change to the real docroot
+is_link(KOHANA) and chdir(dirname(realpath(__FILE__)));
+
+// If kohana folders are relative paths, make them absolute.
+$kohana_application = file_exists($kohana_application) ? $kohana_application : DOCROOT.$kohana_application;
+$kohana_modules = file_exists($kohana_modules) ? $kohana_modules : DOCROOT.$kohana_modules;
+$kohana_system = file_exists($kohana_system) ? $kohana_system : DOCROOT.$kohana_system;
+
+// Define application and system paths
+define('APPPATH', str_replace('\\', '/', realpath($kohana_application)).'/');
+define('MODPATH', str_replace('\\', '/', realpath($kohana_modules)).'/');
+define('SYSPATH', str_replace('\\', '/', realpath($kohana_system)).'/');
+
+// Clean up
+unset($kohana_application, $kohana_modules, $kohana_system);
+
+if (file_exists(DOCROOT.'install'.EXT))
+{
+       // Load the installation tests
+       include DOCROOT.'install'.EXT;
+}
+else
+{
+       // Initialize Kohana
+       require SYSPATH.'core/Bootstrap'.EXT;
+}
\ No newline at end of file
diff --git a/Server/install.php b/Server/install.php
new file mode 100644 (file)
index 0000000..a380710
--- /dev/null
@@ -0,0 +1,154 @@
+<!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 Installation</title>
+
+<style type="text/css">
+body { width: 42em; margin: 0 auto; font-family: sans-serif; font-size: 90%; }
+
+#tests table { border-collapse: collapse; width: 100%; }
+       #tests table th,
+       #tests table td { padding: 0.2em 0.4em; text-align: left; vertical-align: top; }
+       #tests table th { width: 12em; font-weight: normal; font-size: 1.2em; }
+       #tests table tr:nth-child(odd) { background: #eee; }
+       #tests table td.pass { color: #191; }
+       #tests table td.fail { color: #911; }
+               #tests #results { color: #fff; }
+               #tests #results p { padding: 0.8em 0.4em; }
+               #tests #results p.pass { background: #191; }
+               #tests #results p.fail { background: #911; }
+</style>
+
+</head>
+<body>
+
+<h1>Environment Tests</h1>
+
+<p>The following tests have been run to determine if Kohana will work in your environment. If any of the tests have failed, consult the <a href="http://docs.kohanaphp.com/installation">documentation</a> for more information on how to correct the problem.</p>
+
+<div id="tests">
+<?php $failed = FALSE ?>
+<table cellspacing="0">
+<tr>
+<th>PHP Version</th>
+<?php if (version_compare(PHP_VERSION, '5.2', '>=')): ?>
+<td class="pass"><?php echo PHP_VERSION ?></td>
+<?php else: $failed = TRUE ?>
+<td class="fail">Kohana requires PHP 5.2 or newer, this version is <?php echo PHP_VERSION ?>.</td>
+<?php endif ?>
+</tr>
+<tr>
+<th>System Directory</th>
+<?php if (is_dir(SYSPATH) AND is_file(SYSPATH.'core/Bootstrap'.EXT)): ?>
+<td class="pass"><?php echo SYSPATH ?></td>
+<?php else: $failed = TRUE ?>
+<td class="fail">The configured <code>system</code> directory does not exist or does not contain required files.</td>
+<?php endif ?>
+</tr>
+<tr>
+<th>Application Directory</th>
+<?php if (is_dir(APPPATH) AND is_file(APPPATH.'config/config'.EXT)): ?>
+<td class="pass"><?php echo APPPATH ?></td>
+<?php else: $failed = TRUE ?>
+<td class="fail">The configured <code>application</code> directory does not exist or does not contain required files.</td>
+<?php endif ?>
+</tr>
+<tr>
+<th>Modules Directory</th>
+<?php if (is_dir(MODPATH)): ?>
+<td class="pass"><?php echo MODPATH ?></td>
+<?php else: $failed = TRUE ?>
+<td class="fail">The configured <code>modules</code> directory does not exist or does not contain required files.</td>
+<?php endif ?>
+</tr>
+<tr>
+<th>PCRE UTF-8</th>
+<?php if ( !function_exists('preg_match')): $failed = TRUE ?>
+<td class="fail"><a href="http://php.net/pcre">PCRE</a> support is missing.</td>
+<?php elseif ( ! @preg_match('/^.$/u', 'ñ')): $failed = TRUE ?>
+<td class="fail"><a href="http://php.net/pcre">PCRE</a> has not been compiled with UTF-8 support.</td>
+<?php elseif ( ! @preg_match('/^\pL$/u', 'ñ')): $failed = TRUE ?>
+<td class="fail"><a href="http://php.net/pcre">PCRE</a> has not been compiled with Unicode property support.</td>
+<?php else: ?>
+<td class="pass">Pass</td>
+<?php endif ?>
+</tr>
+<tr>
+<th>Reflection Enabled</th>
+<?php if (class_exists('ReflectionClass')): ?>
+<td class="pass">Pass</td>
+<?php else: $failed = TRUE ?>
+<td class="fail">PHP <a href="http://www.php.net/reflection">reflection</a> is either not loaded or not compiled in.</td>
+<?php endif ?>
+</tr>
+<tr>
+<th>Filters Enabled</th>
+<?php if (function_exists('filter_list')): ?>
+<td class="pass">Pass</td>
+<?php else: $failed = TRUE ?>
+<td class="fail">The <a href="http://www.php.net/filter">filter</a> extension is either not loaded or not compiled in.</td>
+<?php endif ?>
+</tr>
+<tr>
+<th>Iconv Extension Loaded</th>
+<?php if (extension_loaded('iconv')): ?>
+<td class="pass">Pass</td>
+<?php else: $failed = TRUE ?>
+<td class="fail">The <a href="http://php.net/iconv">iconv</a> extension is not loaded.</td>
+<?php endif ?>
+</tr>
+
+<tr>
+<th>SPL Enabled</th>
+<?php if (function_exists('spl_autoload_register')): ?>
+<td class="pass">Pass</td>
+<?php else: $failed = TRUE ?>
+<td class="fail"><a href="http://php.net/spl">SPL</a> is not enabled.</td>
+<?php endif ?>
+</tr>
+
+<?php if (extension_loaded('mbstring')): ?>
+<tr>
+<th>Mbstring Not Overloaded</th>
+<?php if (ini_get('mbstring.func_overload') & MB_OVERLOAD_STRING): $failed = TRUE ?>
+<td class="fail">The <a href="http://php.net/mbstring">mbstring</a> extension is overloading PHP's native string functions.</td>
+<?php else: ?>
+<td class="pass">Pass</td>
+</tr>
+<?php endif ?>
+<?php else: // check for utf8_[en|de]code when mbstring is not available ?>
+<tr>
+<th>XML support</th>
+<?php if ( ! function_exists('utf8_encode')): $failed = TRUE ?>
+<td class="fail">PHP is compiled without <a href="http://php.net/xml">XML</a> support, thus lacking support for <code>utf8_encode()</code>/<code>utf8_decode()</code>.</td>
+<?php else: ?>
+<td class="pass">Pass</td>
+<?php endif ?>
+</tr>
+<?php endif ?>
+<tr>
+<th>URI Determination</th>
+<?php if (isset($_SERVER['REQUEST_URI']) OR isset($_SERVER['PHP_SELF'])): ?>
+<td class="pass">Pass</td>
+<?php else: $failed = TRUE ?>
+<td class="fail">Neither <code>$_SERVER['REQUEST_URI']</code> or <code>$_SERVER['PHP_SELF']</code> is available.</td>
+<?php endif ?>
+</tr>
+
+</table>
+
+<div id="results">
+<?php if ($failed === TRUE): ?>
+<p class="fail">Kohana may not work correctly with your environment.</p>
+<?php else: ?>
+<p class="pass">Your environment passed all requirements. Remove or rename the <code>install<?php echo EXT ?></code> file now.</p>
+<?php endif ?>
+</div>
+
+</div>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/Server/system/config/cache.php b/Server/system/config/cache.php
new file mode 100644 (file)
index 0000000..ccd3da4
--- /dev/null
@@ -0,0 +1,32 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * @package  Cache
+ *
+ * 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.
+ *
+ * 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.
+ *
+ *  requests - Average number of cache requests that will processed before all expired
+ *             caches are deleted. This is commonly referred to as "garbage collection".
+ *             Setting this to 0 or a negative number will disable automatic garbage collection.
+ */
+$config['default'] = array
+(
+       'driver'   => 'file',
+       'params'   => APPPATH.'cache',
+       'lifetime' => 1800,
+       'requests' => 1000
+);
diff --git a/Server/system/config/cache_memcache.php b/Server/system/config/cache_memcache.php
new file mode 100644 (file)
index 0000000..43d8f20
--- /dev/null
@@ -0,0 +1,20 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * @package  Cache:Memcache
+ *
+ * memcache server configuration.
+ */
+$config['servers'] = array
+(
+       array
+       (
+               'host' => '127.0.0.1',
+               'port' => 11211,
+               'persistent' => FALSE,
+       )
+);
+
+/**
+ * Enable cache data compression.
+ */
+$config['compression'] = FALSE;
diff --git a/Server/system/config/cache_sqlite.php b/Server/system/config/cache_sqlite.php
new file mode 100644 (file)
index 0000000..818b893
--- /dev/null
@@ -0,0 +1,10 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * @package  Cache:SQLite
+ */
+$config['schema'] =
+'CREATE TABLE caches(
+       id VARCHAR(127) PRIMARY KEY,
+       tags VARCHAR(255),
+       expiration INTEGER,
+       cache TEXT);';
\ No newline at end of file
diff --git a/Server/system/config/cache_xcache.php b/Server/system/config/cache_xcache.php
new file mode 100644 (file)
index 0000000..47aac25
--- /dev/null
@@ -0,0 +1,12 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * @package  Cache:Xcache
+ *
+ * Xcache administrator username.
+ */
+$config['PHP_AUTH_USER'] = 'kohana';
+
+/**
+ * Xcache administrator password.
+ */
+$config['PHP_AUTH_PW'] = 'kohana';
diff --git a/Server/system/config/captcha.php b/Server/system/config/captcha.php
new file mode 100644 (file)
index 0000000..9241ec6
--- /dev/null
@@ -0,0 +1,29 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * @package  Core
+ *
+ * Captcha configuration is defined in groups which allows you to easily switch
+ * between different Captcha settings for different forms on your website.
+ * Note: all groups inherit and overwrite the default group.
+ *
+ * Group Options:
+ *  style      - Captcha type, e.g. basic, alpha, word, math, riddle
+ *  width      - Width of the Captcha image
+ *  height     - Height of the Captcha image
+ *  complexity - Difficulty level (0-10), usage depends on chosen style
+ *  background - Path to background image file
+ *  fontpath   - Path to font folder
+ *  fonts      - Font files
+ *  promote    - Valid response count threshold to promote user (FALSE to disable)
+ */
+$config['default'] = array
+(
+       'style'      => 'basic',
+       'width'      => 150,
+       'height'     => 50,
+       'complexity' => 4,
+       'background' => '',
+       'fontpath'   => SYSPATH.'fonts/',
+       'fonts'      => array('DejaVuSerif.ttf'),
+       'promote'    => FALSE,
+);
\ No newline at end of file
diff --git a/Server/system/config/cookie.php b/Server/system/config/cookie.php
new file mode 100644 (file)
index 0000000..b6ddfe4
--- /dev/null
@@ -0,0 +1,32 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * @package  Core
+ *
+ * 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.
+ */
+$config['domain'] = '';
+
+/**
+ * Restrict cookies to a specific path, typically the installation directory.
+ */
+$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.
+ */
+$config['expire'] = 0;
+
+/**
+ * Enable this option to only allow the cookie to be read when using the a
+ * secure protocol.
+ */
+$config['secure'] = FALSE;
+
+/**
+ * Enable this option to disable the cookie from being accessed when using a
+ * secure protocol. This option is only available in PHP 5.2 and above.
+ */
+$config['httponly'] = FALSE;
\ No newline at end of file
diff --git a/Server/system/config/credit_cards.php b/Server/system/config/credit_cards.php
new file mode 100644 (file)
index 0000000..bfc9842
--- /dev/null
@@ -0,0 +1,60 @@
+<?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
+ */
+$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/Server/system/config/database.php b/Server/system/config/database.php
new file mode 100644 (file)
index 0000000..6519156
--- /dev/null
@@ -0,0 +1,45 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * @package  Database
+ *
+ * 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.
+ *
+ * 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'     => TRUE,
+       'persistent'    => FALSE,
+       'connection'    => array
+       (
+               'type'     => 'mysql',
+               'user'     => 'dbuser',
+               'pass'     => 'p@ssw0rd',
+               'host'     => 'localhost',
+               'port'     => FALSE,
+               'socket'   => FALSE,
+               'database' => 'kohana'
+       ),
+       'character_set' => 'utf8',
+       'table_prefix'  => '',
+       'object'        => TRUE,
+       'cache'         => FALSE,
+       'escape'        => TRUE
+);
\ No newline at end of file
diff --git a/Server/system/config/email.php b/Server/system/config/email.php
new file mode 100644 (file)
index 0000000..c768367
--- /dev/null
@@ -0,0 +1,22 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * SwiftMailer driver, used with the email helper.
+ *
+ * @see http://www.swiftmailer.org/wikidocs/v3/connections/nativemail
+ * @see http://www.swiftmailer.org/wikidocs/v3/connections/sendmail
+ * @see http://www.swiftmailer.org/wikidocs/v3/connections/smtp
+ *
+ * Valid drivers are: native, sendmail, smtp
+ */
+$config['driver'] = 'native';
+
+/**
+ * To use secure connections with SMTP, set "port" to 465 instead of 25.
+ * To enable TLS, set "encryption" to "tls".
+ *
+ * Driver options:
+ * @param   null    native: no options
+ * @param   string  sendmail: executable path, with -bs or equivalent attached
+ * @param   array   smtp: hostname, (username), (password), (port), (auth), (encryption)
+ */
+$config['options'] = NULL;
diff --git a/Server/system/config/encryption.php b/Server/system/config/encryption.php
new file mode 100644 (file)
index 0000000..589a9de
--- /dev/null
@@ -0,0 +1,31 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * @package  Encrypt
+ *
+ * Encrypt configuration is defined in groups which allows you to easily switch
+ * between different encryption settings for different uses.
+ * Note: all groups inherit and overwrite the default group.
+ *
+ * Group Options:
+ *  key    - Encryption key used to do encryption and decryption. The default option
+ *           should never be used for a production website.
+ *
+ *           For best security, your encryption key should be at least 16 characters
+ *           long and contain letters, numbers, and symbols.
+ *           @note 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.
+ *           @see http://php.net/mcrypt
+ *
+ *  cipher - MCrypt encryption cipher. By default, the MCRYPT_RIJNDAEL_128 cipher is used.
+ *           This is also known as 128-bit AES.
+ *           @see 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/Server/system/config/http.php b/Server/system/config/http.php
new file mode 100644 (file)
index 0000000..3c4a86a
--- /dev/null
@@ -0,0 +1,19 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+// 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/Server/system/config/image.php b/Server/system/config/image.php
new file mode 100644 (file)
index 0000000..e38b7cb
--- /dev/null
@@ -0,0 +1,13 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * @package  Image
+ *
+ * Driver name. 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/Server/system/config/inflector.php b/Server/system/config/inflector.php
new file mode 100644 (file)
index 0000000..6dcfc2d
--- /dev/null
@@ -0,0 +1,58 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+$config['uncountable'] = array
+(
+       'access',
+       'advice',
+       'art',
+       'baggage',
+       'dances',
+       'equipment',
+       'fish',
+       'fuel',
+       'furniture',
+       'food',
+       'heat',
+       'honey',
+       'homework',
+       'impatience',
+       'information',
+       'knowledge',
+       'luggage',
+       '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/Server/system/config/locale.php b/Server/system/config/locale.php
new file mode 100644 (file)
index 0000000..3a26882
--- /dev/null
@@ -0,0 +1,16 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * @package  Core
+ *
+ * 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 use the server timezone.
+ * @see http://php.net/timezones
+ */
+$config['timezone'] = '';
\ No newline at end of file
diff --git a/Server/system/config/mimes.php b/Server/system/config/mimes.php
new file mode 100644 (file)
index 0000000..6a960f2
--- /dev/null
@@ -0,0 +1,224 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * @package  Core
+ *
+ * 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://kohanaphp.com/trac/newticket. Be sure to give the filename and
+ * expected MIME type, as well as any additional information you can provide.
+ */
+$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/Server/system/config/pagination.php b/Server/system/config/pagination.php
new file mode 100644 (file)
index 0000000..808fc31
--- /dev/null
@@ -0,0 +1,25 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * @package  Pagination
+ *
+ * Pagination configuration is defined in groups which allows you to easily switch
+ * between different pagination settings for different website sections.
+ * Note: all groups inherit and overwrite the default group.
+ *
+ * Group Options:
+ *  directory      - Views folder in which your pagination style templates reside
+ *  style          - Pagination style template (matches view filename)
+ *  uri_segment    - URI segment (int or 'label') in which the current page number can be found
+ *  query_string   - Alternative to uri_segment: query string key that contains the page number
+ *  items_per_page - Number of items to display per page
+ *  auto_hide      - Automatically hides pagination for single pages
+ */
+$config['default'] = array
+(
+       'directory'      => 'pagination',
+       'style'          => 'classic',
+       'uri_segment'    => 3,
+       'query_string'   => '',
+       'items_per_page' => 20,
+       'auto_hide'      => FALSE,
+);
diff --git a/Server/system/config/profiler.php b/Server/system/config/profiler.php
new file mode 100644 (file)
index 0000000..98ab5a4
--- /dev/null
@@ -0,0 +1,8 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * @package  Profiler
+ *
+ * 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.
+ */
+$config['show'] = TRUE;
diff --git a/Server/system/config/routes.php b/Server/system/config/routes.php
new file mode 100644 (file)
index 0000000..c677fde
--- /dev/null
@@ -0,0 +1,7 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * @package  Core
+ *
+ * Sets the default route to "welcome"
+ */
+$config['_default'] = 'welcome';
diff --git a/Server/system/config/session.php b/Server/system/config/session.php
new file mode 100644 (file)
index 0000000..e287bae
--- /dev/null
@@ -0,0 +1,47 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * @package Session
+ *
+ * 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.
+ * Note: the cookie driver always encrypts session data. Set to TRUE for stronger encryption.
+ */
+$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.
+ */
+$config['regenerate'] = 3;
+
+/**
+ * Percentage probability that the gc (garbage collection) routine is started.
+ */
+$config['gc_probability'] = 2;
\ No newline at end of file
diff --git a/Server/system/config/sql_types.php b/Server/system/config/sql_types.php
new file mode 100644 (file)
index 0000000..4034c6f
--- /dev/null
@@ -0,0 +1,58 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * @package  Database
+ *
+ * SQL data types. If there are missing values, please report them:
+ *
+ * @link  http://trac.kohanaphp.com/newticket
+ */
+$config = array
+(
+       'tinyint'                       => array('type' => 'int', 'max' => 127),
+       'smallint'                      => array('type' => 'int', 'max' => 32767),
+       'mediumint'                     => array('type' => 'int', 'max' => 8388607),
+       'int'                           => array('type' => 'int', 'max' => 2147483647),
+       'integer'                       => array('type' => 'int', 'max' => 2147483647),
+       'bigint'                        => array('type' => 'int', 'max' => 9223372036854775807),
+       'float'                         => array('type' => 'float'),
+       'float unsigned'        => array('type' => 'float', 'min' => 0),
+       'boolean'                       => array('type' => 'boolean'),
+       'time'                          => array('type' => 'string', 'format' => '00:00:00'),
+       'time with time zone' => array('type' => 'string'),
+       'date'                          => array('type' => 'string', 'format' => '0000-00-00'),
+       'year'                          => array('type' => 'string', 'format' => '0000'),
+       'datetime'                      => array('type' => 'string', 'format' => '0000-00-00 00:00:00'),
+       'timestamp with time zone' => array('type' => 'string'),
+       'char'                          => array('type' => 'string', 'exact' => TRUE),
+       'binary'                        => array('type' => 'string', 'binary' => TRUE, 'exact' => TRUE),
+       'varchar'                       => array('type' => 'string'),
+       'varbinary'                     => array('type' => 'string', 'binary' => TRUE),
+       'blob'                          => array('type' => 'string', 'binary' => TRUE),
+       'text'                          => array('type' => 'string')
+);
+
+// DOUBLE
+$config['double'] = $config['double precision'] = $config['decimal'] = $config['real'] = $config['numeric'] = $config['float'];
+$config['double unsigned'] = $config['float unsigned'];
+
+// BIT
+$config['bit'] = $config['boolean'];
+
+// TIMESTAMP
+$config['timestamp'] = $config['timestamp without time zone'] = $config['datetime'];
+
+// ENUM
+$config['enum'] = $config['set'] = $config['varchar'];
+
+// TEXT
+$config['tinytext'] = $config['mediumtext'] = $config['longtext'] = $config['text'];
+
+// BLOB
+$config['tsvector'] = $config['tinyblob'] = $config['mediumblob'] = $config['longblob'] = $config['clob'] = $config['bytea'] = $config['blob'];
+
+// CHARACTER
+$config['character'] = $config['char'];
+$config['character varying'] = $config['varchar'];
+
+// TIME
+$config['time without time zone'] = $config['time'];
diff --git a/Server/system/config/upload.php b/Server/system/config/upload.php
new file mode 100644 (file)
index 0000000..df26a2d
--- /dev/null
@@ -0,0 +1,17 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * @package  Core
+ *
+ * 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/Server/system/config/user_agents.php b/Server/system/config/user_agents.php
new file mode 100644 (file)
index 0000000..c968aeb
--- /dev/null
@@ -0,0 +1,112 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * @package  Core
+ *
+ * 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.
+ */
+$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/Server/system/config/view.php b/Server/system/config/view.php
new file mode 100644 (file)
index 0000000..6bed22e
--- /dev/null
@@ -0,0 +1,17 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * @package  Core
+ *
+ * Allowed non-php view types. Most file extensions are supported.
+ */
+$config['allowed_filetypes'] = array
+(
+       'gif',
+       'jpg', 'jpeg',
+       'png',
+       'tif', 'tiff',
+       'swf',
+       'htm', 'html',
+       'css',
+       'js'
+);
diff --git a/Server/system/controllers/captcha.php b/Server/system/controllers/captcha.php
new file mode 100644 (file)
index 0000000..a689e69
--- /dev/null
@@ -0,0 +1,23 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Outputs the dynamic Captcha resource.
+ * Usage: Call the Captcha controller from a view, e.g.
+ *        <img src="<?php echo url::site('captcha') ?>" />
+ *
+ * $Id: captcha.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package    Captcha
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Captcha_Controller extends Controller {
+
+       public function __call($method, $args)
+       {
+               // Output the Captcha challenge resource (no html)
+               // Pull the config group name from the URL
+               Captcha::factory($this->uri->segment(2))->render(FALSE);
+       }
+
+} // End Captcha_Controller
\ No newline at end of file
diff --git a/Server/system/controllers/template.php b/Server/system/controllers/template.php
new file mode 100644 (file)
index 0000000..34d1a22
--- /dev/null
@@ -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 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+abstract class 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/Server/system/core/Benchmark.php b/Server/system/core/Benchmark.php
new file mode 100644 (file)
index 0000000..ce230f1
--- /dev/null
@@ -0,0 +1,125 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Simple benchmarking.
+ *
+ * $Id: Benchmark.php 4149 2009-04-01 13:32:50Z Shadowhand $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+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]))
+               {
+                       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/Server/system/core/Bootstrap.php b/Server/system/core/Bootstrap.php
new file mode 100644 (file)
index 0000000..1334190
--- /dev/null
@@ -0,0 +1,58 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Kohana process control file, loaded by the front controller.
+ * 
+ * $Id: Bootstrap.php 4409 2009-06-06 00:48:26Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+
+define('KOHANA_VERSION',  '2.3.4');
+define('KOHANA_CODENAME', 'buteo regalis');
+
+// Test of Kohana is running in Windows
+define('KOHANA_IS_WIN', DIRECTORY_SEPARATOR === '\\');
+
+// Kohana benchmarks are prefixed to prevent collisions
+define('SYSTEM_BENCHMARK', 'system_benchmark');
+
+// Load benchmarking support
+require SYSPATH.'core/Benchmark'.EXT;
+
+// Start total_execution
+Benchmark::start(SYSTEM_BENCHMARK.'_total_execution');
+
+// Start kohana_loading
+Benchmark::start(SYSTEM_BENCHMARK.'_kohana_loading');
+
+// Load core files
+require SYSPATH.'core/utf8'.EXT;
+require SYSPATH.'core/Event'.EXT;
+require SYSPATH.'core/Kohana'.EXT;
+
+// Prepare the environment
+Kohana::setup();
+
+// End kohana_loading
+Benchmark::stop(SYSTEM_BENCHMARK.'_kohana_loading');
+
+// Start system_initialization
+Benchmark::start(SYSTEM_BENCHMARK.'_system_initialization');
+
+// Prepare the system
+Event::run('system.ready');
+
+// Determine routing
+Event::run('system.routing');
+
+// End system_initialization
+Benchmark::stop(SYSTEM_BENCHMARK.'_system_initialization');
+
+// Make the magic happen!
+Event::run('system.execute');
+
+// Clean up and exit
+Event::run('system.shutdown');
diff --git a/Server/system/core/Event.php b/Server/system/core/Event.php
new file mode 100644 (file)
index 0000000..06468a8
--- /dev/null
@@ -0,0 +1,232 @@
+<?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.
+ *
+ * $Id: Event.php 4390 2009-06-04 03:05:36Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ * @link       http://docs.kohanaphp.com/general/events
+ */
+final class Event {
+
+       // Event callbacks
+       private static $events = array();
+
+       // Cache of events that have been run
+       private 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
+        * @return  boolean
+        */
+       public static function add($name, $callback)
+       {
+               if ( ! isset(self::$events[$name]))
+               {
+                       // Create an empty event if it is not yet defined
+                       self::$events[$name] = array();
+               }
+               elseif (in_array($callback, self::$events[$name], TRUE))
+               {
+                       // The event already exists
+                       return FALSE;
+               }
+
+               // Add the event
+               self::$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(self::$events[$name]) OR ($key = array_search($existing, self::$events[$name])) === FALSE)
+               {
+                       // Just add the event if there are no events
+                       return self::add($name, $callback);
+               }
+               else
+               {
+                       // Insert the event immediately before the existing event
+                       return self::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(self::$events[$name]) OR ($key = array_search($existing, self::$events[$name])) === FALSE)
+               {
+                       // Just add the event if there are no events
+                       return self::add($name, $callback);
+               }
+               else
+               {
+                       // Insert the event immediately after the existing event
+                       return self::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, self::$events[$name], TRUE))
+                       return FALSE;
+
+               // Add the new event at the given key location
+               self::$events[$name] = array_merge
+               (
+                       // Events before the key
+                       array_slice(self::$events[$name], 0, $key),
+                       // New event callback
+                       array($callback),
+                       // Events after the key
+                       array_slice(self::$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(self::$events[$name]) OR ($key = array_search($existing, self::$events[$name], TRUE)) === FALSE)
+                       return FALSE;
+
+               if ( ! in_array($callback, self::$events[$name], TRUE))
+               {
+                       // Replace the exisiting event with the new event
+                       self::$events[$name][$key] = $callback;
+               }
+               else
+               {
+                       // Remove the existing event from the queue
+                       unset(self::$events[$name][$key]);
+
+                       // Reset the array so the keys are ordered properly
+                       self::$events[$name] = array_values(self::$events[$name]);
+               }
+
+               return TRUE;
+       }
+
+       /**
+        * Get all callbacks for an event.
+        *
+        * @param   string  event name
+        * @return  array
+        */
+       public static function get($name)
+       {
+               return empty(self::$events[$name]) ? array() : self::$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)
+               {
+                       self::$events[$name] = array();
+               }
+               elseif (isset(self::$events[$name]))
+               {
+                       // Loop through each of the event callbacks and compare it to the
+                       // callback requested for removal. The callback is removed if it
+                       // matches.
+                       foreach (self::$events[$name] as $i => $event_callback)
+                       {
+                               if ($callback === $event_callback)
+                               {
+                                       unset(self::$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(self::$events[$name]))
+               {
+                       // So callbacks can access Event::$data
+                       self::$data =& $data;
+                       $callbacks  =  self::get($name);
+
+                       foreach ($callbacks as $callback)
+                       {
+                               call_user_func($callback);
+                       }
+
+                       // Do this to prevent data from getting 'stuck'
+                       $clear_data = '';
+                       self::$data =& $clear_data;
+               }
+
+               // The event has been run!
+               self::$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(self::$has_run[$name]);
+       }
+
+} // End Event
\ No newline at end of file
diff --git a/Server/system/core/Kohana.php b/Server/system/core/Kohana.php
new file mode 100644 (file)
index 0000000..c934b12
--- /dev/null
@@ -0,0 +1,1788 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Provides Kohana-specific helper functions. This is where the magic happens!
+ *
+ * $Id: Kohana.php 4372 2009-05-28 17:00:34Z ixmatus $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+final class Kohana {
+
+       // The singleton instance of the controller
+       public static $instance;
+
+       // Output buffering level
+       private static $buffer_level;
+
+       // Will be set to TRUE when an exception is caught
+       public static $has_error = FALSE;
+
+       // The final output that will displayed by Kohana
+       public static $output = '';
+
+       // The current user agent
+       public static $user_agent;
+
+       // The current locale
+       public static $locale;
+
+       // Configuration
+       private static $configuration;
+
+       // Include paths
+       private static $include_paths;
+
+       // Logged messages
+       private static $log;
+
+       // Cache lifetime
+       private static $cache_lifetime;
+
+       // Log levels
+       private static $log_levels = array
+       (
+               'error' => 1,
+               'alert' => 2,
+               'info'  => 3,
+               'debug' => 4,
+       );
+
+       // Internal caches and write status
+       private static $internal_cache = array();
+       private static $write_cache;
+       private static $internal_cache_path;
+       private static $internal_cache_key;
+       private static $internal_cache_encrypt;
+
+       /**
+        * 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;
+
+               // This function can only be run once
+               if ($run === TRUE)
+                       return;
+
+               // 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);
+
+               if (self::$cache_lifetime = self::config('core.internal_cache'))
+               {
+                       // Are we using encryption for caches?
+                       self::$internal_cache_encrypt   = self::config('core.internal_cache_encrypt');
+                       
+                       if(self::$internal_cache_encrypt===TRUE)
+                       {
+                               self::$internal_cache_key = self::config('core.internal_cache_key');
+                               
+                               // Be sure the key is of acceptable length for the mcrypt algorithm used
+                               self::$internal_cache_key = substr(self::$internal_cache_key, 0, 24);
+                       }
+                       
+                       // Set the directory to be used for the internal cache
+                       if ( ! self::$internal_cache_path = self::config('core.internal_cache_path'))
+                       {
+                               self::$internal_cache_path = APPPATH.'cache/';
+                       }
+
+                       // Load cached configuration and language files
+                       self::$internal_cache['configuration'] = self::cache('configuration', self::$cache_lifetime);
+                       self::$internal_cache['language']      = self::cache('language', self::$cache_lifetime);
+
+                       // Load cached file paths
+                       self::$internal_cache['find_file_paths'] = self::cache('find_file_paths', self::$cache_lifetime);
+
+                       // Enable cache saving
+                       Event::add('system.shutdown', array(__CLASS__, 'internal_cache_save'));
+               }
+
+               // Disable notices and "strict" errors
+               $ER = error_reporting(~E_NOTICE & ~E_STRICT);
+
+               // Set the user agent
+               self::$user_agent = ( ! empty($_SERVER['HTTP_USER_AGENT']) ? trim($_SERVER['HTTP_USER_AGENT']) : '');
+
+               if (function_exists('date_default_timezone_set'))
+               {
+                       $timezone = self::config('locale.timezone');
+
+                       // Set default timezone, due to increased validation of date settings
+                       // which cause massive amounts of E_NOTICEs to be generated in PHP 5.2+
+                       date_default_timezone_set(empty($timezone) ? date_default_timezone_get() : $timezone);
+               }
+
+               // Restore error reporting
+               error_reporting($ER);
+
+               // Start output buffering
+               ob_start(array(__CLASS__, 'output_buffer'));
+
+               // Save buffering level
+               self::$buffer_level = ob_get_level();
+
+               // Set autoloader
+               spl_autoload_register(array('Kohana', 'auto_load'));
+
+               // Set error handler
+               set_error_handler(array('Kohana', 'exception_handler'));
+
+               // Set exception handler
+               set_exception_handler(array('Kohana', 'exception_handler'));
+
+               // Send default text/html UTF-8 header
+               header('Content-Type: text/html; charset=UTF-8');
+
+               // Load locales
+               $locales = self::config('locale.language');
+
+               // Make first locale UTF-8
+               $locales[0] .= '.UTF-8';
+
+               // Set locale information
+               self::$locale = setlocale(LC_ALL, $locales);
+
+               if (self::$configuration['core']['log_threshold'] > 0)
+               {
+                       // Set the log directory
+                       self::log_directory(self::$configuration['core']['log_directory']);
+
+                       // Enable log writing at shutdown
+                       register_shutdown_function(array(__CLASS__, 'log_save'));
+               }
+
+               // 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', 'show_404'));
+
+               // Enable Kohana output handling
+               Event::add('system.shutdown', array('Kohana', 'shutdown'));
+
+               if (self::config('core.enable_hooks') === TRUE)
+               {
+                       // Find all the hook files
+                       $hooks = self::list_files('hooks', TRUE);
+
+                       foreach ($hooks as $file)
+                       {
+                               // Load the hook
+                               include $file;
+                       }
+               }
+
+               // Setup is complete, prevent it from being run again
+               $run = TRUE;
+
+               // Stop the environment setup routine
+               Benchmark::stop(SYSTEM_BENCHMARK.'_environment_setup');
+       }
+
+       /**
+        * 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 (self::$instance === NULL)
+               {
+                       Benchmark::start(SYSTEM_BENCHMARK.'_controller_setup');
+
+                       // Include the Controller file
+                       require 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 self::$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
+                       self::$include_paths = array(APPPATH);
+
+                       foreach (self::$configuration['core']['modules'] as $path)
+                       {
+                               if ($path = str_replace('\\', '/', realpath($path)))
+                               {
+                                       // Add a valid path
+                                       self::$include_paths[] = $path.'/';
+                               }
+                       }
+
+                       // Add SYSPATH as the last path
+                       self::$include_paths[] = SYSPATH;
+               }
+
+               return self::$include_paths;
+       }
+
+       /**
+        * Get a config item or group.
+        *
+        * @param   string   item name
+        * @param   boolean  force a forward slash (/) at the end of the item
+        * @param   boolean  is the item required?
+        * @return  mixed
+        */
+       public static function config($key, $slash = FALSE, $required = TRUE)
+       {
+               if (self::$configuration === NULL)
+               {
+                       // Load core configuration
+                       self::$configuration['core'] = self::config_load('core');
+
+                       // Re-parse the include paths
+                       self::include_paths(TRUE);
+               }
+
+               // Get the group name from the key
+               $group = explode('.', $key, 2);
+               $group = $group[0];
+
+               if ( ! isset(self::$configuration[$group]))
+               {
+                       // Load the configuration group
+                       self::$configuration[$group] = self::config_load($group, $required);
+               }
+
+               // Get the value of the key string
+               $value = self::key_string(self::$configuration, $key);
+
+               if ($slash === TRUE AND is_string($value) AND $value !== '')
+               {
+                       // Force the value to end with "/"
+                       $value = rtrim($value, '/').'/';
+               }
+
+               return $value;
+       }
+
+       /**
+        * Sets a configuration item, if allowed.
+        *
+        * @param   string   config key string
+        * @param   string   config value
+        * @return  boolean
+        */
+       public static function config_set($key, $value)
+       {
+               // Do this to make sure that the config array is already loaded
+               self::config($key);
+
+               if (substr($key, 0, 7) === 'routes.')
+               {
+                       // Routes cannot contain sub keys due to possible dots in regex
+                       $keys = explode('.', $key, 2);
+               }
+               else
+               {
+                       // Convert dot-noted key string to an array
+                       $keys = explode('.', $key);
+               }
+
+               // Used for recursion
+               $conf =& self::$configuration;
+               $last = count($keys) - 1;
+
+               foreach ($keys as $i => $k)
+               {
+                       if ($i === $last)
+                       {
+                               $conf[$k] = $value;
+                       }
+                       else
+                       {
+                               $conf =& $conf[$k];
+                       }
+               }
+
+               if ($key === 'core.modules')
+               {
+                       // Reprocess the include paths
+                       self::include_paths(TRUE);
+               }
+
+               return TRUE;
+       }
+
+       /**
+        * Load a config file.
+        *
+        * @param   string   config filename, without extension
+        * @param   boolean  is the file required?
+        * @return  array
+        */
+       public static function config_load($name, $required = TRUE)
+       {
+               if ($name === 'core')
+               {
+                       // Load the application configuration file
+                       require APPPATH.'config/config'.EXT;
+
+                       if ( ! isset($config['site_domain']))
+                       {
+                               // Invalid config file
+                               die('Your Kohana application configuration file is not valid.');
+                       }
+
+                       return $config;
+               }
+
+               if (isset(self::$internal_cache['configuration'][$name]))
+                       return self::$internal_cache['configuration'][$name];
+
+               // Load matching configs
+               $configuration = array();
+
+               if ($files = self::find_file('config', $name, $required))
+               {
+                       foreach ($files as $file)
+                       {
+                               require $file;
+
+                               if (isset($config) AND is_array($config))
+                               {
+                                       // Merge in configuration
+                                       $configuration = array_merge($configuration, $config);
+                               }
+                       }
+               }
+
+               if ( ! isset(self::$write_cache['configuration']))
+               {
+                       // Cache has changed
+                       self::$write_cache['configuration'] = TRUE;
+               }
+
+               return self::$internal_cache['configuration'][$name] = $configuration;
+       }
+
+       /**
+        * Clears a config group from the cached configuration.
+        *
+        * @param   string  config group
+        * @return  void
+        */
+       public static function config_clear($group)
+       {
+               // Remove the group from config
+               unset(self::$configuration[$group], self::$internal_cache['configuration'][$group]);
+
+               if ( ! isset(self::$write_cache['configuration']))
+               {
+                       // Cache has changed
+                       self::$write_cache['configuration'] = TRUE;
+               }
+       }
+
+       /**
+        * Add a new message to the log.
+        *
+        * @param   string  type of message
+        * @param   string  message text
+        * @return  void
+        */
+       public static function log($type, $message)
+       {
+               if (self::$log_levels[$type] <= self::$configuration['core']['log_threshold'])
+               {
+                       $message = array(date('Y-m-d H:i:s P'), $type, $message);
+
+                       // Run the system.log event
+                       Event::run('system.log', $message);
+
+                       self::$log[] = $message;
+               }
+       }
+
+       /**
+        * Save all currently logged messages.
+        *
+        * @return  void
+        */
+       public static function log_save()
+       {
+               if (empty(self::$log) OR self::$configuration['core']['log_threshold'] < 1)
+                       return;
+
+               // Filename of the log
+               $filename = self::log_directory().date('Y-m-d').'.log'.EXT;
+
+               if ( ! is_file($filename))
+               {
+                       // Write the SYSPATH checking header
+                       file_put_contents($filename,
+                               '<?php defined(\'SYSPATH\') or die(\'No direct script access.\'); ?>'.PHP_EOL.PHP_EOL);
+
+                       // Prevent external writes
+                       chmod($filename, 0644);
+               }
+
+               // Messages to write
+               $messages = array();
+
+               do
+               {
+                       // Load the next mess
+                       list ($date, $type, $text) = array_shift(self::$log);
+
+                       // Add a new message line
+                       $messages[] = $date.' --- '.$type.': '.$text;
+               }
+               while ( ! empty(self::$log));
+
+               // Write messages to log file
+               file_put_contents($filename, implode(PHP_EOL, $messages).PHP_EOL, FILE_APPEND);
+       }
+
+       /**
+        * Get or set the logging directory.
+        *
+        * @param   string  new log directory
+        * @return  string
+        */
+       public static function log_directory($dir = NULL)
+       {
+               static $directory;
+
+               if ( ! empty($dir))
+               {
+                       // Get the directory path
+                       $dir = realpath($dir);
+
+                       if (is_dir($dir) AND is_writable($dir))
+                       {
+                               // Change the log directory
+                               $directory = str_replace('\\', '/', $dir).'/';
+                       }
+                       else
+                       {
+                               // Log directory is invalid
+                               throw new Kohana_Exception('core.log_dir_unwritable', $dir);
+                       }
+               }
+
+               return $directory;
+       }
+
+       /**
+        * 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 = self::$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(self::$internal_cache_encrypt===TRUE)
+                                       {
+                                               $data           = file_get_contents($path);
+                                               
+                                               $iv_size        = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
+                                               $iv                     = mcrypt_create_iv($iv_size, MCRYPT_RAND);
+                                               
+                                               $decrypted_text = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, self::$internal_cache_key, $data, MCRYPT_MODE_ECB, $iv);
+                                               
+                                               $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 = self::$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(self::$internal_cache_encrypt===TRUE)
+                       {
+                               // Encrypt and write data to cache file
+                               $iv_size        = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
+                               $iv                     = mcrypt_create_iv($iv_size, MCRYPT_RAND);
+                               
+                               // Serialize and encrypt!
+                               $encrypted_text = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, self::$internal_cache_key, serialize($data), MCRYPT_MODE_ECB, $iv);
+                               
+                               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');
+               }
+               
+               self::$output   = $output;
+               
+               // Set and return the final output
+               return self::$output;
+       }
+
+       /**
+        * Closes all open output buffers, either by flushing or cleaning, and stores the Kohana
+        * 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() >= self::$buffer_level)
+               {
+                       // Set the close function
+                       $close = ($flush === TRUE) ? 'ob_end_flush' : 'ob_end_clean';
+
+                       while (ob_get_level() > self::$buffer_level)
+                       {
+                               // Flush or clean the buffer
+                               $close();
+                       }
+
+                       // Store the Kohana output buffer
+                       ob_end_clean();
+               }
+       }
+
+       /**
+        * Triggers the shutdown of Kohana by closing the output buffer, runs the system.display event.
+        *
+        * @return  void
+        */
+       public static function shutdown()
+       {
+               // Close output buffers
+               self::close_buffers(TRUE);
+
+               // Run the output event
+               Event::run('system.display', self::$output);
+
+               // Render the final output
+               self::render(self::$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 (self::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 = self::config('core.output_compression') AND ini_get('output_handler') !== 'ob_gzhandler' AND (int) ini_get('zlib.output_compression') === 0)
+               {
+                       if ($level < 1 OR $level > 9)
+                       {
+                               // Normalize the level to be an integer between 1 and 9. This
+                               // step must be done to prevent gzencode from triggering an error
+                               $level = max(1, min($level, 9));
+                       }
+
+                       if (stripos(@$_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE)
+                       {
+                               $compress = 'gzip';
+                       }
+                       elseif (stripos(@$_SERVER['HTTP_ACCEPT_ENCODING'], 'deflate') !== FALSE)
+                       {
+                               $compress = 'deflate';
+                       }
+               }
+
+               if (isset($compress) AND $level > 0)
+               {
+                       switch ($compress)
+                       {
+                               case 'gzip':
+                                       // Compress output using gzip
+                                       $output = gzencode($output, $level);
+                               break;
+                               case 'deflate':
+                                       // Compress output using zlib (HTTP deflate)
+                                       $output = gzdeflate($output, $level);
+                               break;
+                       }
+
+                       // This header must be sent with compressed content to prevent
+                       // browser caches from breaking
+                       header('Vary: Accept-Encoding');
+
+                       // Send the content encoding header
+                       header('Content-Encoding: '.$compress);
+
+                       // Sending Content-Length in CGI can result in unexpected behavior
+                       if (stripos(PHP_SAPI, 'cgi') === FALSE)
+                       {
+                               header('Content-Length: '.strlen($output));
+                       }
+               }
+
+               echo $output;
+       }
+
+       /**
+        * Displays a 404 page.
+        *
+        * @throws  Kohana_404_Exception
+        * @param   string  URI of page
+        * @param   string  custom template
+        * @return  void
+        */
+       public static function show_404($page = FALSE, $template = FALSE)
+       {
+               throw new Kohana_404_Exception($page, $template);
+       }
+
+       /**
+        * Dual-purpose PHP error and exception handler. Uses the kohana_error_page
+        * view to display the message.
+        *
+        * @param   integer|object  exception object or error code
+        * @param   string          error message
+        * @param   string          filename
+        * @param   integer         line number
+        * @return  void
+        */
+       public static function exception_handler($exception, $message = NULL, $file = NULL, $line = NULL)
+       {
+               try
+               {
+                       // PHP errors have 5 args, always
+                       $PHP_ERROR = (func_num_args() === 5);
+       
+                       // Test to see if errors should be displayed
+                       if ($PHP_ERROR AND (error_reporting() & $exception) === 0)
+                               return;
+       
+                       // This is useful for hooks to determine if a page has an error
+                       self::$has_error = TRUE;
+       
+                       // Error handling will use exactly 5 args, every time
+                       if ($PHP_ERROR)
+                       {
+                               $code     = $exception;
+                               $type     = 'PHP Error';
+                               $template = 'kohana_error_page';
+                       }
+                       else
+                       {
+                               $code     = $exception->getCode();
+                               $type     = get_class($exception);
+                               $message  = $exception->getMessage();
+                               $file     = $exception->getFile();
+                               $line     = $exception->getLine();
+                               $template = ($exception instanceof Kohana_Exception) ? $exception->getTemplate() : 'kohana_error_page';
+                       }
+       
+                       if (is_numeric($code))
+                       {
+                               $codes = self::lang('errors');
+       
+                               if ( ! empty($codes[$code]))
+                               {
+                                       list($level, $error, $description) = $codes[$code];
+                               }
+                               else
+                               {
+                                       $level = 1;
+                                       $error = $PHP_ERROR ? 'Unknown Error' : get_class($exception);
+                                       $description = '';
+                               }
+                       }
+                       else
+                       {
+                               // Custom error message, this will never be logged
+                               $level = 5;
+                               $error = $code;
+                               $description = '';
+                       }
+       
+                       // Remove the DOCROOT from the path, as a security precaution
+                       $file = str_replace('\\', '/', realpath($file));
+                       $file = preg_replace('|^'.preg_quote(DOCROOT).'|', '', $file);
+       
+                       if ($level <= self::$configuration['core']['log_threshold'])
+                       {
+                               // Log the error
+                               self::log('error', self::lang('core.uncaught_exception', $type, $message, $file, $line));
+                       }
+       
+                       if ($PHP_ERROR)
+                       {
+                               $description = self::lang('errors.'.E_RECOVERABLE_ERROR);
+                               $description = is_array($description) ? $description[2] : '';
+       
+                               if ( ! headers_sent())
+                               {
+                                       // Send the 500 header
+                                       header('HTTP/1.1 500 Internal Server Error');
+                               }
+                       }
+                       else
+                       {
+                               if (method_exists($exception, 'sendHeaders') AND ! headers_sent())
+                               {
+                                       // Send the headers if they have not already been sent
+                                       $exception->sendHeaders();
+                               }
+                       }
+       
+                       // Close all output buffers except for Kohana
+                       while (ob_get_level() > self::$buffer_level)
+                       {
+                               ob_end_clean();
+                       }
+       
+                       // Test if display_errors is on
+                       if (self::$configuration['core']['display_errors'] === TRUE)
+                       {
+                               if ( ! IN_PRODUCTION AND $line != FALSE)
+                               {
+                                       // Remove the first entry of debug_backtrace(), it is the exception_handler call
+                                       $trace = $PHP_ERROR ? array_slice(debug_backtrace(), 1) : $exception->getTrace();
+       
+                                       // Beautify backtrace
+                                       $trace = self::backtrace($trace);
+                               }
+       
+                               // Load the error
+                               require self::find_file('views', empty($template) ? 'kohana_error_page' : $template);
+                       }
+                       else
+                       {
+                               // Get the i18n messages
+                               $error   = self::lang('core.generic_error');
+                               $message = self::lang('core.errors_disabled', url::site(), url::site(Router::$current_uri));
+       
+                               // Load the errors_disabled view
+                               require self::find_file('views', 'kohana_error_disabled');
+                       }
+       
+                       if ( ! Event::has_run('system.shutdown'))
+                       {
+                               // Run the shutdown even to ensure a clean exit
+                               Event::run('system.shutdown');
+                       }
+       
+                       // Turn off error reporting
+                       error_reporting(0);
+                       exit;
+               }
+               catch (Exception $e)
+               {
+                       if (IN_PRODUCTION)
+                       {
+                               die('Fatal Error');
+                       }
+                       else
+                       {
+                               die('Fatal Error: '.$e->getMessage().' File: '.$e->getFile().' Line: '.$e->getLine());
+                       }
+               }
+       }
+
+       /**
+        * 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))
+                       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 = self::find_file($type, $file))
+               {
+                       // Load the class
+                       require $filename;
+               }
+               else
+               {
+                       // The class could not be found
+                       return FALSE;
+               }
+
+               if ($filename = self::find_file($type, self::$configuration['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(self::$internal_cache['find_file_paths'][$search]))
+                       return self::$internal_cache['find_file_paths'][$search];
+
+               // Load include paths
+               $paths = self::$include_paths;
+
+               // Nothing found, yet
+               $found = NULL;
+
+               if ($directory === 'config' 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)
+                       {
+                               // Directory i18n key
+                               $directory = 'core.'.inflector::singular($directory);
+
+                               // If the file is required, throw an exception
+                               throw new Kohana_Exception('core.resource_not_found', self::lang($directory), $filename);
+                       }
+                       else
+                       {
+                               // Nothing was found, return FALSE
+                               $found = FALSE;
+                       }
+               }
+
+               if ( ! isset(self::$write_cache['find_file_paths']))
+               {
+                       // Write cache at shutdown
+                       self::$write_cache['find_file_paths'] = TRUE;
+               }
+
+               return self::$internal_cache['find_file_paths'][$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   full path to search (used for recursion, *never* set this manually)
+        * @return  array    filenames and directories
+        */
+       public static function list_files($directory, $recursive = FALSE, $path = FALSE)
+       {
+               $files = array();
+
+               if ($path === FALSE)
+               {
+                       $paths = array_reverse(self::include_paths());
+
+                       foreach ($paths as $path)
+                       {
+                               // Recursively get and merge all files
+                               $files = array_merge($files, self::list_files($directory, $recursive, $path.$directory));
+                       }
+               }
+               else
+               {
+                       $path = rtrim($path, '/').'/';
+
+                       if (is_readable($path))
+                       {
+                               $items = (array) glob($path.'*');
+
+                               if ( ! empty($items))
+                               {
+                                       foreach ($items as $index => $item)
+                                       {
+                                               $files[] = $item = str_replace('\\', '/', $item);
+
+                                               // Handle recursion
+                                               if (is_dir($item) AND $recursive == TRUE)
+                                               {
+                                                       // Filename should only be the basename
+                                                       $item = pathinfo($item, PATHINFO_BASENAME);
+
+                                                       // Append sub-directory search
+                                                       $files = array_merge($files, self::list_files($directory, TRUE, $path.$item));
+                                               }
+                                       }
+                               }
+                       }
+               }
+
+               return $files;
+       }
+
+       /**
+        * Fetch an i18n language item.
+        *
+        * @param   string  language key to fetch
+        * @param   array   additional information to insert into the line
+        * @return  string  i18n language string, or the requested key if the i18n item is not found
+        */
+       public static function lang($key, $args = array())
+       {
+               // Extract the main group from the key
+               $group = explode('.', $key, 2);
+               $group = $group[0];
+
+               // Get locale name
+               $locale = self::config('locale.language.0');
+
+               if ( ! isset(self::$internal_cache['language'][$locale][$group]))
+               {
+                       // Messages for this group
+                       $messages = array();
+
+                       if ($files = self::find_file('i18n', $locale.'/'.$group))
+                       {
+                               foreach ($files as $file)
+                               {
+                                       include $file;
+
+                                       // Merge in configuration
+                                       if ( ! empty($lang) AND is_array($lang))
+                                       {
+                                               foreach ($lang as $k => $v)
+                                               {
+                                                       $messages[$k] = $v;
+                                               }
+                                       }
+                               }
+                       }
+
+                       if ( ! isset(self::$write_cache['language']))
+                       {
+                               // Write language cache
+                               self::$write_cache['language'] = TRUE;
+                       }
+
+                       self::$internal_cache['language'][$locale][$group] = $messages;
+               }
+
+               // Get the line from cache
+               $line = self::key_string(self::$internal_cache['language'][$locale], $key);
+
+               if ($line === NULL)
+               {
+                       self::log('error', 'Missing i18n entry '.$key.' for language '.$locale);
+
+                       // 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);
+               }
+       }
+
+       /**
+        * Retrieves current user agent information:
+        * keys:  browser, version, platform, mobile, robot, referrer, languages, charsets
+        * tests: is_browser, is_mobile, is_robot, accept_lang, accept_charset
+        *
+        * @param   string   key or test name
+        * @param   string   used with "accept" tests: user_agent(accept_lang, en)
+        * @return  array    languages and charsets
+        * @return  string   all other keys
+        * @return  boolean  all tests
+        */
+       public static function user_agent($key = 'agent', $compare = NULL)
+       {
+               static $info;
+
+               // Return the raw string
+               if ($key === 'agent')
+                       return self::$user_agent;
+
+               if ($info === NULL)
+               {
+                       // Parse the user agent and extract basic information
+                       $agents = self::config('user_agents');
+
+                       foreach ($agents as $type => $data)
+                       {
+                               foreach ($data as $agent => $name)
+                               {
+                                       if (stripos(self::$user_agent, $agent) !== FALSE)
+                                       {
+                                               if ($type === 'browser' AND preg_match('|'.preg_quote($agent).'[^0-9.]*+([0-9.][0-9.a-z]*)|i', self::$user_agent, $match))
+                                               {
+                                                       // Set the browser version
+                                                       $info['version'] = $match[1];
+                                               }
+
+                                               // Set the agent name
+                                               $info[$type] = $name;
+                                               break;
+                                       }
+                               }
+                       }
+               }
+
+               if (empty($info[$key]))
+               {
+                       switch ($key)
+                       {
+                               case 'is_robot':
+                               case 'is_browser':
+                               case 'is_mobile':
+                                       // A boolean result
+                                       $return = ! empty($info[substr($key, 3)]);
+                               break;
+                               case 'languages':
+                                       $return = array();
+                                       if ( ! empty($_SERVER['HTTP_ACCEPT_LANGUAGE']))
+                                       {
+                                               if (preg_match_all('/[-a-z]{2,}/', strtolower(trim($_SERVER['HTTP_ACCEPT_LANGUAGE'])), $matches))
+                                               {
+                                                       // Found a result
+                                                       $return = $matches[0];
+                                               }
+                                       }
+                               break;
+                               case 'charsets':
+                                       $return = array();
+                                       if ( ! empty($_SERVER['HTTP_ACCEPT_CHARSET']))
+                                       {
+                                               if (preg_match_all('/[-a-z0-9]{2,}/', strtolower(trim($_SERVER['HTTP_ACCEPT_CHARSET'])), $matches))
+                                               {
+                                                       // Found a result
+                                                       $return = $matches[0];
+                                               }
+                                       }
+                               break;
+                               case 'referrer':
+                                       if ( ! empty($_SERVER['HTTP_REFERER']))
+                                       {
+                                               // Found a result
+                                               $return = trim($_SERVER['HTTP_REFERER']);
+                                       }
+                               break;
+                       }
+
+                       // Cache the return value
+                       isset($return) and $info[$key] = $return;
+               }
+
+               if ( ! empty($compare))
+               {
+                       // The comparison must always be lowercase
+                       $compare = strtolower($compare);
+
+                       switch ($key)
+                       {
+                               case 'accept_lang':
+                                       // Check if the lange is accepted
+                                       return in_array($compare, self::user_agent('languages'));
+                               break;
+                               case 'accept_charset':
+                                       // Check if the charset is accepted
+                                       return in_array($compare, self::user_agent('charsets'));
+                               break;
+                               default:
+                                       // Invalid comparison
+                                       return FALSE;
+                               break;
+                       }
+               }
+
+               // Return the key, if set
+               return isset($info[$key]) ? $info[$key] : NULL;
+       }
+
+       /**
+        * Quick debugging of any variable. Any number of parameters can be set.
+        *
+        * @return  string
+        */
+       public static function debug()
+       {
+               if (func_num_args() === 0)
+                       return;
+
+               // Get params
+               $params = func_get_args();
+               $output = array();
+
+               foreach ($params as $var)
+               {
+                       $output[] = '<pre>('.gettype($var).') '.html::specialchars(print_r($var, TRUE)).'</pre>';
+               }
+
+               return implode("\n", $output);
+       }
+
+       /**
+        * Displays nice backtrace information.
+        * @see http://php.net/debug_backtrace
+        *
+        * @param   array   backtrace generated by an exception or debug_backtrace
+        * @return  string
+        */
+       public static function backtrace($trace)
+       {
+               if ( ! is_array($trace))
+                       return;
+
+               // Final output
+               $output = array();
+
+               foreach ($trace as $entry)
+               {
+                       $temp = '<li>';
+
+                       if (isset($entry['file']))
+                       {
+                               $temp .= self::lang('core.error_file_line', preg_replace('!^'.preg_quote(DOCROOT).'!', '', $entry['file']), $entry['line']);
+                       }
+
+                       $temp .= '<pre>';
+
+                       if (isset($entry['class']))
+                       {
+                               // Add class and call type
+                               $temp .= $entry['class'].$entry['type'];
+                       }
+
+                       // Add function
+                       $temp .= $entry['function'].'( ';
+
+                       // Add function args
+                       if (isset($entry['args']) AND is_array($entry['args']))
+                       {
+                               // Separator starts as nothing
+                               $sep = '';
+
+                               while ($arg = array_shift($entry['args']))
+                               {
+                                       if (is_string($arg) AND is_file($arg))
+                                       {
+                                               // Remove docroot from filename
+                                               $arg = preg_replace('!^'.preg_quote(DOCROOT).'!', '', $arg);
+                                       }
+
+                                       $temp .= $sep.html::specialchars(print_r($arg, TRUE));
+
+                                       // Change separator to a comma
+                                       $sep = ', ';
+                               }
+                       }
+
+                       $temp .= ' )</pre></li>';
+
+                       $output[] = $temp;
+               }
+
+               return '<ul class="backtrace">'.implode("\n", $output).'</ul>';
+       }
+
+       /**
+        * Saves the internal caches: configuration, include paths, etc.
+        *
+        * @return  boolean
+        */
+       public static function internal_cache_save()
+       {
+               if ( ! is_array(self::$write_cache))
+                       return FALSE;
+
+               // Get internal cache names
+               $caches = array_keys(self::$write_cache);
+
+               // Nothing written
+               $written = FALSE;
+
+               foreach ($caches as $cache)
+               {
+                       if (isset(self::$internal_cache[$cache]))
+                       {
+                               // Write the cache file
+                               self::cache_save($cache, self::$internal_cache[$cache], self::$configuration['core']['internal_cache']);
+
+                               // A cache has been written
+                               $written = TRUE;
+                       }
+               }
+
+               return $written;
+       }
+
+} // End Kohana
+
+/**
+ * Creates a generic i18n exception.
+ */
+class Kohana_Exception extends Exception {
+
+       // Template file
+       protected $template = 'kohana_error_page';
+
+       // Header
+       protected $header = FALSE;
+
+       // Error code
+       protected $code = E_KOHANA;
+
+       /**
+        * Set exception message.
+        *
+        * @param  string  i18n language key for the message
+        * @param  array   addition line parameters
+        */
+       public function __construct($error)
+       {
+               $args = array_slice(func_get_args(), 1);
+
+               // Fetch the error message
+               $message = Kohana::lang($error, $args);
+
+               if ($message === $error OR empty($message))
+               {
+                       // Unable to locate the message for the error
+                       $message = 'Unknown Exception: '.$error;
+               }
+
+               // Sets $this->message the proper way
+               parent::__construct($message);
+       }
+
+       /**
+        * Magic method for converting an object to a string.
+        *
+        * @return  string  i18n message
+        */
+       public function __toString()
+       {
+               return (string) $this->message;
+       }
+
+       /**
+        * Fetch the template name.
+        *
+        * @return  string
+        */
+       public function getTemplate()
+       {
+               return $this->template;
+       }
+
+       /**
+        * Sends an Internal Server Error header.
+        *
+        * @return  void
+        */
+       public function sendHeaders()
+       {
+               // Send the 500 header
+               header('HTTP/1.1 500 Internal Server Error');
+       }
+
+} // End Kohana Exception
+
+/**
+ * Creates a custom exception.
+ */
+class Kohana_User_Exception extends Kohana_Exception {
+
+       /**
+        * Set exception title and message.
+        *
+        * @param   string  exception title string
+        * @param   string  exception message string
+        * @param   string  custom error template
+        */
+       public function __construct($title, $message, $template = FALSE)
+       {
+               Exception::__construct($message);
+
+               $this->code = $title;
+
+               if ($template !== FALSE)
+               {
+                       $this->template = $template;
+               }
+       }
+
+} // End Kohana PHP Exception
+
+/**
+ * Creates a Page Not Found exception.
+ */
+class Kohana_404_Exception extends Kohana_Exception {
+
+       protected $code = E_PAGE_NOT_FOUND;
+
+       /**
+        * Set internal properties.
+        *
+        * @param  string  URL of page
+        * @param  string  custom error template
+        */
+       public function __construct($page = FALSE, $template = FALSE)
+       {
+               if ($page === FALSE)
+               {
+                       // Construct the page URI using Router properties
+                       $page = Router::$current_uri.Router::$url_suffix.Router::$query_string;
+               }
+
+               Exception::__construct(Kohana::lang('core.page_not_found', $page));
+
+               $this->template = $template;
+       }
+
+       /**
+        * Sends "File Not Found" headers, to emulate server behavior.
+        *
+        * @return void
+        */
+       public function sendHeaders()
+       {
+               // Send the 404 header
+               header('HTTP/1.1 404 File Not Found');
+       }
+
+} // End Kohana 404 Exception
diff --git a/Server/system/core/utf8.php b/Server/system/core/utf8.php
new file mode 100644 (file)
index 0000000..9f20f42
--- /dev/null
@@ -0,0 +1,743 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * A port of phputf8 to a unified file/class. Checks PHP status to ensure that
+ * UTF-8 support is available and normalize global variables to UTF-8. It also
+ * provides multi-byte aware replacement string functions.
+ *
+ * This file is licensed differently from the rest of Kohana. As a port of
+ * phputf8, which is LGPL software, this file is released under the LGPL.
+ *
+ * PCRE needs to be compiled with UTF-8 support (--enable-utf8).
+ * Support for Unicode properties is highly recommended (--enable-unicode-properties).
+ * @see http://php.net/manual/reference.pcre.pattern.modifiers.php
+ *
+ * UTF-8 conversion will be much more reliable if the iconv extension is loaded.
+ * @see http://php.net/iconv
+ *
+ * The mbstring extension is highly recommended, but must not be overloading
+ * string functions.
+ * @see http://php.net/mbstring
+ *
+ * $Id: utf8.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+
+if ( ! preg_match('/^.$/u', 'ñ'))
+{
+       trigger_error
+       (
+               '<a href="http://php.net/pcre">PCRE</a> has not been compiled with UTF-8 support. '.
+               'See <a href="http://php.net/manual/reference.pcre.pattern.modifiers.php">PCRE Pattern Modifiers</a> '.
+               'for more information. This application cannot be run without UTF-8 support.',
+               E_USER_ERROR
+       );
+}
+
+if ( ! extension_loaded('iconv'))
+{
+       trigger_error
+       (
+               'The <a href="http://php.net/iconv">iconv</a> extension is not loaded. '.
+               'Without iconv, strings cannot be properly translated to UTF-8 from user input. '.
+               'This application cannot be run without UTF-8 support.',
+               E_USER_ERROR
+       );
+}
+
+if (extension_loaded('mbstring') AND (ini_get('mbstring.func_overload') & MB_OVERLOAD_STRING))
+{
+       trigger_error
+       (
+               'The <a href="http://php.net/mbstring">mbstring</a> extension is overloading PHP\'s native string functions. '.
+               'Disable this by setting mbstring.func_overload to 0, 1, 4 or 5 in php.ini or a .htaccess file.'.
+               'This application cannot be run without UTF-8 support.',
+               E_USER_ERROR
+       );
+}
+
+// Check PCRE support for Unicode properties such as \p and \X.
+$ER = error_reporting(0);
+define('PCRE_UNICODE_PROPERTIES', (bool) preg_match('/^\pL$/u', 'ñ'));
+error_reporting($ER);
+
+// SERVER_UTF8 ? use mb_* functions : use non-native functions
+if (extension_loaded('mbstring'))
+{
+       mb_internal_encoding('UTF-8');
+       define('SERVER_UTF8', TRUE);
+}
+else
+{
+       define('SERVER_UTF8', FALSE);
+}
+
+// Convert all global variables to UTF-8.
+$_GET    = utf8::clean($_GET);
+$_POST   = utf8::clean($_POST);
+$_COOKIE = utf8::clean($_COOKIE);
+$_SERVER = utf8::clean($_SERVER);
+
+if (PHP_SAPI == 'cli')
+{
+       // Convert command line arguments
+       $_SERVER['argv'] = utf8::clean($_SERVER['argv']);
+}
+
+final class utf8 {
+
+       // Called methods
+       static $called = array();
+
+       /**
+        * Recursively cleans arrays, objects, and strings. Removes ASCII control
+        * codes and converts to UTF-8 while silently discarding incompatible
+        * UTF-8 characters.
+        *
+        * @param   string  string to clean
+        * @return  string
+        */
+       public static function clean($str)
+       {
+               if (is_array($str) OR is_object($str))
+               {
+                       foreach ($str as $key => $val)
+                       {
+                               // Recursion!
+                               $str[self::clean($key)] = self::clean($val);
+                       }
+               }
+               elseif (is_string($str) AND $str !== '')
+               {
+                       // Remove control characters
+                       $str = self::strip_ascii_ctrl($str);
+
+                       if ( ! self::is_ascii($str))
+                       {
+                               // Disable notices
+                               $ER = error_reporting(~E_NOTICE);
+
+                               // iconv is expensive, so it is only used when needed
+                               $str = iconv('UTF-8', 'UTF-8//IGNORE', $str);
+
+                               // Turn notices back on
+                               error_reporting($ER);
+                       }
+               }
+
+               return $str;
+       }
+
+       /**
+        * Tests whether a string contains only 7bit ASCII bytes. This is used to
+        * determine when to use native functions or UTF-8 functions.
+        *
+        * @param   string  string to check
+        * @return  bool
+        */
+       public static function is_ascii($str)
+       {
+               return ! preg_match('/[^\x00-\x7F]/S', $str);
+       }
+
+       /**
+        * Strips out device control codes in the ASCII range.
+        *
+        * @param   string  string to clean
+        * @return  string
+        */
+       public static function strip_ascii_ctrl($str)
+       {
+               return preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S', '', $str);
+       }
+
+       /**
+        * Strips out all non-7bit ASCII bytes.
+        *
+        * @param   string  string to clean
+        * @return  string
+        */
+       public static function strip_non_ascii($str)
+       {
+               return preg_replace('/[^\x00-\x7F]+/S', '', $str);
+       }
+
+       /**
+        * Replaces special/accented UTF-8 characters by ASCII-7 'equivalents'.
+        *
+        * @author  Andreas Gohr <andi@splitbrain.org>
+        *
+        * @param   string   string to transliterate
+        * @param   integer  -1 lowercase only, +1 uppercase only, 0 both cases
+        * @return  string
+        */
+       public static function transliterate_to_ascii($str, $case = 0)
+       {
+               if ( ! isset(self::$called[__FUNCTION__]))
+               {
+                       require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+                       // Function has been called
+                       self::$called[__FUNCTION__] = TRUE;
+               }
+
+               return _transliterate_to_ascii($str, $case);
+       }
+
+       /**
+        * Returns the length of the given string.
+        * @see http://php.net/strlen
+        *
+        * @param   string   string being measured for length
+        * @return  integer
+        */
+       public static function strlen($str)
+       {
+               if ( ! isset(self::$called[__FUNCTION__]))
+               {
+                       require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+                       // Function has been called
+                       self::$called[__FUNCTION__] = TRUE;
+               }
+
+               return _strlen($str);
+       }
+
+       /**
+        * Finds position of first occurrence of a UTF-8 string.
+        * @see http://php.net/strlen
+        *
+        * @author  Harry Fuecks <hfuecks@gmail.com>
+        *
+        * @param   string   haystack
+        * @param   string   needle
+        * @param   integer  offset from which character in haystack to start searching
+        * @return  integer  position of needle
+        * @return  boolean  FALSE if the needle is not found
+        */
+       public static function strpos($str, $search, $offset = 0)
+       {
+               if ( ! isset(self::$called[__FUNCTION__]))
+               {
+                       require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+                       // Function has been called
+                       self::$called[__FUNCTION__] = TRUE;
+               }
+
+               return _strpos($str, $search, $offset);
+       }
+
+       /**
+        * Finds position of last occurrence of a char in a UTF-8 string.
+        * @see http://php.net/strrpos
+        *
+        * @author  Harry Fuecks <hfuecks@gmail.com>
+        *
+        * @param   string   haystack
+        * @param   string   needle
+        * @param   integer  offset from which character in haystack to start searching
+        * @return  integer  position of needle
+        * @return  boolean  FALSE if the needle is not found
+        */
+       public static function strrpos($str, $search, $offset = 0)
+       {
+               if ( ! isset(self::$called[__FUNCTION__]))
+               {
+                       require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+                       // Function has been called
+                       self::$called[__FUNCTION__] = TRUE;
+               }
+
+               return _strrpos($str, $search, $offset);
+       }
+
+       /**
+        * Returns part of a UTF-8 string.
+        * @see http://php.net/substr
+        *
+        * @author  Chris Smith <chris@jalakai.co.uk>
+        *
+        * @param   string   input string
+        * @param   integer  offset
+        * @param   integer  length limit
+        * @return  string
+        */
+       public static function substr($str, $offset, $length = NULL)
+       {
+               if ( ! isset(self::$called[__FUNCTION__]))
+               {
+                       require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+                       // Function has been called
+                       self::$called[__FUNCTION__] = TRUE;
+               }
+
+               return _substr($str, $offset, $length);
+       }
+
+       /**
+        * Replaces text within a portion of a UTF-8 string.
+        * @see http://php.net/substr_replace
+        *
+        * @author  Harry Fuecks <hfuecks@gmail.com>
+        *
+        * @param   string   input string
+        * @param   string   replacement string
+        * @param   integer  offset
+        * @return  string
+        */
+       public static function substr_replace($str, $replacement, $offset, $length = NULL)
+       {
+               if ( ! isset(self::$called[__FUNCTION__]))
+               {
+                       require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+                       // Function has been called
+                       self::$called[__FUNCTION__] = TRUE;
+               }
+
+               return _substr_replace($str, $replacement, $offset, $length);
+       }
+
+       /**
+        * Makes a UTF-8 string lowercase.
+        * @see http://php.net/strtolower
+        *
+        * @author  Andreas Gohr <andi@splitbrain.org>
+        *
+        * @param   string   mixed case string
+        * @return  string
+        */
+       public static function strtolower($str)
+       {
+               if ( ! isset(self::$called[__FUNCTION__]))
+               {
+                       require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+                       // Function has been called
+                       self::$called[__FUNCTION__] = TRUE;
+               }
+
+               return _strtolower($str);
+       }
+
+       /**
+        * Makes a UTF-8 string uppercase.
+        * @see http://php.net/strtoupper
+        *
+        * @author  Andreas Gohr <andi@splitbrain.org>
+        *
+        * @param   string   mixed case string
+        * @return  string
+        */
+       public static function strtoupper($str)
+       {
+               if ( ! isset(self::$called[__FUNCTION__]))
+               {
+                       require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+                       // Function has been called
+                       self::$called[__FUNCTION__] = TRUE;
+               }
+
+               return _strtoupper($str);
+       }
+
+       /**
+        * Makes a UTF-8 string's first character uppercase.
+        * @see http://php.net/ucfirst
+        *
+        * @author  Harry Fuecks <hfuecks@gmail.com>
+        *
+        * @param   string   mixed case string
+        * @return  string
+        */
+       public static function ucfirst($str)
+       {
+               if ( ! isset(self::$called[__FUNCTION__]))
+               {
+                       require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+                       // Function has been called
+                       self::$called[__FUNCTION__] = TRUE;
+               }
+
+               return _ucfirst($str);
+       }
+
+       /**
+        * Makes the first character of every word in a UTF-8 string uppercase.
+        * @see http://php.net/ucwords
+        *
+        * @author  Harry Fuecks <hfuecks@gmail.com>
+        *
+        * @param   string   mixed case string
+        * @return  string
+        */
+       public static function ucwords($str)
+       {
+               if ( ! isset(self::$called[__FUNCTION__]))
+               {
+                       require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+                       // Function has been called
+                       self::$called[__FUNCTION__] = TRUE;
+               }
+
+               return _ucwords($str);
+       }
+
+       /**
+        * Case-insensitive UTF-8 string comparison.
+        * @see http://php.net/strcasecmp
+        *
+        * @author  Harry Fuecks <hfuecks@gmail.com>
+        *
+        * @param   string   string to compare
+        * @param   string   string to compare
+        * @return  integer  less than 0 if str1 is less than str2
+        * @return  integer  greater than 0 if str1 is greater than str2
+        * @return  integer  0 if they are equal
+        */
+       public static function strcasecmp($str1, $str2)
+       {
+               if ( ! isset(self::$called[__FUNCTION__]))
+               {
+                       require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+                       // Function has been called
+                       self::$called[__FUNCTION__] = TRUE;
+               }
+
+               return _strcasecmp($str1, $str2);
+       }
+
+       /**
+        * Returns a string or an array with all occurrences of search in subject (ignoring case).
+        * replaced with the given replace value.
+        * @see     http://php.net/str_ireplace
+        *
+        * @note    It's not fast and gets slower if $search and/or $replace are arrays.
+        * @author  Harry Fuecks <hfuecks@gmail.com
+        *
+        * @param   string|array  text to replace
+        * @param   string|array  replacement text
+        * @param   string|array  subject text
+        * @param   integer       number of matched and replaced needles will be returned via this parameter which is passed by reference
+        * @return  string        if the input was a string
+        * @return  array         if the input was an array
+        */
+       public static function str_ireplace($search, $replace, $str, & $count = NULL)
+       {
+               if ( ! isset(self::$called[__FUNCTION__]))
+               {
+                       require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+                       // Function has been called
+                       self::$called[__FUNCTION__] = TRUE;
+               }
+
+               return _str_ireplace($search, $replace, $str, $count);
+       }
+
+       /**
+        * Case-insenstive UTF-8 version of strstr. Returns all of input string
+        * from the first occurrence of needle to the end.
+        * @see http://php.net/stristr
+        *
+        * @author Harry Fuecks <hfuecks@gmail.com>
+        *
+        * @param   string   input string
+        * @param   string   needle
+        * @return  string   matched substring if found
+        * @return  boolean  FALSE if the substring was not found
+        */
+       public static function stristr($str, $search)
+       {
+               if ( ! isset(self::$called[__FUNCTION__]))
+               {
+                       require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+                       // Function has been called
+                       self::$called[__FUNCTION__] = TRUE;
+               }
+
+               return _stristr($str, $search);
+       }
+
+       /**
+        * Finds the length of the initial segment matching mask.
+        * @see http://php.net/strspn
+        *
+        * @author Harry Fuecks <hfuecks@gmail.com>
+        *
+        * @param   string   input string
+        * @param   string   mask for search
+        * @param   integer  start position of the string to examine
+        * @param   integer  length of the string to examine
+        * @return  integer  length of the initial segment that contains characters in the mask
+        */
+       public static function strspn($str, $mask, $offset = NULL, $length = NULL)
+       {
+               if ( ! isset(self::$called[__FUNCTION__]))
+               {
+                       require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+                       // Function has been called
+                       self::$called[__FUNCTION__] = TRUE;
+               }
+
+               return _strspn($str, $mask, $offset, $length);
+       }
+
+       /**
+        * Finds the length of the initial segment not matching mask.
+        * @see http://php.net/strcspn
+        *
+        * @author  Harry Fuecks <hfuecks@gmail.com>
+        *
+        * @param   string   input string
+        * @param   string   mask for search
+        * @param   integer  start position of the string to examine
+        * @param   integer  length of the string to examine
+        * @return  integer  length of the initial segment that contains characters not in the mask
+        */
+       public static function strcspn($str, $mask, $offset = NULL, $length = NULL)
+       {
+               if ( ! isset(self::$called[__FUNCTION__]))
+               {
+                       require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+                       // Function has been called
+                       self::$called[__FUNCTION__] = TRUE;
+               }
+
+               return _strcspn($str, $mask, $offset, $length);
+       }
+
+       /**
+        * Pads a UTF-8 string to a certain length with another string.
+        * @see http://php.net/str_pad
+        *
+        * @author  Harry Fuecks <hfuecks@gmail.com>
+        *
+        * @param   string   input string
+        * @param   integer  desired string length after padding
+        * @param   string   string to use as padding
+        * @param   string   padding type: STR_PAD_RIGHT, STR_PAD_LEFT, or STR_PAD_BOTH
+        * @return  string
+        */
+       public static function str_pad($str, $final_str_length, $pad_str = ' ', $pad_type = STR_PAD_RIGHT)
+       {
+               if ( ! isset(self::$called[__FUNCTION__]))
+               {
+                       require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+                       // Function has been called
+                       self::$called[__FUNCTION__] = TRUE;
+               }
+
+               return _str_pad($str, $final_str_length, $pad_str, $pad_type);
+       }
+
+       /**
+        * Converts a UTF-8 string to an array.
+        * @see http://php.net/str_split
+        *
+        * @author  Harry Fuecks <hfuecks@gmail.com>
+        *
+        * @param   string   input string
+        * @param   integer  maximum length of each chunk
+        * @return  array
+        */
+       public static function str_split($str, $split_length = 1)
+       {
+               if ( ! isset(self::$called[__FUNCTION__]))
+               {
+                       require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+                       // Function has been called
+                       self::$called[__FUNCTION__] = TRUE;
+               }
+
+               return _str_split($str, $split_length);
+       }
+
+       /**
+        * Reverses a UTF-8 string.
+        * @see http://php.net/strrev
+        *
+        * @author  Harry Fuecks <hfuecks@gmail.com>
+        *
+        * @param   string   string to be reversed
+        * @return  string
+        */
+       public static function strrev($str)
+       {
+               if ( ! isset(self::$called[__FUNCTION__]))
+               {
+                       require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+                       // Function has been called
+                       self::$called[__FUNCTION__] = TRUE;
+               }
+
+               return _strrev($str);
+       }
+
+       /**
+        * Strips whitespace (or other UTF-8 characters) from the beginning and
+        * end of a string.
+        * @see http://php.net/trim
+        *
+        * @author  Andreas Gohr <andi@splitbrain.org>
+        *
+        * @param   string   input string
+        * @param   string   string of characters to remove
+        * @return  string
+        */
+       public static function trim($str, $charlist = NULL)
+       {
+               if ( ! isset(self::$called[__FUNCTION__]))
+               {
+                       require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+                       // Function has been called
+                       self::$called[__FUNCTION__] = TRUE;
+               }
+
+               return _trim($str, $charlist);
+       }
+
+       /**
+        * Strips whitespace (or other UTF-8 characters) from the beginning of a string.
+        * @see http://php.net/ltrim
+        *
+        * @author  Andreas Gohr <andi@splitbrain.org>
+        *
+        * @param   string   input string
+        * @param   string   string of characters to remove
+        * @return  string
+        */
+       public static function ltrim($str, $charlist = NULL)
+       {
+               if ( ! isset(self::$called[__FUNCTION__]))
+               {
+                       require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+                       // Function has been called
+                       self::$called[__FUNCTION__] = TRUE;
+               }
+
+               return _ltrim($str, $charlist);
+       }
+
+       /**
+        * Strips whitespace (or other UTF-8 characters) from the end of a string.
+        * @see http://php.net/rtrim
+        *
+        * @author  Andreas Gohr <andi@splitbrain.org>
+        *
+        * @param   string   input string
+        * @param   string   string of characters to remove
+        * @return  string
+        */
+       public static function rtrim($str, $charlist = NULL)
+       {
+               if ( ! isset(self::$called[__FUNCTION__]))
+               {
+                       require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+                       // Function has been called
+                       self::$called[__FUNCTION__] = TRUE;
+               }
+
+               return _rtrim($str, $charlist);
+       }
+
+       /**
+        * Returns the unicode ordinal for a character.
+        * @see http://php.net/ord
+        *
+        * @author Harry Fuecks <hfuecks@gmail.com>
+        *
+        * @param   string   UTF-8 encoded character
+        * @return  integer
+        */
+       public static function ord($chr)
+       {
+               if ( ! isset(self::$called[__FUNCTION__]))
+               {
+                       require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+                       // Function has been called
+                       self::$called[__FUNCTION__] = TRUE;
+               }
+
+               return _ord($chr);
+       }
+
+       /**
+        * Takes an UTF-8 string and returns an array of ints representing the Unicode characters.
+        * Astral planes are supported i.e. the ints in the output can be > 0xFFFF.
+        * Occurrances of the BOM are ignored. Surrogates are not allowed.
+        *
+        * The Original Code is Mozilla Communicator client code.
+        * The Initial Developer of the Original Code is Netscape Communications Corporation.
+        * Portions created by the Initial Developer are Copyright (C) 1998 the Initial Developer.
+        * Ported to PHP by Henri Sivonen <hsivonen@iki.fi>, see http://hsivonen.iki.fi/php-utf8/.
+        * Slight modifications to fit with phputf8 library by Harry Fuecks <hfuecks@gmail.com>.
+        *
+        * @param   string   UTF-8 encoded string
+        * @return  array    unicode code points
+        * @return  boolean  FALSE if the string is invalid
+        */
+       public static function to_unicode($str)
+       {
+               if ( ! isset(self::$called[__FUNCTION__]))
+               {
+                       require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+                       // Function has been called
+                       self::$called[__FUNCTION__] = TRUE;
+               }
+
+               return _to_unicode($str);
+       }
+
+       /**
+        * Takes an array of ints representing the Unicode characters and returns a UTF-8 string.
+        * Astral planes are supported i.e. the ints in the input can be > 0xFFFF.
+        * Occurrances of the BOM are ignored. Surrogates are not allowed.
+        *
+        * The Original Code is Mozilla Communicator client code.
+        * The Initial Developer of the Original Code is Netscape Communications Corporation.
+        * Portions created by the Initial Developer are Copyright (C) 1998 the Initial Developer.
+        * Ported to PHP by Henri Sivonen <hsivonen@iki.fi>, see http://hsivonen.iki.fi/php-utf8/.
+        * Slight modifications to fit with phputf8 library by Harry Fuecks <hfuecks@gmail.com>.
+        *
+        * @param   array    unicode code points representing a string
+        * @return  string   utf8 string of characters
+        * @return  boolean  FALSE if a code point cannot be found
+        */
+       public static function from_unicode($arr)
+       {
+               if ( ! isset(self::$called[__FUNCTION__]))
+               {
+                       require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+                       // Function has been called
+                       self::$called[__FUNCTION__] = TRUE;
+               }
+
+               return _from_unicode($arr);
+       }
+
+} // End utf8
\ No newline at end of file
diff --git a/Server/system/core/utf8/from_unicode.php b/Server/system/core/utf8/from_unicode.php
new file mode 100644 (file)
index 0000000..66c6742
--- /dev/null
@@ -0,0 +1,68 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::from_unicode
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _from_unicode($arr)
+{
+       ob_start();
+
+       $keys = array_keys($arr);
+
+       foreach ($keys as $k)
+       {
+               // ASCII range (including control chars)
+               if (($arr[$k] >= 0) AND ($arr[$k] <= 0x007f))
+               {
+                       echo chr($arr[$k]);
+               }
+               // 2 byte sequence
+               elseif ($arr[$k] <= 0x07ff)
+               {
+                       echo chr(0xc0 | ($arr[$k] >> 6));
+                       echo chr(0x80 | ($arr[$k] & 0x003f));
+               }
+               // Byte order mark (skip)
+               elseif ($arr[$k] == 0xFEFF)
+               {
+                       // nop -- zap the BOM
+               }
+               // Test for illegal surrogates
+               elseif ($arr[$k] >= 0xD800 AND $arr[$k] <= 0xDFFF)
+               {
+                       // Found a surrogate
+                       trigger_error('utf8::from_unicode: Illegal surrogate at index: '.$k.', value: '.$arr[$k], E_USER_WARNING);
+                       return FALSE;
+               }
+               // 3 byte sequence
+               elseif ($arr[$k] <= 0xffff)
+               {
+                       echo chr(0xe0 | ($arr[$k] >> 12));
+                       echo chr(0x80 | (($arr[$k] >> 6) & 0x003f));
+                       echo chr(0x80 | ($arr[$k] & 0x003f));
+               }
+               // 4 byte sequence
+               elseif ($arr[$k] <= 0x10ffff)
+               {
+                       echo chr(0xf0 | ($arr[$k] >> 18));
+                       echo chr(0x80 | (($arr[$k] >> 12) & 0x3f));
+                       echo chr(0x80 | (($arr[$k] >> 6) & 0x3f));
+                       echo chr(0x80 | ($arr[$k] & 0x3f));
+               }
+               // Out of range
+               else
+               {
+                       trigger_error('utf8::from_unicode: Codepoint out of Unicode range at index: '.$k.', value: '.$arr[$k], E_USER_WARNING);
+                       return FALSE;
+               }
+       }
+
+       $result = ob_get_contents();
+       ob_end_clean();
+       return $result;
+}
diff --git a/Server/system/core/utf8/ltrim.php b/Server/system/core/utf8/ltrim.php
new file mode 100644 (file)
index 0000000..556fe07
--- /dev/null
@@ -0,0 +1,22 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::ltrim
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _ltrim($str, $charlist = NULL)
+{
+       if ($charlist === NULL)
+               return ltrim($str);
+
+       if (utf8::is_ascii($charlist))
+               return ltrim($str, $charlist);
+
+       $charlist = preg_replace('#[-\[\]:\\\\^/]#', '\\\\$0', $charlist);
+
+       return preg_replace('/^['.$charlist.']+/u', '', $str);
+}
\ No newline at end of file
diff --git a/Server/system/core/utf8/ord.php b/Server/system/core/utf8/ord.php
new file mode 100644 (file)
index 0000000..76e3187
--- /dev/null
@@ -0,0 +1,88 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::ord
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _ord($chr)
+{
+       $ord0 = ord($chr);
+
+       if ($ord0 >= 0 AND $ord0 <= 127)
+       {
+               return $ord0;
+       }
+
+       if ( ! isset($chr[1]))
+       {
+               trigger_error('Short sequence - at least 2 bytes expected, only 1 seen', E_USER_WARNING);
+               return FALSE;
+       }
+
+       $ord1 = ord($chr[1]);
+
+       if ($ord0 >= 192 AND $ord0 <= 223)
+       {
+               return ($ord0 - 192) * 64 + ($ord1 - 128);
+       }
+
+       if ( ! isset($chr[2]))
+       {
+               trigger_error('Short sequence - at least 3 bytes expected, only 2 seen', E_USER_WARNING);
+               return FALSE;
+       }
+
+       $ord2 = ord($chr[2]);
+
+       if ($ord0 >= 224 AND $ord0 <= 239)
+       {
+               return ($ord0 - 224) * 4096 + ($ord1 - 128) * 64 + ($ord2 - 128);
+       }
+
+       if ( ! isset($chr[3]))
+       {
+               trigger_error('Short sequence - at least 4 bytes expected, only 3 seen', E_USER_WARNING);
+               return FALSE;
+       }
+
+       $ord3 = ord($chr[3]);
+
+       if ($ord0 >= 240 AND $ord0 <= 247)
+       {
+               return ($ord0 - 240) * 262144 + ($ord1 - 128) * 4096 + ($ord2-128) * 64 + ($ord3 - 128);
+       }
+
+       if ( ! isset($chr[4]))
+       {
+               trigger_error('Short sequence - at least 5 bytes expected, only 4 seen', E_USER_WARNING);
+               return FALSE;
+       }
+
+       $ord4 = ord($chr[4]);
+
+       if ($ord0 >= 248 AND $ord0 <= 251)
+       {
+               return ($ord0 - 248) * 16777216 + ($ord1-128) * 262144 + ($ord2 - 128) * 4096 + ($ord3 - 128) * 64 + ($ord4 - 128);
+       }
+
+       if ( ! isset($chr[5]))
+       {
+               trigger_error('Short sequence - at least 6 bytes expected, only 5 seen', E_USER_WARNING);
+               return FALSE;
+       }
+
+       if ($ord0 >= 252 AND $ord0 <= 253)
+       {
+               return ($ord0 - 252) * 1073741824 + ($ord1 - 128) * 16777216 + ($ord2 - 128) * 262144 + ($ord3 - 128) * 4096 + ($ord4 - 128) * 64 + (ord($chr[5]) - 128);
+       }
+
+       if ($ord0 >= 254 AND $ord0 <= 255)
+       {
+               trigger_error('Invalid UTF-8 with surrogate ordinal '.$ord0, E_USER_WARNING);
+               return FALSE;
+       }
+}
\ No newline at end of file
diff --git a/Server/system/core/utf8/rtrim.php b/Server/system/core/utf8/rtrim.php
new file mode 100644 (file)
index 0000000..efa0e19
--- /dev/null
@@ -0,0 +1,22 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::rtrim
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _rtrim($str, $charlist = NULL)
+{
+       if ($charlist === NULL)
+               return rtrim($str);
+
+       if (utf8::is_ascii($charlist))
+               return rtrim($str, $charlist);
+
+       $charlist = preg_replace('#[-\[\]:\\\\^/]#', '\\\\$0', $charlist);
+
+       return preg_replace('/['.$charlist.']++$/uD', '', $str);
+}
\ No newline at end of file
diff --git a/Server/system/core/utf8/str_ireplace.php b/Server/system/core/utf8/str_ireplace.php
new file mode 100644 (file)
index 0000000..97ad71a
--- /dev/null
@@ -0,0 +1,70 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::str_ireplace
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _str_ireplace($search, $replace, $str, & $count = NULL)
+{
+       if (utf8::is_ascii($search) AND utf8::is_ascii($replace) AND utf8::is_ascii($str))
+               return str_ireplace($search, $replace, $str, $count);
+
+       if (is_array($str))
+       {
+               foreach ($str as $key => $val)
+               {
+                       $str[$key] = utf8::str_ireplace($search, $replace, $val, $count);
+               }
+               return $str;
+       }
+
+       if (is_array($search))
+       {
+               $keys = array_keys($search);
+
+               foreach ($keys as $k)
+               {
+                       if (is_array($replace))
+                       {
+                               if (array_key_exists($k, $replace))
+                               {
+                                       $str = utf8::str_ireplace($search[$k], $replace[$k], $str, $count);
+                               }
+                               else
+                               {
+                                       $str = utf8::str_ireplace($search[$k], '', $str, $count);
+                               }
+                       }
+                       else
+                       {
+                               $str = utf8::str_ireplace($search[$k], $replace, $str, $count);
+                       }
+               }
+               return $str;
+       }
+
+       $search = utf8::strtolower($search);
+       $str_lower = utf8::strtolower($str);
+
+       $total_matched_strlen = 0;
+       $i = 0;
+
+       while (preg_match('/(.*?)'.preg_quote($search, '/').'/s', $str_lower, $matches))
+       {
+               $matched_strlen = strlen($matches[0]);
+               $str_lower = substr($str_lower, $matched_strlen);
+
+               $offset = $total_matched_strlen + strlen($matches[1]) + ($i * (strlen($replace) - 1));
+               $str = substr_replace($str, $replace, $offset, strlen($search));
+
+               $total_matched_strlen += $matched_strlen;
+               $i++;
+       }
+
+       $count += $i;
+       return $str;
+}
diff --git a/Server/system/core/utf8/str_pad.php b/Server/system/core/utf8/str_pad.php
new file mode 100644 (file)
index 0000000..aab4ccc
--- /dev/null
@@ -0,0 +1,54 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::str_pad
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _str_pad($str, $final_str_length, $pad_str = ' ', $pad_type = STR_PAD_RIGHT)
+{
+       if (utf8::is_ascii($str) AND utf8::is_ascii($pad_str))
+       {
+               return str_pad($str, $final_str_length, $pad_str, $pad_type);
+       }
+
+       $str_length = utf8::strlen($str);
+
+       if ($final_str_length <= 0 OR $final_str_length <= $str_length)
+       {
+               return $str;
+       }
+
+       $pad_str_length = utf8::strlen($pad_str);
+       $pad_length = $final_str_length - $str_length;
+
+       if ($pad_type == STR_PAD_RIGHT)
+       {
+               $repeat = ceil($pad_length / $pad_str_length);
+               return utf8::substr($str.str_repeat($pad_str, $repeat), 0, $final_str_length);
+       }
+
+       if ($pad_type == STR_PAD_LEFT)
+       {
+               $repeat = ceil($pad_length / $pad_str_length);
+               return utf8::substr(str_repeat($pad_str, $repeat), 0, floor($pad_length)).$str;
+       }
+
+       if ($pad_type == STR_PAD_BOTH)
+       {
+               $pad_length /= 2;
+               $pad_length_left = floor($pad_length);
+               $pad_length_right = ceil($pad_length);
+               $repeat_left = ceil($pad_length_left / $pad_str_length);
+               $repeat_right = ceil($pad_length_right / $pad_str_length);
+
+               $pad_left = utf8::substr(str_repeat($pad_str, $repeat_left), 0, $pad_length_left);
+               $pad_right = utf8::substr(str_repeat($pad_str, $repeat_right), 0, $pad_length_left);
+               return $pad_left.$str.$pad_right;
+       }
+
+       trigger_error('utf8::str_pad: Unknown padding type (' . $pad_type . ')', E_USER_ERROR);
+}
\ No newline at end of file
diff --git a/Server/system/core/utf8/str_split.php b/Server/system/core/utf8/str_split.php
new file mode 100644 (file)
index 0000000..bc382cb
--- /dev/null
@@ -0,0 +1,33 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::str_split
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _str_split($str, $split_length = 1)
+{
+       $split_length = (int) $split_length;
+
+       if (utf8::is_ascii($str))
+       {
+               return str_split($str, $split_length);
+       }
+
+       if ($split_length < 1)
+       {
+               return FALSE;
+       }
+
+       if (utf8::strlen($str) <= $split_length)
+       {
+               return array($str);
+       }
+
+       preg_match_all('/.{'.$split_length.'}|[^\x00]{1,'.$split_length.'}$/us', $str, $matches);
+
+       return $matches[0];
+}
\ No newline at end of file
diff --git a/Server/system/core/utf8/strcasecmp.php b/Server/system/core/utf8/strcasecmp.php
new file mode 100644 (file)
index 0000000..d06d3eb
--- /dev/null
@@ -0,0 +1,19 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::strcasecmp
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _strcasecmp($str1, $str2)
+{
+       if (utf8::is_ascii($str1) AND utf8::is_ascii($str2))
+               return strcasecmp($str1, $str2);
+
+       $str1 = utf8::strtolower($str1);
+       $str2 = utf8::strtolower($str2);
+       return strcmp($str1, $str2);
+}
\ No newline at end of file
diff --git a/Server/system/core/utf8/strcspn.php b/Server/system/core/utf8/strcspn.php
new file mode 100644 (file)
index 0000000..20d3e80
--- /dev/null
@@ -0,0 +1,30 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::strcspn
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _strcspn($str, $mask, $offset = NULL, $length = NULL)
+{
+       if ($str == '' OR $mask == '')
+               return 0;
+
+       if (utf8::is_ascii($str) AND utf8::is_ascii($mask))
+               return ($offset === NULL) ? strcspn($str, $mask) : (($length === NULL) ? strcspn($str, $mask, $offset) : strcspn($str, $mask, $offset, $length));
+
+       if ($str !== NULL OR $length !== NULL)
+       {
+               $str = utf8::substr($str, $offset, $length);
+       }
+
+       // Escape these characters:  - [ ] . : \ ^ /
+       // The . and : are escaped to prevent possible warnings about POSIX regex elements
+       $mask = preg_replace('#[-[\].:\\\\^/]#', '\\\\$0', $mask);
+       preg_match('/^[^'.$mask.']+/u', $str, $matches);
+
+       return isset($matches[0]) ? utf8::strlen($matches[0]) : 0;
+}
\ No newline at end of file
diff --git a/Server/system/core/utf8/stristr.php b/Server/system/core/utf8/stristr.php
new file mode 100644 (file)
index 0000000..73ea139
--- /dev/null
@@ -0,0 +1,28 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::stristr
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _stristr($str, $search)
+{
+       if (utf8::is_ascii($str) AND utf8::is_ascii($search))
+               return stristr($str, $search);
+
+       if ($search == '')
+               return $str;
+
+       $str_lower = utf8::strtolower($str);
+       $search_lower = utf8::strtolower($search);
+
+       preg_match('/^(.*?)'.preg_quote($search, '/').'/s', $str_lower, $matches);
+
+       if (isset($matches[1]))
+               return substr($str, strlen($matches[1]));
+
+       return FALSE;
+}
\ No newline at end of file
diff --git a/Server/system/core/utf8/strlen.php b/Server/system/core/utf8/strlen.php
new file mode 100644 (file)
index 0000000..f186a9c
--- /dev/null
@@ -0,0 +1,21 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::strlen
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _strlen($str)
+{
+       // Try mb_strlen() first because it's faster than combination of is_ascii() and strlen()
+       if (SERVER_UTF8)
+               return mb_strlen($str);
+
+       if (utf8::is_ascii($str))
+               return strlen($str);
+
+       return strlen(utf8_decode($str));
+}
\ No newline at end of file
diff --git a/Server/system/core/utf8/strpos.php b/Server/system/core/utf8/strpos.php
new file mode 100644 (file)
index 0000000..4a34dbe
--- /dev/null
@@ -0,0 +1,30 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::strpos
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _strpos($str, $search, $offset = 0)
+{
+       $offset = (int) $offset;
+
+       if (SERVER_UTF8)
+               return mb_strpos($str, $search, $offset);
+
+       if (utf8::is_ascii($str) AND utf8::is_ascii($search))
+               return strpos($str, $search, $offset);
+
+       if ($offset == 0)
+       {
+               $array = explode($search, $str, 2);
+               return isset($array[1]) ? utf8::strlen($array[0]) : FALSE;
+       }
+
+       $str = utf8::substr($str, $offset);
+       $pos = utf8::strpos($str, $search);
+       return ($pos === FALSE) ? FALSE : $pos + $offset;
+}
\ No newline at end of file
diff --git a/Server/system/core/utf8/strrev.php b/Server/system/core/utf8/strrev.php
new file mode 100644 (file)
index 0000000..89c428c
--- /dev/null
@@ -0,0 +1,18 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::strrev
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _strrev($str)
+{
+       if (utf8::is_ascii($str))
+               return strrev($str);
+
+       preg_match_all('/./us', $str, $matches);
+       return implode('', array_reverse($matches[0]));
+}
\ No newline at end of file
diff --git a/Server/system/core/utf8/strrpos.php b/Server/system/core/utf8/strrpos.php
new file mode 100644 (file)
index 0000000..1ac8f9c
--- /dev/null
@@ -0,0 +1,30 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::strrpos
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _strrpos($str, $search, $offset = 0)
+{
+       $offset = (int) $offset;
+
+       if (SERVER_UTF8)
+               return mb_strrpos($str, $search, $offset);
+
+       if (utf8::is_ascii($str) AND utf8::is_ascii($search))
+               return strrpos($str, $search, $offset);
+
+       if ($offset == 0)
+       {
+               $array = explode($search, $str, -1);
+               return isset($array[0]) ? utf8::strlen(implode($search, $array)) : FALSE;
+       }
+
+       $str = utf8::substr($str, $offset);
+       $pos = utf8::strrpos($str, $search);
+       return ($pos === FALSE) ? FALSE : $pos + $offset;
+}
\ No newline at end of file
diff --git a/Server/system/core/utf8/strspn.php b/Server/system/core/utf8/strspn.php
new file mode 100644 (file)
index 0000000..bc7e52f
--- /dev/null
@@ -0,0 +1,30 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::strspn
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _strspn($str, $mask, $offset = NULL, $length = NULL)
+{
+       if ($str == '' OR $mask == '')
+               return 0;
+
+       if (utf8::is_ascii($str) AND utf8::is_ascii($mask))
+               return ($offset === NULL) ? strspn($str, $mask) : (($length === NULL) ? strspn($str, $mask, $offset) : strspn($str, $mask, $offset, $length));
+
+       if ($offset !== NULL OR $length !== NULL)
+       {
+               $str = utf8::substr($str, $offset, $length);
+       }
+
+       // Escape these characters:  - [ ] . : \ ^ /
+       // The . and : are escaped to prevent possible warnings about POSIX regex elements
+       $mask = preg_replace('#[-[\].:\\\\^/]#', '\\\\$0', $mask);
+       preg_match('/^[^'.$mask.']+/u', $str, $matches);
+
+       return isset($matches[0]) ? utf8::strlen($matches[0]) : 0;
+}
\ No newline at end of file
diff --git a/Server/system/core/utf8/strtolower.php b/Server/system/core/utf8/strtolower.php
new file mode 100644 (file)
index 0000000..ff4da85
--- /dev/null
@@ -0,0 +1,84 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::strtolower
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _strtolower($str)
+{
+       if (SERVER_UTF8)
+               return mb_strtolower($str);
+
+       if (utf8::is_ascii($str))
+               return strtolower($str);
+
+       static $UTF8_UPPER_TO_LOWER = NULL;
+
+       if ($UTF8_UPPER_TO_LOWER === NULL)
+       {
+               $UTF8_UPPER_TO_LOWER = array(
+                       0x0041=>0x0061, 0x03A6=>0x03C6, 0x0162=>0x0163, 0x00C5=>0x00E5, 0x0042=>0x0062,
+                       0x0139=>0x013A, 0x00C1=>0x00E1, 0x0141=>0x0142, 0x038E=>0x03CD, 0x0100=>0x0101,
+                       0x0490=>0x0491, 0x0394=>0x03B4, 0x015A=>0x015B, 0x0044=>0x0064, 0x0393=>0x03B3,
+                       0x00D4=>0x00F4, 0x042A=>0x044A, 0x0419=>0x0439, 0x0112=>0x0113, 0x041C=>0x043C,
+                       0x015E=>0x015F, 0x0143=>0x0144, 0x00CE=>0x00EE, 0x040E=>0x045E, 0x042F=>0x044F,
+                       0x039A=>0x03BA, 0x0154=>0x0155, 0x0049=>0x0069, 0x0053=>0x0073, 0x1E1E=>0x1E1F,
+                       0x0134=>0x0135, 0x0427=>0x0447, 0x03A0=>0x03C0, 0x0418=>0x0438, 0x00D3=>0x00F3,
+                       0x0420=>0x0440, 0x0404=>0x0454, 0x0415=>0x0435, 0x0429=>0x0449, 0x014A=>0x014B,
+                       0x0411=>0x0431, 0x0409=>0x0459, 0x1E02=>0x1E03, 0x00D6=>0x00F6, 0x00D9=>0x00F9,
+                       0x004E=>0x006E, 0x0401=>0x0451, 0x03A4=>0x03C4, 0x0423=>0x0443, 0x015C=>0x015D,
+                       0x0403=>0x0453, 0x03A8=>0x03C8, 0x0158=>0x0159, 0x0047=>0x0067, 0x00C4=>0x00E4,
+                       0x0386=>0x03AC, 0x0389=>0x03AE, 0x0166=>0x0167, 0x039E=>0x03BE, 0x0164=>0x0165,
+                       0x0116=>0x0117, 0x0108=>0x0109, 0x0056=>0x0076, 0x00DE=>0x00FE, 0x0156=>0x0157,
+                       0x00DA=>0x00FA, 0x1E60=>0x1E61, 0x1E82=>0x1E83, 0x00C2=>0x00E2, 0x0118=>0x0119,
+                       0x0145=>0x0146, 0x0050=>0x0070, 0x0150=>0x0151, 0x042E=>0x044E, 0x0128=>0x0129,
+                       0x03A7=>0x03C7, 0x013D=>0x013E, 0x0422=>0x0442, 0x005A=>0x007A, 0x0428=>0x0448,
+                       0x03A1=>0x03C1, 0x1E80=>0x1E81, 0x016C=>0x016D, 0x00D5=>0x00F5, 0x0055=>0x0075,
+                       0x0176=>0x0177, 0x00DC=>0x00FC, 0x1E56=>0x1E57, 0x03A3=>0x03C3, 0x041A=>0x043A,
+                       0x004D=>0x006D, 0x016A=>0x016B, 0x0170=>0x0171, 0x0424=>0x0444, 0x00CC=>0x00EC,
+                       0x0168=>0x0169, 0x039F=>0x03BF, 0x004B=>0x006B, 0x00D2=>0x00F2, 0x00C0=>0x00E0,
+                       0x0414=>0x0434, 0x03A9=>0x03C9, 0x1E6A=>0x1E6B, 0x00C3=>0x00E3, 0x042D=>0x044D,
+                       0x0416=>0x0436, 0x01A0=>0x01A1, 0x010C=>0x010D, 0x011C=>0x011D, 0x00D0=>0x00F0,
+                       0x013B=>0x013C, 0x040F=>0x045F, 0x040A=>0x045A, 0x00C8=>0x00E8, 0x03A5=>0x03C5,
+                       0x0046=>0x0066, 0x00DD=>0x00FD, 0x0043=>0x0063, 0x021A=>0x021B, 0x00CA=>0x00EA,
+                       0x0399=>0x03B9, 0x0179=>0x017A, 0x00CF=>0x00EF, 0x01AF=>0x01B0, 0x0045=>0x0065,
+                       0x039B=>0x03BB, 0x0398=>0x03B8, 0x039C=>0x03BC, 0x040C=>0x045C, 0x041F=>0x043F,
+                       0x042C=>0x044C, 0x00DE=>0x00FE, 0x00D0=>0x00F0, 0x1EF2=>0x1EF3, 0x0048=>0x0068,
+                       0x00CB=>0x00EB, 0x0110=>0x0111, 0x0413=>0x0433, 0x012E=>0x012F, 0x00C6=>0x00E6,
+                       0x0058=>0x0078, 0x0160=>0x0161, 0x016E=>0x016F, 0x0391=>0x03B1, 0x0407=>0x0457,
+                       0x0172=>0x0173, 0x0178=>0x00FF, 0x004F=>0x006F, 0x041B=>0x043B, 0x0395=>0x03B5,
+                       0x0425=>0x0445, 0x0120=>0x0121, 0x017D=>0x017E, 0x017B=>0x017C, 0x0396=>0x03B6,
+                       0x0392=>0x03B2, 0x0388=>0x03AD, 0x1E84=>0x1E85, 0x0174=>0x0175, 0x0051=>0x0071,
+                       0x0417=>0x0437, 0x1E0A=>0x1E0B, 0x0147=>0x0148, 0x0104=>0x0105, 0x0408=>0x0458,
+                       0x014C=>0x014D, 0x00CD=>0x00ED, 0x0059=>0x0079, 0x010A=>0x010B, 0x038F=>0x03CE,
+                       0x0052=>0x0072, 0x0410=>0x0430, 0x0405=>0x0455, 0x0402=>0x0452, 0x0126=>0x0127,
+                       0x0136=>0x0137, 0x012A=>0x012B, 0x038A=>0x03AF, 0x042B=>0x044B, 0x004C=>0x006C,
+                       0x0397=>0x03B7, 0x0124=>0x0125, 0x0218=>0x0219, 0x00DB=>0x00FB, 0x011E=>0x011F,
+                       0x041E=>0x043E, 0x1E40=>0x1E41, 0x039D=>0x03BD, 0x0106=>0x0107, 0x03AB=>0x03CB,
+                       0x0426=>0x0446, 0x00DE=>0x00FE, 0x00C7=>0x00E7, 0x03AA=>0x03CA, 0x0421=>0x0441,
+                       0x0412=>0x0432, 0x010E=>0x010F, 0x00D8=>0x00F8, 0x0057=>0x0077, 0x011A=>0x011B,
+                       0x0054=>0x0074, 0x004A=>0x006A, 0x040B=>0x045B, 0x0406=>0x0456, 0x0102=>0x0103,
+                       0x039B=>0x03BB, 0x00D1=>0x00F1, 0x041D=>0x043D, 0x038C=>0x03CC, 0x00C9=>0x00E9,
+                       0x00D0=>0x00F0, 0x0407=>0x0457, 0x0122=>0x0123,
+               );
+       }
+
+       $uni = utf8::to_unicode($str);
+
+       if ($uni === FALSE)
+               return FALSE;
+
+       for ($i = 0, $c = count($uni); $i < $c; $i++)
+       {
+               if (isset($UTF8_UPPER_TO_LOWER[$uni[$i]]))
+               {
+                       $uni[$i] = $UTF8_UPPER_TO_LOWER[$uni[$i]];
+               }
+       }
+
+       return utf8::from_unicode($uni);
+}
\ No newline at end of file
diff --git a/Server/system/core/utf8/strtoupper.php b/Server/system/core/utf8/strtoupper.php
new file mode 100644 (file)
index 0000000..f3ded73
--- /dev/null
@@ -0,0 +1,84 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::strtoupper
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _strtoupper($str)
+{
+       if (SERVER_UTF8)
+               return mb_strtoupper($str);
+
+       if (utf8::is_ascii($str))
+               return strtoupper($str);
+
+       static $UTF8_LOWER_TO_UPPER = NULL;
+
+       if ($UTF8_LOWER_TO_UPPER === NULL)
+       {
+               $UTF8_LOWER_TO_UPPER = array(
+                       0x0061=>0x0041, 0x03C6=>0x03A6, 0x0163=>0x0162, 0x00E5=>0x00C5, 0x0062=>0x0042,
+                       0x013A=>0x0139, 0x00E1=>0x00C1, 0x0142=>0x0141, 0x03CD=>0x038E, 0x0101=>0x0100,
+                       0x0491=>0x0490, 0x03B4=>0x0394, 0x015B=>0x015A, 0x0064=>0x0044, 0x03B3=>0x0393,
+                       0x00F4=>0x00D4, 0x044A=>0x042A, 0x0439=>0x0419, 0x0113=>0x0112, 0x043C=>0x041C,
+                       0x015F=>0x015E, 0x0144=>0x0143, 0x00EE=>0x00CE, 0x045E=>0x040E, 0x044F=>0x042F,
+                       0x03BA=>0x039A, 0x0155=>0x0154, 0x0069=>0x0049, 0x0073=>0x0053, 0x1E1F=>0x1E1E,
+                       0x0135=>0x0134, 0x0447=>0x0427, 0x03C0=>0x03A0, 0x0438=>0x0418, 0x00F3=>0x00D3,
+                       0x0440=>0x0420, 0x0454=>0x0404, 0x0435=>0x0415, 0x0449=>0x0429, 0x014B=>0x014A,
+                       0x0431=>0x0411, 0x0459=>0x0409, 0x1E03=>0x1E02, 0x00F6=>0x00D6, 0x00F9=>0x00D9,
+                       0x006E=>0x004E, 0x0451=>0x0401, 0x03C4=>0x03A4, 0x0443=>0x0423, 0x015D=>0x015C,
+                       0x0453=>0x0403, 0x03C8=>0x03A8, 0x0159=>0x0158, 0x0067=>0x0047, 0x00E4=>0x00C4,
+                       0x03AC=>0x0386, 0x03AE=>0x0389, 0x0167=>0x0166, 0x03BE=>0x039E, 0x0165=>0x0164,
+                       0x0117=>0x0116, 0x0109=>0x0108, 0x0076=>0x0056, 0x00FE=>0x00DE, 0x0157=>0x0156,
+                       0x00FA=>0x00DA, 0x1E61=>0x1E60, 0x1E83=>0x1E82, 0x00E2=>0x00C2, 0x0119=>0x0118,
+                       0x0146=>0x0145, 0x0070=>0x0050, 0x0151=>0x0150, 0x044E=>0x042E, 0x0129=>0x0128,
+                       0x03C7=>0x03A7, 0x013E=>0x013D, 0x0442=>0x0422, 0x007A=>0x005A, 0x0448=>0x0428,
+                       0x03C1=>0x03A1, 0x1E81=>0x1E80, 0x016D=>0x016C, 0x00F5=>0x00D5, 0x0075=>0x0055,
+                       0x0177=>0x0176, 0x00FC=>0x00DC, 0x1E57=>0x1E56, 0x03C3=>0x03A3, 0x043A=>0x041A,
+                       0x006D=>0x004D, 0x016B=>0x016A, 0x0171=>0x0170, 0x0444=>0x0424, 0x00EC=>0x00CC,
+                       0x0169=>0x0168, 0x03BF=>0x039F, 0x006B=>0x004B, 0x00F2=>0x00D2, 0x00E0=>0x00C0,
+                       0x0434=>0x0414, 0x03C9=>0x03A9, 0x1E6B=>0x1E6A, 0x00E3=>0x00C3, 0x044D=>0x042D,
+                       0x0436=>0x0416, 0x01A1=>0x01A0, 0x010D=>0x010C, 0x011D=>0x011C, 0x00F0=>0x00D0,
+                       0x013C=>0x013B, 0x045F=>0x040F, 0x045A=>0x040A, 0x00E8=>0x00C8, 0x03C5=>0x03A5,
+                       0x0066=>0x0046, 0x00FD=>0x00DD, 0x0063=>0x0043, 0x021B=>0x021A, 0x00EA=>0x00CA,
+                       0x03B9=>0x0399, 0x017A=>0x0179, 0x00EF=>0x00CF, 0x01B0=>0x01AF, 0x0065=>0x0045,
+                       0x03BB=>0x039B, 0x03B8=>0x0398, 0x03BC=>0x039C, 0x045C=>0x040C, 0x043F=>0x041F,
+                       0x044C=>0x042C, 0x00FE=>0x00DE, 0x00F0=>0x00D0, 0x1EF3=>0x1EF2, 0x0068=>0x0048,
+                       0x00EB=>0x00CB, 0x0111=>0x0110, 0x0433=>0x0413, 0x012F=>0x012E, 0x00E6=>0x00C6,
+                       0x0078=>0x0058, 0x0161=>0x0160, 0x016F=>0x016E, 0x03B1=>0x0391, 0x0457=>0x0407,
+                       0x0173=>0x0172, 0x00FF=>0x0178, 0x006F=>0x004F, 0x043B=>0x041B, 0x03B5=>0x0395,
+                       0x0445=>0x0425, 0x0121=>0x0120, 0x017E=>0x017D, 0x017C=>0x017B, 0x03B6=>0x0396,
+                       0x03B2=>0x0392, 0x03AD=>0x0388, 0x1E85=>0x1E84, 0x0175=>0x0174, 0x0071=>0x0051,
+                       0x0437=>0x0417, 0x1E0B=>0x1E0A, 0x0148=>0x0147, 0x0105=>0x0104, 0x0458=>0x0408,
+                       0x014D=>0x014C, 0x00ED=>0x00CD, 0x0079=>0x0059, 0x010B=>0x010A, 0x03CE=>0x038F,
+                       0x0072=>0x0052, 0x0430=>0x0410, 0x0455=>0x0405, 0x0452=>0x0402, 0x0127=>0x0126,
+                       0x0137=>0x0136, 0x012B=>0x012A, 0x03AF=>0x038A, 0x044B=>0x042B, 0x006C=>0x004C,
+                       0x03B7=>0x0397, 0x0125=>0x0124, 0x0219=>0x0218, 0x00FB=>0x00DB, 0x011F=>0x011E,
+                       0x043E=>0x041E, 0x1E41=>0x1E40, 0x03BD=>0x039D, 0x0107=>0x0106, 0x03CB=>0x03AB,
+                       0x0446=>0x0426, 0x00FE=>0x00DE, 0x00E7=>0x00C7, 0x03CA=>0x03AA, 0x0441=>0x0421,
+                       0x0432=>0x0412, 0x010F=>0x010E, 0x00F8=>0x00D8, 0x0077=>0x0057, 0x011B=>0x011A,
+                       0x0074=>0x0054, 0x006A=>0x004A, 0x045B=>0x040B, 0x0456=>0x0406, 0x0103=>0x0102,
+                       0x03BB=>0x039B, 0x00F1=>0x00D1, 0x043D=>0x041D, 0x03CC=>0x038C, 0x00E9=>0x00C9,
+                       0x00F0=>0x00D0, 0x0457=>0x0407, 0x0123=>0x0122,
+               );
+       }
+
+       $uni = utf8::to_unicode($str);
+
+       if ($uni === FALSE)
+               return FALSE;
+
+       for ($i = 0, $c = count($uni); $i < $c; $i++)
+       {
+               if (isset($UTF8_LOWER_TO_UPPER[$uni[$i]]))
+               {
+                       $uni[$i] = $UTF8_LOWER_TO_UPPER[$uni[$i]];
+               }
+       }
+
+       return utf8::from_unicode($uni);
+}
\ No newline at end of file
diff --git a/Server/system/core/utf8/substr.php b/Server/system/core/utf8/substr.php
new file mode 100644 (file)
index 0000000..daf66b8
--- /dev/null
@@ -0,0 +1,75 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::substr
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _substr($str, $offset, $length = NULL)
+{
+       if (SERVER_UTF8)
+               return ($length === NULL) ? mb_substr($str, $offset) : mb_substr($str, $offset, $length);
+
+       if (utf8::is_ascii($str))
+               return ($length === NULL) ? substr($str, $offset) : substr($str, $offset, $length);
+
+       // Normalize params
+       $str    = (string) $str;
+       $strlen = utf8::strlen($str);
+       $offset = (int) ($offset < 0) ? max(0, $strlen + $offset) : $offset; // Normalize to positive offset
+       $length = ($length === NULL) ? NULL : (int) $length;
+
+       // Impossible
+       if ($length === 0 OR $offset >= $strlen OR ($length < 0 AND $length <= $offset - $strlen))
+               return '';
+
+       // Whole string
+       if ($offset == 0 AND ($length === NULL OR $length >= $strlen))
+               return $str;
+
+       // Build regex
+       $regex = '^';
+
+       // Create an offset expression
+       if ($offset > 0)
+       {
+               // PCRE repeating quantifiers must be less than 65536, so repeat when necessary
+               $x = (int) ($offset / 65535);
+               $y = (int) ($offset % 65535);
+               $regex .= ($x == 0) ? '' : '(?:.{65535}){'.$x.'}';
+               $regex .= ($y == 0) ? '' : '.{'.$y.'}';
+       }
+
+       // Create a length expression
+       if ($length === NULL)
+       {
+               $regex .= '(.*)'; // No length set, grab it all
+       }
+       // Find length from the left (positive length)
+       elseif ($length > 0)
+       {
+               // Reduce length so that it can't go beyond the end of the string
+               $length = min($strlen - $offset, $length);
+
+               $x = (int) ($length / 65535);
+               $y = (int) ($length % 65535);
+               $regex .= '(';
+               $regex .= ($x == 0) ? '' : '(?:.{65535}){'.$x.'}';
+               $regex .= '.{'.$y.'})';
+       }
+       // Find length from the right (negative length)
+       else
+       {
+               $x = (int) (-$length / 65535);
+               $y = (int) (-$length % 65535);
+               $regex .= '(.*)';
+               $regex .= ($x == 0) ? '' : '(?:.{65535}){'.$x.'}';
+               $regex .= '.{'.$y.'}';
+       }
+
+       preg_match('/'.$regex.'/us', $str, $matches);
+       return $matches[1];
+}
\ No newline at end of file
diff --git a/Server/system/core/utf8/substr_replace.php b/Server/system/core/utf8/substr_replace.php
new file mode 100644 (file)
index 0000000..45e2d2a
--- /dev/null
@@ -0,0 +1,22 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::substr_replace
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _substr_replace($str, $replacement, $offset, $length = NULL)
+{
+       if (utf8::is_ascii($str))
+               return ($length === NULL) ? substr_replace($str, $replacement, $offset) : substr_replace($str, $replacement, $offset, $length);
+
+       $length = ($length === NULL) ? utf8::strlen($str) : (int) $length;
+       preg_match_all('/./us', $str, $str_array);
+       preg_match_all('/./us', $replacement, $replacement_array);
+
+       array_splice($str_array[0], $offset, $length, $replacement_array[0]);
+       return implode('', $str_array[0]);
+}
\ No newline at end of file
diff --git a/Server/system/core/utf8/to_unicode.php b/Server/system/core/utf8/to_unicode.php
new file mode 100644 (file)
index 0000000..93f741a
--- /dev/null
@@ -0,0 +1,141 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::to_unicode
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _to_unicode($str)
+{
+       $mState = 0; // cached expected number of octets after the current octet until the beginning of the next UTF8 character sequence
+       $mUcs4  = 0; // cached Unicode character
+       $mBytes = 1; // cached expected number of octets in the current sequence
+
+       $out = array();
+
+       $len = strlen($str);
+
+       for ($i = 0; $i < $len; $i++)
+       {
+               $in = ord($str[$i]);
+
+               if ($mState == 0)
+               {
+                       // When mState is zero we expect either a US-ASCII character or a
+                       // multi-octet sequence.
+                       if (0 == (0x80 & $in))
+                       {
+                               // US-ASCII, pass straight through.
+                               $out[] = $in;
+                               $mBytes = 1;
+                       }
+                       elseif (0xC0 == (0xE0 & $in))
+                       {
+                               // First octet of 2 octet sequence
+                               $mUcs4 = $in;
+                               $mUcs4 = ($mUcs4 & 0x1F) << 6;
+                               $mState = 1;
+                               $mBytes = 2;
+                       }
+                       elseif (0xE0 == (0xF0 & $in))
+                       {
+                               // First octet of 3 octet sequence
+                               $mUcs4 = $in;
+                               $mUcs4 = ($mUcs4 & 0x0F) << 12;
+                               $mState = 2;
+                               $mBytes = 3;
+                       }
+                       elseif (0xF0 == (0xF8 & $in))
+                       {
+                               // First octet of 4 octet sequence
+                               $mUcs4 = $in;
+                               $mUcs4 = ($mUcs4 & 0x07) << 18;
+                               $mState = 3;
+                               $mBytes = 4;
+                       }
+                       elseif (0xF8 == (0xFC & $in))
+                       {
+                               // First octet of 5 octet sequence.
+                               //
+                               // This is illegal because the encoded codepoint must be either
+                               // (a) not the shortest form or
+                               // (b) outside the Unicode range of 0-0x10FFFF.
+                               // Rather than trying to resynchronize, we will carry on until the end
+                               // of the sequence and let the later error handling code catch it.
+                               $mUcs4 = $in;
+                               $mUcs4 = ($mUcs4 & 0x03) << 24;
+                               $mState = 4;
+                               $mBytes = 5;
+                       }
+                       elseif (0xFC == (0xFE & $in))
+                       {
+                               // First octet of 6 octet sequence, see comments for 5 octet sequence.
+                               $mUcs4 = $in;
+                               $mUcs4 = ($mUcs4 & 1) << 30;
+                               $mState = 5;
+                               $mBytes = 6;
+                       }
+                       else
+                       {
+                               // Current octet is neither in the US-ASCII range nor a legal first octet of a multi-octet sequence.
+                               trigger_error('utf8::to_unicode: Illegal sequence identifier in UTF-8 at byte '.$i, E_USER_WARNING);
+                               return FALSE;
+                       }
+               }
+               else
+               {
+                       // When mState is non-zero, we expect a continuation of the multi-octet sequence
+                       if (0x80 == (0xC0 & $in))
+                       {
+                               // Legal continuation
+                               $shift = ($mState - 1) * 6;
+                               $tmp = $in;
+                               $tmp = ($tmp & 0x0000003F) << $shift;
+                               $mUcs4 |= $tmp;
+
+                               // End of the multi-octet sequence. mUcs4 now contains the final Unicode codepoint to be output
+                               if (0 == --$mState)
+                               {
+                                       // Check for illegal sequences and codepoints
+
+                                       // From Unicode 3.1, non-shortest form is illegal
+                                       if (((2 == $mBytes) AND ($mUcs4 < 0x0080)) OR
+                                               ((3 == $mBytes) AND ($mUcs4 < 0x0800)) OR
+                                               ((4 == $mBytes) AND ($mUcs4 < 0x10000)) OR
+                                               (4 < $mBytes) OR
+                                               // From Unicode 3.2, surrogate characters are illegal
+                                               (($mUcs4 & 0xFFFFF800) == 0xD800) OR
+                                               // Codepoints outside the Unicode range are illegal
+                                               ($mUcs4 > 0x10FFFF))
+                                       {
+                                               trigger_error('utf8::to_unicode: Illegal sequence or codepoint in UTF-8 at byte '.$i, E_USER_WARNING);
+                                               return FALSE;
+                                       }
+
+                                       if (0xFEFF != $mUcs4)
+                                       {
+                                               // BOM is legal but we don't want to output it
+                                               $out[] = $mUcs4;
+                                       }
+
+                                       // Initialize UTF-8 cache
+                                       $mState = 0;
+                                       $mUcs4  = 0;
+                                       $mBytes = 1;
+                               }
+                       }
+                       else
+                       {
+                               // ((0xC0 & (*in) != 0x80) AND (mState != 0))
+                               // Incomplete multi-octet sequence
+                               trigger_error('utf8::to_unicode: Incomplete multi-octet sequence in UTF-8 at byte '.$i, E_USER_WARNING);
+                               return FALSE;
+                       }
+               }
+       }
+
+       return $out;
+}
\ No newline at end of file
diff --git a/Server/system/core/utf8/transliterate_to_ascii.php b/Server/system/core/utf8/transliterate_to_ascii.php
new file mode 100644 (file)
index 0000000..07461fb
--- /dev/null
@@ -0,0 +1,77 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::transliterate_to_ascii
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _transliterate_to_ascii($str, $case = 0)
+{
+       static $UTF8_LOWER_ACCENTS = NULL;
+       static $UTF8_UPPER_ACCENTS = NULL;
+
+       if ($case <= 0)
+       {
+               if ($UTF8_LOWER_ACCENTS === NULL)
+               {
+                       $UTF8_LOWER_ACCENTS = array(
+                               'à' => 'a',  'ô' => 'o',  'ď' => 'd',  'ḟ' => 'f',  'ë' => 'e',  'š' => 's',  'ơ' => 'o',
+                               'ß' => 'ss', 'ă' => 'a',  'ř' => 'r',  'ț' => 't',  'ň' => 'n',  'ā' => 'a',  'ķ' => 'k',
+                               'ŝ' => 's',  'ỳ' => 'y',  'ņ' => 'n',  'ĺ' => 'l',  'ħ' => 'h',  'ṗ' => 'p',  'ó' => 'o',
+                               'ú' => 'u',  'ě' => 'e',  'é' => 'e',  'ç' => 'c',  'ẁ' => 'w',  'ċ' => 'c',  'õ' => 'o',
+                               'ṡ' => 's',  'ø' => 'o',  'ģ' => 'g',  'ŧ' => 't',  'ș' => 's',  'ė' => 'e',  'ĉ' => 'c',
+                               'ś' => 's',  'î' => 'i',  'ű' => 'u',  'ć' => 'c',  'ę' => 'e',  'ŵ' => 'w',  'ṫ' => 't',
+                               'ū' => 'u',  'č' => 'c',  'ö' => 'o',  'è' => 'e',  'ŷ' => 'y',  'ą' => 'a',  'ł' => 'l',
+                               'ų' => 'u',  'ů' => 'u',  'ş' => 's',  'ğ' => 'g',  'ļ' => 'l',  'ƒ' => 'f',  'ž' => 'z',
+                               'ẃ' => 'w',  'ḃ' => 'b',  'å' => 'a',  'ì' => 'i',  'ï' => 'i',  'ḋ' => 'd',  'ť' => 't',
+                               'ŗ' => 'r',  'ä' => 'a',  'í' => 'i',  'ŕ' => 'r',  'ê' => 'e',  'ü' => 'u',  'ò' => 'o',
+                               'ē' => 'e',  'ñ' => 'n',  'ń' => 'n',  'ĥ' => 'h',  'ĝ' => 'g',  'đ' => 'd',  'ĵ' => 'j',
+                               'ÿ' => 'y',  'ũ' => 'u',  'ŭ' => 'u',  'ư' => 'u',  'ţ' => 't',  'ý' => 'y',  'ő' => 'o',
+                               'â' => 'a',  'ľ' => 'l',  'ẅ' => 'w',  'ż' => 'z',  'ī' => 'i',  'ã' => 'a',  'ġ' => 'g',
+                               'ṁ' => 'm',  'ō' => 'o',  'ĩ' => 'i',  'ù' => 'u',  'į' => 'i',  'ź' => 'z',  'á' => 'a',
+                               'û' => 'u',  'þ' => 'th', 'ð' => 'dh', 'æ' => 'ae', 'µ' => 'u',  'ĕ' => 'e',
+                       );
+               }
+
+               $str = str_replace(
+                       array_keys($UTF8_LOWER_ACCENTS),
+                       array_values($UTF8_LOWER_ACCENTS),
+                       $str
+               );
+       }
+
+       if ($case >= 0)
+       {
+               if ($UTF8_UPPER_ACCENTS === NULL)
+               {
+                       $UTF8_UPPER_ACCENTS = array(
+                               'À' => 'A',  'Ô' => 'O',  'Ď' => 'D',  'Ḟ' => 'F',  'Ë' => 'E',  'Š' => 'S',  'Ơ' => 'O',
+                               'Ă' => 'A',  'Ř' => 'R',  'Ț' => 'T',  'Ň' => 'N',  'Ā' => 'A',  'Ķ' => 'K',  'Ĕ' => 'E',
+                               'Ŝ' => 'S',  'Ỳ' => 'Y',  'Ņ' => 'N',  'Ĺ' => 'L',  'Ħ' => 'H',  'Ṗ' => 'P',  'Ó' => 'O',
+                               'Ú' => 'U',  'Ě' => 'E',  'É' => 'E',  'Ç' => 'C',  'Ẁ' => 'W',  'Ċ' => 'C',  'Õ' => 'O',
+                               'Ṡ' => 'S',  'Ø' => 'O',  'Ģ' => 'G',  'Ŧ' => 'T',  'Ș' => 'S',  'Ė' => 'E',  'Ĉ' => 'C',
+                               'Ś' => 'S',  'Î' => 'I',  'Ű' => 'U',  'Ć' => 'C',  'Ę' => 'E',  'Ŵ' => 'W',  'Ṫ' => 'T',
+                               'Ū' => 'U',  'Č' => 'C',  'Ö' => 'O',  'È' => 'E',  'Ŷ' => 'Y',  'Ą' => 'A',  'Ł' => 'L',
+                               'Ų' => 'U',  'Ů' => 'U',  'Ş' => 'S',  'Ğ' => 'G',  'Ļ' => 'L',  'Ƒ' => 'F',  'Ž' => 'Z',
+                               'Ẃ' => 'W',  'Ḃ' => 'B',  'Å' => 'A',  'Ì' => 'I',  'Ï' => 'I',  'Ḋ' => 'D',  'Ť' => 'T',
+                               'Ŗ' => 'R',  'Ä' => 'A',  'Í' => 'I',  'Ŕ' => 'R',  'Ê' => 'E',  'Ü' => 'U',  'Ò' => 'O',
+                               'Ē' => 'E',  'Ñ' => 'N',  'Ń' => 'N',  'Ĥ' => 'H',  'Ĝ' => 'G',  'Đ' => 'D',  'Ĵ' => 'J',
+                               'Ÿ' => 'Y',  'Ũ' => 'U',  'Ŭ' => 'U',  'Ư' => 'U',  'Ţ' => 'T',  'Ý' => 'Y',  'Ő' => 'O',
+                               'Â' => 'A',  'Ľ' => 'L',  'Ẅ' => 'W',  'Ż' => 'Z',  'Ī' => 'I',  'Ã' => 'A',  'Ġ' => 'G',
+                               'Ṁ' => 'M',  'Ō' => 'O',  'Ĩ' => 'I',  'Ù' => 'U',  'Į' => 'I',  'Ź' => 'Z',  'Á' => 'A',
+                               'Û' => 'U',  'Þ' => 'Th', 'Ð' => 'Dh', 'Æ' => 'Ae',
+                       );
+               }
+
+               $str = str_replace(
+                       array_keys($UTF8_UPPER_ACCENTS),
+                       array_values($UTF8_UPPER_ACCENTS),
+                       $str
+               );
+       }
+
+       return $str;
+}
\ No newline at end of file
diff --git a/Server/system/core/utf8/trim.php b/Server/system/core/utf8/trim.php
new file mode 100644 (file)
index 0000000..7434102
--- /dev/null
@@ -0,0 +1,17 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::trim
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _trim($str, $charlist = NULL)
+{
+       if ($charlist === NULL)
+               return trim($str);
+
+       return utf8::ltrim(utf8::rtrim($str, $charlist), $charlist);
+}
\ No newline at end of file
diff --git a/Server/system/core/utf8/ucfirst.php b/Server/system/core/utf8/ucfirst.php
new file mode 100644 (file)
index 0000000..81a4b38
--- /dev/null
@@ -0,0 +1,18 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::ucfirst
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _ucfirst($str)
+{
+       if (utf8::is_ascii($str))
+               return ucfirst($str);
+
+       preg_match('/^(.?)(.*)$/us', $str, $matches);
+       return utf8::strtoupper($matches[1]).$matches[2];
+}
\ No newline at end of file
diff --git a/Server/system/core/utf8/ucwords.php b/Server/system/core/utf8/ucwords.php
new file mode 100644 (file)
index 0000000..2d4c94b
--- /dev/null
@@ -0,0 +1,26 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::ucwords
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _ucwords($str)
+{
+       if (SERVER_UTF8)
+               return mb_convert_case($str, MB_CASE_TITLE);
+
+       if (utf8::is_ascii($str))
+               return ucwords($str);
+
+       // [\x0c\x09\x0b\x0a\x0d\x20] matches form feeds, horizontal tabs, vertical tabs, linefeeds and carriage returns.
+       // This corresponds to the definition of a 'word' defined at http://php.net/ucwords
+       return preg_replace(
+               '/(?<=^|[\x0c\x09\x0b\x0a\x0d\x20])[^\x0c\x09\x0b\x0a\x0d\x20]/ue',
+               'utf8::strtoupper(\'$0\')',
+               $str
+       );
+}
\ No newline at end of file
diff --git a/Server/system/fonts/DejaVuSerif.ttf b/Server/system/fonts/DejaVuSerif.ttf
new file mode 100644 (file)
index 0000000..b7f4482
Binary files /dev/null and b/Server/system/fonts/DejaVuSerif.ttf differ
diff --git a/Server/system/fonts/LICENSE b/Server/system/fonts/LICENSE
new file mode 100644 (file)
index 0000000..254e2cc
--- /dev/null
@@ -0,0 +1,99 @@
+Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
+Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below)
+
+Bitstream Vera Fonts Copyright
+------------------------------
+
+Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is
+a trademark of Bitstream, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of the fonts accompanying this license ("Fonts") and associated
+documentation files (the "Font Software"), to reproduce and distribute the
+Font Software, including without limitation the rights to use, copy, merge,
+publish, distribute, and/or sell copies of the Font Software, and to permit
+persons to whom the Font Software is furnished to do so, subject to the
+following conditions:
+
+The above copyright and trademark notices and this permission notice shall
+be included in all copies of one or more of the Font Software typefaces.
+
+The Font Software may be modified, altered, or added to, and in particular
+the designs of glyphs or characters in the Fonts may be modified and
+additional glyphs or characters may be added to the Fonts, only if the fonts
+are renamed to names not containing either the words "Bitstream" or the word
+"Vera".
+
+This License becomes null and void to the extent applicable to Fonts or Font
+Software that has been modified and is distributed under the "Bitstream
+Vera" names.
+
+The Font Software may be sold as part of a larger software package but no
+copy of one or more of the Font Software typefaces may be sold by itself.
+
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
+TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME
+FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING
+ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE
+FONT SOFTWARE.
+
+Except as contained in this notice, the names of Gnome, the Gnome
+Foundation, and Bitstream Inc., shall not be used in advertising or
+otherwise to promote the sale, use or other dealings in this Font Software
+without prior written authorization from the Gnome Foundation or Bitstream
+Inc., respectively. For further information, contact: fonts at gnome dot
+org. 
+
+Arev Fonts Copyright
+------------------------------
+
+Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the fonts accompanying this license ("Fonts") and
+associated documentation files (the "Font Software"), to reproduce
+and distribute the modifications to the Bitstream Vera Font Software,
+including without limitation the rights to use, copy, merge, publish,
+distribute, and/or sell copies of the Font Software, and to permit
+persons to whom the Font Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright and trademark notices and this permission notice
+shall be included in all copies of one or more of the Font Software
+typefaces.
+
+The Font Software may be modified, altered, or added to, and in
+particular the designs of glyphs or characters in the Fonts may be
+modified and additional glyphs or characters may be added to the
+Fonts, only if the fonts are renamed to names not containing either
+the words "Tavmjong Bah" or the word "Arev".
+
+This License becomes null and void to the extent applicable to Fonts
+or Font Software that has been modified and is distributed under the 
+"Tavmjong Bah Arev" names.
+
+The Font Software may be sold as part of a larger software package but
+no copy of one or more of the Font Software typefaces may be sold by
+itself.
+
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL
+TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
+
+Except as contained in this notice, the name of Tavmjong Bah shall not
+be used in advertising or otherwise to promote the sale, use or other
+dealings in this Font Software without prior written authorization
+from Tavmjong Bah. For further information, contact: tavmjong @ free
+. fr.
+
+$Id: LICENSE 2133 2007-11-28 02:46:28Z lechimp $
diff --git a/Server/system/helpers/arr.php b/Server/system/helpers/arr.php
new file mode 100644 (file)
index 0000000..9570c4b
--- /dev/null
@@ -0,0 +1,312 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Array helper class.
+ *
+ * $Id: arr.php 4346 2009-05-11 17:08:15Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class 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)
+               {
+                       if (isset($search[$key]))
+                       {
+                               $found[$key] = $search[$key];
+                       }
+                       else
+                       {
+                               $found[$key] = NULL;
+                       }
+               }
+
+               return $found;
+       }
+
+       /**
+        * 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;
+       }
+
+       /**
+        * @param mixed $needle     the value to search for
+        * @param array $haystack   an array of values to search in
+        * @param boolean $sort     sort the array now
+        * @return integer|FALSE    the index of the match or FALSE when not found
+        */
+       public static function binary_search($needle, $haystack, $sort = FALSE)
+       {
+               if ($sort)
+               {
+                       sort($haystack);
+               }
+
+               $high = count($haystack) - 1;
+               $low = 0;
+
+               while ($low <= $high)
+               {
+                       $mid = ($low + $high) >> 1;
+
+                       if ($haystack[$mid] < $needle)
+                       {
+                               $low = $mid + 1;
+                       }
+                       elseif ($haystack[$mid] > $needle)
+                       {
+                               $high = $mid - 1;
+                       }
+                       else
+                       {
+                               return $mid;
+                       }
+               }
+
+               return FALSE;
+       }
+
+
+       /**
+        * Emulates array_merge_recursive, but appends numeric keys and replaces
+        * associative keys, instead of appending all keys.
+        *
+        * @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;
+       }
+
+       /**
+        * Fill an array with a range of numbers.
+        *
+        * @param   integer  stepping
+        * @param   integer  ending number
+        * @return  array
+        */
+       public static function range($step = 10, $max = 100)
+       {
+               if ($step < 1)
+                       return array();
+
+               $array = array();
+               for ($i = $step; $i <= $max; $i += $step)
+               {
+                       $array[$i] = $i;
+               }
+
+               return $array;
+       }
+
+       /**
+        * Recursively convert an array to an object.
+        *
+        * @param   array   array to convert
+        * @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;
+       }
+
+} // End arr
diff --git a/Server/system/helpers/cookie.php b/Server/system/helpers/cookie.php
new file mode 100644 (file)
index 0000000..901b6d8
--- /dev/null
@@ -0,0 +1,84 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Cookie helper class.
+ *
+ * $Id: cookie.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class 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];
+                       }
+               }
+
+               // Expiration timestamp
+               $expire = ($expire == 0) ? 0 : time() + (int) $expire;
+
+               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, $default = NULL, $xss_clean = FALSE)
+       {
+               return Input::instance()->cookie($name, $default, $xss_clean);
+       }
+
+       /**
+        * 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)
+       {
+               if ( ! isset($_COOKIE[$name]))
+                       return FALSE;
+
+               // 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);
+       }
+
+} // End cookie
\ No newline at end of file
diff --git a/Server/system/helpers/date.php b/Server/system/helpers/date.php
new file mode 100644 (file)
index 0000000..7d5a9ab
--- /dev/null
@@ -0,0 +1,405 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Date helper class.
+ *
+ * $Id: date.php 4316 2009-05-04 01:03:54Z kiall $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+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 that to find the offset of
+        * @param   string|boolean  timezone used as the baseline
+        * @return  integer
+        */
+       public static function offset($remote, $local = TRUE)
+       {
+               static $offsets;
+
+               // Default values
+               $remote = (string) $remote;
+               $local  = ($local === TRUE) ? date_default_timezone_get() : (string) $local;
+
+               // Cache key name
+               $cache = $remote.$local;
+
+               if (empty($offsets[$cache]))
+               {
+                       // Create timezone objects
+                       $remote = new DateTimeZone($remote);
+                       $local  = new DateTimeZone($local);
+
+                       // Create date objects from timezones
+                       $time_there = new DateTime('now', $remote);
+                       $time_here  = new DateTime('now', $local);
+
+                       // Find the offset
+                       $offsets[$cache] = $remote->getOffset($time_there) - $local->getOffset($time_here);
+               }
+
+               return $offsets[$cache];
+       }
+
+       /**
+        * 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/Server/system/helpers/download.php b/Server/system/helpers/download.php
new file mode 100644 (file)
index 0000000..49fed42
--- /dev/null
@@ -0,0 +1,105 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Download helper class.
+ *
+ * $Id: download.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class download_Core {
+
+       /**
+        * Force a download of a file to the user's browser. This function is
+        * binary-safe and will work with any MIME type that Kohana is aware of.
+        *
+        * @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)
+       {
+               if (empty($filename))
+                       return FALSE;
+
+               if (is_file($filename))
+               {
+                       // Get the real path
+                       $filepath = str_replace('\\', '/', realpath($filename));
+
+                       // Set filesize
+                       $filesize = filesize($filepath);
+
+                       // Get filename
+                       $filename = substr(strrchr('/'.$filepath, '/'), 1);
+
+                       // Get extension
+                       $extension = strtolower(substr(strrchr($filepath, '.'), 1));
+               }
+               else
+               {
+                       // Get filesize
+                       $filesize = strlen($data);
+
+                       // Make sure the filename does not have directory info
+                       $filename = substr(strrchr('/'.$filename, '/'), 1);
+
+                       // Get extension
+                       $extension = strtolower(substr(strrchr($filename, '.'), 1));
+               }
+
+               // Get the mime type of the file
+               $mime = Kohana::config('mimes.'.$extension);
+
+               if (empty($mime))
+               {
+                       // Set a default mime if none was found
+                       $mime = array('application/octet-stream');
+               }
+
+               // Generate the server headers
+               header('Content-Type: '.$mime[0]);
+               header('Content-Disposition: attachment; filename="'.(empty($nicename) ? $filename : $nicename).'"');
+               header('Content-Transfer-Encoding: binary');
+               header('Content-Length: '.sprintf('%d', $filesize));
+
+               // More caching prevention
+               header('Expires: 0');
+
+               if (Kohana::user_agent('browser') === 'Internet Explorer')
+               {
+                       // Send IE headers
+                       header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
+                       header('Pragma: public');
+               }
+               else
+               {
+                       // Send normal headers
+                       header('Pragma: no-cache');
+               }
+
+               // Clear the output buffer
+               Kohana::close_buffers(FALSE);
+
+               if (isset($filepath))
+               {
+                       // Open the file
+                       $handle = fopen($filepath, 'rb');
+
+                       // Send the file data
+                       fpassthru($handle);
+
+                       // Close the file
+                       fclose($handle);
+               }
+               else
+               {
+                       // Send the file data
+                       echo $data;
+               }
+       }
+
+} // End download
\ No newline at end of file
diff --git a/Server/system/helpers/email.php b/Server/system/helpers/email.php
new file mode 100644 (file)
index 0000000..fb222d0
--- /dev/null
@@ -0,0 +1,181 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Email helper class.
+ *
+ * $Id: email.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class email_Core {
+
+       // SwiftMailer instance
+       protected static $mail;
+
+       /**
+        * Creates a SwiftMailer instance.
+        *
+        * @param   string  DSN connection string
+        * @return  object  Swift object
+        */
+       public static function connect($config = NULL)
+       {
+               if ( ! class_exists('Swift', FALSE))
+               {
+                       // Load SwiftMailer
+                       require Kohana::find_file('vendor', 'swift/Swift');
+
+                       // Register the Swift ClassLoader as an autoload
+                       spl_autoload_register(array('Swift_ClassLoader', 'load'));
+               }
+
+               // Load default configuration
+               ($config === NULL) and $config = Kohana::config('email');
+
+               switch ($config['driver'])
+               {
+                       case 'smtp':
+                               // Set port
+                               $port = empty($config['options']['port']) ? NULL : (int) $config['options']['port'];
+
+                               if (empty($config['options']['encryption']))
+                               {
+                                       // No encryption
+                                       $encryption = Swift_Connection_SMTP::ENC_OFF;
+                               }
+                               else
+                               {
+                                       // Set encryption
+                                       switch (strtolower($config['options']['encryption']))
+                                       {
+                                               case 'tls': $encryption = Swift_Connection_SMTP::ENC_TLS; break;
+                                               case 'ssl': $encryption = Swift_Connection_SMTP::ENC_SSL; break;
+                                       }
+                               }
+
+                               // Create a SMTP connection
+                               $connection = new Swift_Connection_SMTP($config['options']['hostname'], $port, $encryption);
+
+                               // Do authentication, if part of the DSN
+                               empty($config['options']['username']) or $connection->setUsername($config['options']['username']);
+                               empty($config['options']['password']) or $connection->setPassword($config['options']['password']);
+
+                               if ( ! empty($config['options']['auth']))
+                               {
+                                       // Get the class name and params
+                                       list ($class, $params) = arr::callback_string($config['options']['auth']);
+
+                                       if ($class === 'PopB4Smtp')
+                                       {
+                                               // Load the PopB4Smtp class manually, due to its odd filename
+                                               require Kohana::find_file('vendor', 'swift/Swift/Authenticator/$PopB4Smtp$');
+                                       }
+
+                                       // Prepare the class name for auto-loading
+                                       $class = 'Swift_Authenticator_'.$class;
+
+                                       // Attach the authenticator
+                                       $connection->attachAuthenticator(($params === NULL) ? new $class : new $class($params[0]));
+                               }
+
+                               // Set the timeout to 5 seconds
+                               $connection->setTimeout(empty($config['options']['timeout']) ? 5 : (int) $config['options']['timeout']);
+                       break;
+                       case 'sendmail':
+                               // Create a sendmail connection
+                               $connection = new Swift_Connection_Sendmail
+                               (
+                                       empty($config['options']) ? Swift_Connection_Sendmail::AUTO_DETECT : $config['options']
+                               );
+
+                               // Set the timeout to 5 seconds
+                               $connection->setTimeout(5);
+                       break;
+                       default:
+                               // Use the native connection
+                               $connection = new Swift_Connection_NativeMail($config['options']);
+                       break;
+               }
+
+               // Create the SwiftMailer instance
+               return email::$mail = new Swift($connection);
+       }
+
+       /**
+        * Send an email message.
+        *
+        * @param   string|array  recipient email (and name), or an array of To, Cc, Bcc names
+        * @param   string|array  sender email (and name)
+        * @param   string        message subject
+        * @param   string        message body
+        * @param   boolean       send email as HTML
+        * @return  integer       number of emails sent
+        */
+       public static function send($to, $from, $subject, $message, $html = FALSE)
+       {
+               // Connect to SwiftMailer
+               (email::$mail === NULL) and email::connect();
+
+               // Determine the message type
+               $html = ($html === TRUE) ? 'text/html' : 'text/plain';
+
+               // Create the message
+               $message = new Swift_Message($subject, $message, $html, '8bit', 'utf-8');
+
+               if (is_string($to))
+               {
+                       // Single recipient
+                       $recipients = new Swift_Address($to);
+               }
+               elseif (is_array($to))
+               {
+                       if (isset($to[0]) AND isset($to[1]))
+                       {
+                               // Create To: address set
+                               $to = array('to' => $to);
+                       }
+
+                       // Create a list of recipients
+                       $recipients = new Swift_RecipientList;
+
+                       foreach ($to as $method => $set)
+                       {
+                               if ( ! in_array($method, array('to', 'cc', 'bcc')))
+                               {
+                                       // Use To: by default
+                                       $method = 'to';
+                               }
+
+                               // Create method name
+                               $method = 'add'.ucfirst($method);
+
+                               if (is_array($set))
+                               {
+                                       // Add a recipient with name
+                                       $recipients->$method($set[0], $set[1]);
+                               }
+                               else
+                               {
+                                       // Add a recipient without name
+                                       $recipients->$method($set);
+                               }
+                       }
+               }
+
+               if (is_string($from))
+               {
+                       // From without a name
+                       $from = new Swift_Address($from);
+               }
+               elseif (is_array($from))
+               {
+                       // From with a name
+                       $from = new Swift_Address($from[0], $from[1]);
+               }
+
+               return email::$mail->send($message, $recipients, $from);
+       }
+
+} // End email
\ No newline at end of file
diff --git a/Server/system/helpers/expires.php b/Server/system/helpers/expires.php
new file mode 100644 (file)
index 0000000..c43cc0c
--- /dev/null
@@ -0,0 +1,111 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Controls headers that effect client caching of pages
+ *
+ * $Id: expires.php 4272 2009-04-25 21:47:26Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class expires_Core {
+
+       /**
+        * Sets the amount of time before a page expires
+        *
+        * @param  integer Seconds before the page expires 
+        * @return boolean
+        */
+       public static function set($seconds = 60)
+       {
+               if (expires::check_headers())
+               {
+                       $now = $expires = time();
+
+                       // Set the expiration timestamp
+                       $expires += $seconds;
+
+                       // Send headers
+                       header('Last-Modified: '.gmdate('D, d M Y H:i:s T', $now));
+                       header('Expires: '.gmdate('D, d M Y H:i:s T', $expires));
+                       header('Cache-Control: max-age='.$seconds);
+
+                       return $expires;
+               }
+
+               return FALSE;
+       }
+
+       /**
+        * Checks to see if a page should be updated or send Not Modified status
+        *
+        * @param   integer  Seconds added to the modified time received to calculate what should be sent
+        * @return  bool     FALSE when the request needs to be updated
+        */
+       public static function check($seconds = 60)
+       {
+               if ( ! empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) AND expires::check_headers())
+               {
+                       if (($strpos = strpos($_SERVER['HTTP_IF_MODIFIED_SINCE'], ';')) !== FALSE)
+                       {
+                               // IE6 and perhaps other IE versions send length too, compensate here
+                               $mod_time = substr($_SERVER['HTTP_IF_MODIFIED_SINCE'], 0, $strpos);
+                       }
+                       else
+                       {
+                               $mod_time = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
+                       }
+
+                       $mod_time = strtotime($mod_time);
+                       $mod_time_diff = $mod_time + $seconds - time();
+
+                       if ($mod_time_diff > 0)
+                       {
+                               // Re-send headers
+                               header('Last-Modified: '.gmdate('D, d M Y H:i:s T', $mod_time));
+                               header('Expires: '.gmdate('D, d M Y H:i:s T', time() + $mod_time_diff));
+                               header('Cache-Control: max-age='.$mod_time_diff);
+                               header('Status: 304 Not Modified', TRUE, 304);
+
+                               // Prevent any output
+                               Event::add('system.display', array('expires', 'prevent_output'));
+
+                               // Exit to prevent other output
+                               exit;
+                       }
+               }
+
+               return FALSE;
+       }
+
+       /**
+        * Check headers already created to not step on download or Img_lib's feet
+        *
+        * @return boolean
+        */
+       public static function check_headers()
+       {
+               foreach (headers_list() as $header)
+               {
+                       if ((session_cache_limiter() == '' AND stripos($header, 'Last-Modified:') === 0)
+                           OR stripos($header, 'Expires:') === 0)
+                       {
+                               return FALSE;
+                       }
+               }
+
+               return TRUE;
+       }
+
+       /**
+        * Prevent any output from being displayed. Executed during system.display.
+        *
+        * @return  void
+        */
+       public static function prevent_output()
+       {
+               Kohana::$output = '';
+       }
+
+} // End expires
\ No newline at end of file
diff --git a/Server/system/helpers/feed.php b/Server/system/helpers/feed.php
new file mode 100644 (file)
index 0000000..74bb2f6
--- /dev/null
@@ -0,0 +1,122 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Feed helper class.
+ *
+ * $Id: feed.php 4152 2009-04-03 23:26:23Z ixmatus $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+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/Server/system/helpers/file.php b/Server/system/helpers/file.php
new file mode 100644 (file)
index 0000000..b1b7174
--- /dev/null
@@ -0,0 +1,186 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * File helper class.
+ *
+ * $Id: file.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class 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/Server/system/helpers/form.php b/Server/system/helpers/form.php
new file mode 100644 (file)
index 0000000..ce8767c
--- /dev/null
@@ -0,0 +1,542 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Form helper class.
+ *
+ * $Id: form.php 4291 2009-04-29 22:51:58Z kiall $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+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
+        * @return  string
+        */
+       public static function open($action = NULL, $attr = array(), $hidden = 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);
+               }
+               elseif (strpos($action, '://') === FALSE)
+               {
+                       // Make the action URI into a URL
+                       $action = url::site($action);
+               }
+
+               // 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);
+       }
+
+       /**
+        * Generates a fieldset opening tag.
+        *
+        * @param   array   html attributes
+        * @param   string  a string to be attached to the end of the attributes
+        * @return  string
+        */
+       public static function open_fieldset($data = NULL, $extra = '')
+       {
+               return '<fieldset'.html::attributes((array) $data).' '.$extra.'>'."\n";
+       }
+
+       /**
+        * Generates a fieldset closing tag.
+        *
+        * @return  string
+        */
+       public static function close_fieldset()
+       {
+               return '</fieldset>'."\n";
+       }
+
+       /**
+        * Generates a legend tag for use with a fieldset.
+        *
+        * @param   string  legend text
+        * @param   array   HTML attributes
+        * @param   string  a string to be attached to the end of the attributes
+        * @return  string
+        */
+       public static function legend($text = '', $data = NULL, $extra = '')
+       {
+               return '<legend'.form::attributes((array) $data).' '.$extra.'>'.$text.'</legend>'."\n";
+       }
+
+       /**
+        * Generates hidden form fields.
+        * You can pass a simple key/value string or an associative array with multiple values.
+        *
+        * @param   string|array  input name (string) or key/value pairs (array)
+        * @param   string        input value, if using an input name
+        * @return  string
+        */
+       public static function hidden($data, $value = '')
+       {
+               if ( ! is_array($data))
+               {
+                       $data = array
+                       (
+                               $data => $value
+                       );
+               }
+
+               $input = '';
+               foreach ($data as $name => $value)
+               {
+                       $attr = array
+                       (
+                               'type'  => 'hidden',
+                               'name'  => $name,
+                               'value' => $value
+                       );
+
+                       $input .= form::input($attr)."\n";
+               }
+
+               return $input;
+       }
+
+       /**
+        * 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);
+               }
+
+               // Use the value from $data if possible, or use $value
+               $value = isset($data['value']) ? $data['value'] : $value;
+
+               // Value is not part of the attributes
+               unset($data['value']);
+
+               return '<textarea'.form::attributes($data, 'textarea').' '.$extra.'>'.html::specialchars($value, $double_encode).'</textarea>';
+       }
+
+       /**
+        * 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.'>'.$inner_val.'</option>'."\n";
+                               }
+                               $input .= '</optgroup>'."\n";
+                       }
+                       else
+                       {
+                               $sel = in_array($key, $selected) ? ' selected="selected"' : '';
+                               $input .= '<option value="'.$key.'"'.$sel.'>'.$val.'</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>';
+       }
+
+       /**
+        * Closes an open form tag.
+        *
+        * @param   string  string to be attached after the closing tag
+        * @return  string
+        */
+       public static function close($extra = '')
+       {
+               return '</form>'."\n".$extra;
+       }
+
+       /**
+        * 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 '';
+
+               if (isset($attr['name']) AND empty($attr['id']) AND strpos($attr['name'], '[') === FALSE)
+               {
+                       if ($type === NULL AND ! empty($attr['type']))
+                       {
+                               // Set the type by the attributes
+                               $type = $attr['type'];
+                       }
+
+                       switch ($type)
+                       {
+                               case 'text':
+                               case 'textarea':
+                               case 'password':
+                               case 'select':
+                               case 'checkbox':
+                               case 'file':
+                               case 'image':
+                               case 'button':
+                               case 'submit':
+                                       // Only specific types of inputs use name to id matching
+                                       $attr['id'] = $attr['name'];
+                               break;
+                       }
+               }
+
+               $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
\ No newline at end of file
diff --git a/Server/system/helpers/format.php b/Server/system/helpers/format.php
new file mode 100644 (file)
index 0000000..fb8a029
--- /dev/null
@@ -0,0 +1,66 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Format helper class.
+ *
+ * $Id: format.php 4070 2009-03-11 20:37:38Z Geert $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class format_Core {
+
+       /**
+        * 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;
+       }
+
+} // End format
\ No newline at end of file
diff --git a/Server/system/helpers/html.php b/Server/system/helpers/html.php
new file mode 100644 (file)
index 0000000..2c60956
--- /dev/null
@@ -0,0 +1,446 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * HTML helper class.
+ *
+ * $Id: html.php 4376 2009-06-01 11:40:39Z samsoir $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+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 specialchars($str, $double_encode = TRUE)
+       {
+               // Force the string to be a string
+               $str = (string) $str;
+
+               // Do encode existing HTML entities (default)
+               if ($double_encode === TRUE)
+               {
+                       $str = htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
+               }
+               else
+               {
+                       // Do not encode existing HTML entities
+                       // From PHP 5.2.3 this functionality is built-in, otherwise use a regex
+                       if (version_compare(PHP_VERSION, '5.2.3', '>='))
+                       {
+                               $str = htmlspecialchars($str, ENT_QUOTES, 'UTF-8', FALSE);
+                       }
+                       else
+                       {
+                               $str = preg_replace('/&(?!(?:#\d++|[a-z]++);)/ui', '&amp;', $str);
+                               $str = str_replace(array('<', '>', '\'', '"'), array('&lt;', '&gt;', '&#39;', '&quot;'), $str);
+                       }
+               }
+
+               return $str;
+       }
+
+       /**
+        * Perform a html::specialchars() with additional URL specific encoding.
+        *  
+        * @param   string   string to convert
+        * @param   boolean  encode existing entities
+        * @return  string
+        */
+       public static function specialurlencode($str, $double_encode = TRUE)
+       {
+               return str_replace(' ', '%20', html::specialchars($str, $double_encode));
+       }
+       
+       /**
+        * Create HTML link anchors.
+        *
+        * @param   string  URL or URI string
+        * @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="'.html::specialurlencode($site_url, FALSE).'"'
+               // Attributes empty? Use an empty string
+               .(is_array($attributes) ? html::attributes($attributes) : '').'>'
+               // Title empty? Use the parsed URL
+               .($escape_title ? html::specialchars((($title === NULL) ? $site_url : $title), FALSE) : (($title === NULL) ? $site_url : $title)).'</a>';
+       }
+
+       /**
+        * 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="'.html::specialurlencode(url::base(FALSE, $protocol).$file, 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>';
+       }
+
+       /**
+        * Similar to anchor, but with the protocol parameter first.
+        *
+        * @param   string  link protocol
+        * @param   string  URI or URL to link to
+        * @param   string  link text
+        * @param   array   HTML anchor attributes
+        * @return  string
+        */
+       public static function panchor($protocol, $uri, $title = NULL, $attributes = FALSE)
+       {
+               return html::anchor($uri, $title, $attributes, $protocol);
+       }
+
+       /**
+        * Create an array of anchors from an array of link/title pairs.
+        *
+        * @param   array  link/title pairs
+        * @return  array
+        */
+       public static function anchor_array(array $array)
+       {
+               $anchors = array();
+               foreach ($array as $link => $title)
+               {
+                       // Create list of anchors
+                       $anchors[] = html::anchor($link, $title);
+               }
+               return $anchors;
+       }
+
+       /**
+        * Generates an obfuscated version of an email address.
+        *
+        * @param   string  email address
+        * @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', '.css', $media, $index);
+       }
+
+       /**
+        * Creates a link tag.
+        *
+        * @param   string|array  filename
+        * @param   string|array  relationship
+        * @param   string|array  mimetype
+        * @param   string        specifies suffix of the file
+        * @param   string|array  specifies on what device the document will be displayed
+        * @param   boolean       include the index_page in the link
+        * @return  string
+        */
+       public static function link($href, $rel, $type, $suffix = FALSE, $media = FALSE, $index = FALSE)
+       {
+               $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, $suffix, $_media, $index);
+                       }
+               }
+               else
+               {
+                       if (strpos($href, '://') === FALSE)
+                       {
+                               // Make the URL absolute
+                               $href = url::base($index).$href;
+                       }
+
+                       $length = strlen($suffix);
+
+                       if ( $length > 0 AND substr_compare($href, $suffix, -$length, $length, FALSE) !== 0)
+                       {
+                               // Add the defined suffix
+                               $href .= $suffix;
+                       }
+
+                       $attr = array
+                       (
+                               'rel' => $rel,
+                               '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;
+                       }
+
+                       if (substr_compare($script, '.js', -3, 3, FALSE) !== 0)
+                       {
+                               // Add the javascript suffix
+                               $script .= '.js';
+                       }
+
+                       $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.'="'.html::specialchars($val).'"';
+               }
+
+               return $compiled;
+       }
+
+} // End html
diff --git a/Server/system/helpers/inflector.php b/Server/system/helpers/inflector.php
new file mode 100644 (file)
index 0000000..1e4fee2
--- /dev/null
@@ -0,0 +1,193 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Inflector helper class.
+ *
+ * $Id: inflector.php 4072 2009-03-13 17:20:38Z jheathco $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class 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)
+       {
+               // 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)
+       {
+               // 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 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 preg_replace('/\s+/', '_', trim($str));
+       }
+
+       /**
+        * Makes an underscored or dashed phrase human-reable.
+        *
+        * @param   string  phrase to make human-reable
+        * @return  string
+        */
+       public static function humanize($str)
+       {
+               return preg_replace('/[_-]+/', ' ', trim($str));
+       }
+
+} // End inflector
\ No newline at end of file
diff --git a/Server/system/helpers/num.php b/Server/system/helpers/num.php
new file mode 100644 (file)
index 0000000..3eb5d5a
--- /dev/null
@@ -0,0 +1,26 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Number helper class.
+ *
+ * $Id: num.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class 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/Server/system/helpers/remote.php b/Server/system/helpers/remote.php
new file mode 100644 (file)
index 0000000..f9e0267
--- /dev/null
@@ -0,0 +1,66 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Remote url/file helper.
+ *
+ * $Id: remote.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class remote_Core {
+
+       public static function status($url)
+       {
+               if ( ! valid::url($url, 'http'))
+                       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'].' 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
\ No newline at end of file
diff --git a/Server/system/helpers/request.php b/Server/system/helpers/request.php
new file mode 100644 (file)
index 0000000..4203d0e
--- /dev/null
@@ -0,0 +1,239 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Request helper class.
+ *
+ * $Id: request.php 4355 2009-05-15 17:18:28Z kiall $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class request_Core {
+
+       // Possible HTTP methods
+       protected static $http_methods = array('get', 'head', 'options', 'post', 'put', 'delete');
+
+       // Content types from client's HTTP Accept request header (array)
+       protected static $accept_types;
+
+       /**
+        * Returns the HTTP referrer, or the default if the referrer is not set.
+        *
+        * @param   mixed   default to return
+        * @return  string
+        */
+       public static function referrer($default = FALSE)
+       {
+               if ( ! empty($_SERVER['HTTP_REFERER']))
+               {
+                       // Set referrer
+                       $ref = $_SERVER['HTTP_REFERER'];
+
+                       if (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 (PHP_SAPI === '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('request.unknown_method', $method);
+
+               return $method;
+        }
+
+       /**
+        * 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_header();
+
+               if ($type === NULL)
+                       return request::$accept_types;
+
+               return (request::accepts_at_quality($type, $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)
+       {
+               // Initialize
+               $mime_types = array();
+               $max_q = 0;
+               $preferred = FALSE;
+
+               // Load q values for all given content types
+               foreach (array_unique($types) as $type)
+               {
+                       $mime_types[$type] = request::accepts_at_quality($type, $explicit_check);
+               }
+
+               // Look for the highest q value
+               foreach ($mime_types as $type => $q)
+               {
+                       if ($q > $max_q)
+                       {
+                               $max_q = $q;
+                               $preferred = $type;
+                       }
+               }
+
+               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 = NULL, $explicit_check = FALSE)
+       {
+               request::parse_accept_header();
+
+               // Normalize type
+               $type = strtolower((string) $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]];
+
+               // Wildcard match (if not checking explicitly)
+               if ($explicit_check === FALSE AND isset(request::$accept_types[$type[0]]['*']))
+                       return request::$accept_types[$type[0]]['*'];
+
+               // Catch-all wildcard match (if not checking explicitly)
+               if ($explicit_check === FALSE AND isset(request::$accept_types['*']['*']))
+                       return request::$accept_types['*']['*'];
+
+               // Content type not accepted
+               return 0;
+       }
+
+       /**
+        * Parses client's HTTP Accept request header, and builds array structure representing it.
+        *
+        * @return  void
+        */
+       protected static function parse_accept_header()
+       {
+               // Run this function just once
+               if (request::$accept_types !== NULL)
+                       return;
+
+               // Initialize accept_types array
+               request::$accept_types = array();
+
+               // No HTTP Accept header found
+               if (empty($_SERVER['HTTP_ACCEPT']))
+               {
+                       // Accept everything
+                       request::$accept_types['*']['*'] = 1;
+                       return;
+               }
+
+               // Remove linebreaks and parse the HTTP Accept header
+               foreach (explode(',', str_replace(array("\r", "\n"), '', $_SERVER['HTTP_ACCEPT'])) as $accept_entry)
+               {
+                       // Explode each entry in content type and possible quality factor
+                       $accept_entry = explode(';', trim($accept_entry), 2);
+
+                       // Explode each content type (e.g. "text/html")
+                       $type = explode('/', $accept_entry[0], 2);
+
+                       // Skip invalid content types
+                       if ( ! isset($type[1]))
+                               continue;
+
+                       // Assume a default quality factor of 1 if no custom q value found
+                       $q = (isset($accept_entry[1]) AND preg_match('~\bq\s*+=\s*+([.0-9]+)~', $accept_entry[1], $match)) ? (float) $match[1] : 1;
+
+                       // Populate accept_types array
+                       if ( ! isset(request::$accept_types[$type[0]][$type[1]]) OR $q > request::$accept_types[$type[0]][$type[1]])
+                       {
+                               request::$accept_types[$type[0]][$type[1]] = $q;
+                       }
+               }
+       }
+
+} // End request
\ No newline at end of file
diff --git a/Server/system/helpers/security.php b/Server/system/helpers/security.php
new file mode 100644 (file)
index 0000000..cd48d2e
--- /dev/null
@@ -0,0 +1,47 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Security helper class.
+ *
+ * $Id: security.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class security_Core {
+
+       /**
+        * Sanitize a string with the xss_clean method.
+        *
+        * @param   string  string to sanitize
+        * @return  string
+        */
+       public static function xss_clean($str)
+       {
+               return Input::instance()->xss_clean($str);
+       }
+
+       /**
+        * 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);
+       }
+
+       /**
+        * Remove PHP tags from a string.
+        *
+        * @param   string  string to sanitize
+        * @return  string
+        */
+       public static function encode_php_tags($str)
+       {
+               return str_replace(array('<?', '?>'), array('&lt;?', '?&gt;'), $str);
+       }
+
+} // End security
\ No newline at end of file
diff --git a/Server/system/helpers/text.php b/Server/system/helpers/text.php
new file mode 100644 (file)
index 0000000..d0e573e
--- /dev/null
@@ -0,0 +1,410 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Text helper class.
+ *
+ * $Id: text.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class 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) ? '&#8230;' : $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) ? '&#8230;' : $end_char;
+
+               $limit = (int) $limit;
+
+               if (trim($str) === '' OR utf8::strlen($str) <= $limit)
+                       return $str;
+
+               if ($limit <= 0)
+                       return $end_char;
+
+               if ($preserve_words == FALSE)
+               {
+                       return rtrim(utf8::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 = ! utf8::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 = FALSE)
+       {
+               foreach ((array) $badwords as $key => $badword)
+               {
+                       $badwords[$key] = str_replace('\*', '\S*?', preg_quote((string) $badword));
+               }
+
+               $regex = '('.implode('|', $badwords).')';
+
+               if ($replace_partial_words == TRUE)
+               {
+                       // 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 (utf8::strlen($replacement) == 1)
+               {
+                       $regex .= 'e';
+                       return preg_replace($regex, 'str_repeat($replacement, utf8::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);
+       }
+
+       /**
+        * Converts text email addresses and anchors into links.
+        *
+        * @param   string   text to auto link
+        * @return  string
+        */
+       public static function auto_link($text)
+       {
+               // Auto link emails first to prevent problems with "www.domain.com@example.com"
+               return text::auto_link_urls(text::auto_link_emails($text));
+       }
+
+       /**
+        * Converts text anchors into links.
+        *
+        * @param   string   text to auto link
+        * @return  string
+        */
+       public static function auto_link_urls($text)
+       {
+               // Finds all http/https/ftp/ftps links that are not part of an existing html anchor
+               if (preg_match_all('~\b(?<!href="|">)(?:ht|f)tps?://\S+(?:/|\b)~i', $text, $matches))
+               {
+                       foreach ($matches[0] as $match)
+                       {
+                               // Replace each link with an anchor
+                               $text = str_replace($match, html::anchor($match), $text);
+                       }
+               }
+
+               // Find all naked www.links.com (without http://)
+               if (preg_match_all('~\b(?<!://)www(?:\.[a-z0-9][-a-z0-9]*+)+\.[a-z]{2,6}\b~i', $text, $matches))
+               {
+                       foreach ($matches[0] as $match)
+                       {
+                               // Replace each link with an anchor
+                               $text = str_replace($match, html::anchor('http://'.$match, $match), $text);
+                       }
+               }
+
+               return $text;
+       }
+
+       /**
+        * 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
+        * @return  string
+        */
+       public static function auto_p($str)
+       {
+               // 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 />
+               $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;
+       }
+
+} // End text
\ No newline at end of file
diff --git a/Server/system/helpers/upload.php b/Server/system/helpers/upload.php
new file mode 100644 (file)
index 0000000..422e9e8
--- /dev/null
@@ -0,0 +1,162 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Upload helper class for working with the global $_FILES
+ * array and Validation library.
+ *
+ * $Id: upload.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class 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('upload.not_writable', $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));
+
+               // Get the mime types for the extension
+               $mime_types = Kohana::config('mimes.'.$extension);
+
+               // Make sure there is an extension, that the extension is allowed, and that mime types exist
+               return ( ! empty($extension) AND in_array($extension, $allowed_types) AND is_array($mime_types));
+       }
+
+       /**
+        * 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/Server/system/helpers/url.php b/Server/system/helpers/url.php
new file mode 100644 (file)
index 0000000..f3d0ec8
--- /dev/null
@@ -0,0 +1,252 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * URL helper class.
+ *
+ * $Id: url.php 4029 2009-03-03 12:39:32Z Shadowhand $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class url_Core {
+
+       /**
+        * Fetches the current URI.
+        *
+        * @param   boolean  include the query string
+        * @return  string
+        */
+       public static function current($qs = FALSE)
+       {
+               return ($qs === TRUE) ? Router::$complete_uri : Router::$current_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
+                               $base_url = $protocol.'://'.$_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 _)
+        * @return  string
+        */
+       public static function title($title, $separator = '-')
+       {
+               $separator = ($separator === '-') ? '-' : '_';
+
+               // Replace accented characters by their unaccented equivalents
+               $title = utf8::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));
+
+               // 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/Server/system/helpers/valid.php b/Server/system/helpers/valid.php
new file mode 100644 (file)
index 0000000..8a3583b
--- /dev/null
@@ -0,0 +1,338 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Validation helper class.
+ *
+ * $Id: valid.php 4367 2009-05-27 21:23:57Z samsoir $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class valid_Core {
+
+       /**
+        * Validate email, commonly used characters only
+        *
+        * @param   string   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.
+        *
+        * @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');
+       }
+
+       /**
+        * Validate email, RFC compliant version
+        * Note: 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);
+       }
+
+       /**
+        * Validate URL
+        *
+        * @param   string   URL
+        * @return  boolean
+        */
+       public static function url($url)
+       {
+               return (bool) filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_HOST_REQUIRED);
+       }
+
+       /**
+        * Validate IP
+        *
+        * @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) formula.
+        * @see http://en.wikipedia.org/wiki/Luhn_algorithm
+        *
+        * @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 += $number[$i];
+               }
+
+               for ($i = $length - 2; $i >= 0; $i -= 2)
+               {
+                       // Add up every 2nd digit doubled, starting from the right
+                       $double = $number[$i] * 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.
+        *
+        * @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 string.
+        * 
+        * @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).
+        *
+        * @see Uses locale conversion to allow decimal point to be locale specific.
+        * @see http://www.php.net/manual/en/function.localeconv.php
+        * 
+        * @param   string   input string
+        * @return  boolean
+        */
+       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);
+       }
+
+       /**
+        * Checks whether a string is a valid text. Letters, numbers, whitespace,
+        * dashes, periods, and underscores are allowed.
+        *
+        * @param   string   text to check
+        * @return  boolean
+        */
+       public static function standard_text($str)
+       {
+               // pL matches letters
+               // pN matches numbers
+               // pZ matches whitespace
+               // pPc matches underscores
+               // pPd matches dashes
+               // pPo matches normal puncuation
+               return (bool) preg_match('/^[\pL\pN\pZ\p{Pc}\p{Pd}\p{Po}]++$/uD', (string) $str);
+       }
+
+       /**
+        * 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.
+        *
+        * @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);
+       }
+
+} // End valid
\ No newline at end of file
diff --git a/Server/system/i18n/en_US/cache.php b/Server/system/i18n/en_US/cache.php
new file mode 100644 (file)
index 0000000..bef0279
--- /dev/null
@@ -0,0 +1,10 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+$lang = array
+(
+       'undefined_group'      => 'The %s group is not defined in your configuration.',
+       'extension_not_loaded' => 'The %s PHP extension must be loaded to use this driver.',
+       'unwritable'           => 'The configured storage location, %s, is not writable.',
+       'resources'            => 'Caching of resources is impossible, because resources cannot be serialized.',
+       'driver_error'         => '%s',
+);
\ No newline at end of file
diff --git a/Server/system/i18n/en_US/calendar.php b/Server/system/i18n/en_US/calendar.php
new file mode 100644 (file)
index 0000000..21dad22
--- /dev/null
@@ -0,0 +1,59 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+$lang = array
+(
+       // Two letter days
+       'su'        => 'Su',
+       'mo'        => 'Mo',
+       'tu'        => 'Tu',
+       'we'        => 'We',
+       'th'        => 'Th',
+       'fr'        => 'Fr',
+       'sa'        => 'Sa',
+
+       // Short day names
+       'sun'       => 'Sun',
+       'mon'       => 'Mon',
+       'tue'       => 'Tue',
+       'wed'       => 'Wed',
+       'thu'       => 'Thu',
+       'fri'       => 'Fri',
+       'sat'       => 'Sat',
+
+       // Long day names
+       'sunday'    => 'Sunday',
+       'monday'    => 'Monday',
+       'tuesday'   => 'Tuesday',
+       'wednesday' => 'Wednesday',
+       'thursday'  => 'Thursday',
+       'friday'    => 'Friday',
+       'saturday'  => 'Saturday',
+
+       // Short month names
+       'jan'       => 'Jan',
+       'feb'       => 'Feb',
+       'mar'       => 'Mar',
+       'apr'       => 'Apr',
+       'may'       => 'May',
+       'jun'       => 'Jun',
+       'jul'       => 'Jul',
+       'aug'       => 'Aug',
+       'sep'       => 'Sep',
+       'oct'       => 'Oct',
+       'nov'       => 'Nov',
+       'dec'       => 'Dec',
+
+       // Long month names
+       'january'   => 'January',
+       'february'  => 'February',
+       'march'     => 'March',
+       'april'     => 'April',
+       'mayl'      => 'May',
+       'june'      => 'June',
+       'july'      => 'July',
+       'august'    => 'August',
+       'september' => 'September',
+       'october'   => 'October',
+       'november'  => 'November',
+       'december'  => 'December'
+);
\ No newline at end of file
diff --git a/Server/system/i18n/en_US/captcha.php b/Server/system/i18n/en_US/captcha.php
new file mode 100644 (file)
index 0000000..6040471
--- /dev/null
@@ -0,0 +1,33 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+$lang = array
+(
+       'file_not_found' => 'The specified file, %s, was not found. Please verify that files exist by using file_exists() before using them.',
+       'requires_GD2'   => 'The Captcha library requires GD2 with FreeType support. Please see http://php.net/gd_info for more information.',
+
+       // Words of varying length for the Captcha_Word_Driver to pick from
+       // Note: use only alphanumeric characters
+       'words' => array
+       (
+               'cd', 'tv', 'it', 'to', 'be', 'or',
+               'sun', 'car', 'dog', 'bed', 'kid', 'egg',
+               'bike', 'tree', 'bath', 'roof', 'road', 'hair',
+               'hello', 'world', 'earth', 'beard', 'chess', 'water',
+               'barber', 'bakery', 'banana', 'market', 'purple', 'writer',
+               'america', 'release', 'playing', 'working', 'foreign', 'general',
+               'aircraft', 'computer', 'laughter', 'alphabet', 'kangaroo', 'spelling',
+               'architect', 'president', 'cockroach', 'encounter', 'terrorism', 'cylinders',
+       ),
+
+       // Riddles for the Captcha_Riddle_Driver to pick from
+       // Note: use only alphanumeric characters
+       'riddles' => array
+       (
+               array('Do you hate spam? (yes or no)', 'yes'),
+               array('Are you a robot? (yes or no)', 'no'),
+               array('Fire is... (hot or cold)', 'hot'),
+               array('The season after fall is...', 'winter'),
+               array('Which day of the week is it today?', strftime('%A')),
+               array('Which month of the year are we in?', strftime('%B')),
+       ),
+);
diff --git a/Server/system/i18n/en_US/core.php b/Server/system/i18n/en_US/core.php
new file mode 100644 (file)
index 0000000..9711b7c
--- /dev/null
@@ -0,0 +1,34 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+$lang = array
+(
+       'there_can_be_only_one' => 'There can be only one instance of Kohana per page request',
+       'uncaught_exception'    => 'Uncaught %s: %s in file %s on line %s',
+       'invalid_method'        => 'Invalid method %s called in %s',
+       'invalid_property'      => 'The %s property does not exist in the %s class.',
+       'log_dir_unwritable'    => 'The log directory is not writable: %s',
+       'resource_not_found'    => 'The requested %s, %s, could not be found',
+       'invalid_filetype'      => 'The requested filetype, .%s, is not allowed in your view configuration file',
+       'view_set_filename'     => 'You must set the the view filename before calling render',
+       'no_default_route'      => 'Please set a default route in config/routes.php',
+       'no_controller'         => 'Kohana was not able to determine a controller to process this request: %s',
+       'page_not_found'        => 'The page you requested, %s, could not be found.',
+       'stats_footer'          => 'Loaded in {execution_time} seconds, using {memory_usage} of memory. Generated by Kohana v{kohana_version}.',
+       'error_file_line'       => '<tt>%s <strong>[%s]:</strong></tt>',
+       'stack_trace'           => 'Stack Trace',
+       'generic_error'         => 'Unable to Complete Request',
+       'errors_disabled'       => 'You can go to the <a href="%s">home page</a> or <a href="%s">try again</a>.',
+
+       // Drivers
+       'driver_implements'     => 'The %s driver for the %s library must implement the %s interface',
+       'driver_not_found'      => 'The %s driver for the %s library could not be found',
+
+       // Resource names
+       'config'                => 'config file',
+       'controller'            => 'controller',
+       'helper'                => 'helper',
+       'library'               => 'library',
+       'driver'                => 'driver',
+       'model'                 => 'model',
+       'view'                  => 'view',
+);
diff --git a/Server/system/i18n/en_US/database.php b/Server/system/i18n/en_US/database.php
new file mode 100644 (file)
index 0000000..172e5c9
--- /dev/null
@@ -0,0 +1,15 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+$lang = array
+(
+       'undefined_group'       => 'The %s group is not defined in your configuration.',
+       'error'                 => 'There was an SQL error: %s',
+       'connection'            => 'There was an error connecting to the database: %s',
+       'invalid_dsn'           => 'The DSN you supplied is not valid: %s',
+       'must_use_set'          => 'You must set a SET clause for your query.',
+       'must_use_where'        => 'You must set a WHERE clause for your query.',
+       'must_use_table'        => 'You must set a database table for your query.',
+       'table_not_found'       => 'Table %s does not exist in your database.',
+       'not_implemented'       => 'The method you called, %s, is not supported by this driver.',
+       'result_read_only'      => 'Query results are read only.'
+);
\ No newline at end of file
diff --git a/Server/system/i18n/en_US/encrypt.php b/Server/system/i18n/en_US/encrypt.php
new file mode 100644 (file)
index 0000000..9afd620
--- /dev/null
@@ -0,0 +1,8 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+$lang = array
+(
+       'undefined_group'   => 'The %s group is not defined in your configuration.',
+       'requires_mcrypt'   => 'To use the Encrypt library, mcrypt must be enabled in your PHP installation',
+       'no_encryption_key' => 'To use the Encrypt library, you must set an encryption key in your config file'
+);
diff --git a/Server/system/i18n/en_US/errors.php b/Server/system/i18n/en_US/errors.php
new file mode 100644 (file)
index 0000000..83341a2
--- /dev/null
@@ -0,0 +1,16 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+$lang = array
+(
+       E_KOHANA             => array( 1, 'Framework Error',   'Please check the Kohana documentation for information about the following error.'),
+       E_PAGE_NOT_FOUND     => array( 1, 'Page Not Found',    'The requested page was not found. It may have moved, been deleted, or archived.'),
+       E_DATABASE_ERROR     => array( 1, 'Database Error',    'A database error occurred while performing the requested procedure. Please review the database error below for more information.'),
+       E_RECOVERABLE_ERROR  => array( 1, 'Recoverable Error', 'An error was detected which prevented the loading of this page. If this problem persists, please contact the website administrator.'),
+       E_ERROR              => array( 1, 'Fatal Error',       ''),
+       E_USER_ERROR         => array( 1, 'Fatal Error',       ''),
+       E_PARSE              => array( 1, 'Syntax Error',      ''),
+       E_WARNING            => array( 1, 'Warning Message',   ''),
+       E_USER_WARNING       => array( 1, 'Warning Message',   ''),
+       E_STRICT             => array( 2, 'Strict Mode Error', ''),
+       E_NOTICE             => array( 2, 'Runtime Message',   ''),
+);
\ No newline at end of file
diff --git a/Server/system/i18n/en_US/event.php b/Server/system/i18n/en_US/event.php
new file mode 100644 (file)
index 0000000..282a0a2
--- /dev/null
@@ -0,0 +1,7 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+$lang = array
+(
+       'invalid_subject'  => 'Attempt to attach invalid subject %s to %s failed: Subjects must extend the Event_Subject class',
+       'invalid_observer' => 'Attempt to attach invalid observer %s to %s failed: Observers must extend the Event_Observer class',
+);
diff --git a/Server/system/i18n/en_US/image.php b/Server/system/i18n/en_US/image.php
new file mode 100644 (file)
index 0000000..9f18493
--- /dev/null
@@ -0,0 +1,33 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+$lang = array
+(
+       'getimagesize_missing'    => 'The Image library requires the getimagesize() PHP function, which is not available in your installation.',
+       'unsupported_method'      => 'Your configured driver does not support the %s image transformation.',
+       'file_not_found'          => 'The specified image, %s, was not found. Please verify that images exist by using file_exists() before manipulating them.',
+       'type_not_allowed'        => 'The specified image, %s, is not an allowed image type.',
+       'invalid_width'           => 'The width you specified, %s, is not valid.',
+       'invalid_height'          => 'The height you specified, %s, is not valid.',
+       'invalid_dimensions'      => 'The dimensions specified for %s are not valid.',
+       'invalid_master'          => 'The master dimension specified is not valid.',
+       'invalid_flip'            => 'The flip direction specified is not valid.',
+       'directory_unwritable'    => 'The specified directory, %s, is not writable.',
+
+       // ImageMagick specific messages
+       'imagemagick' => array
+       (
+               'not_found' => 'The ImageMagick directory specified does not contain a required program, %s.',
+       ),
+       
+       // GraphicsMagick specific messages
+       'graphicsmagick' => array
+       (
+               'not_found' => 'The GraphicsMagick directory specified does not contain a required program, %s.',
+       ),
+       
+       // GD specific messages
+       'gd' => array
+       (
+               'requires_v2' => 'The Image library requires GD2. Please see http://php.net/gd_info for more information.',
+       ),
+);
diff --git a/Server/system/i18n/en_US/orm.php b/Server/system/i18n/en_US/orm.php
new file mode 100644 (file)
index 0000000..3c5720b
--- /dev/null
@@ -0,0 +1,3 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+$lang['query_methods_not_allowed'] = 'Query methods cannot be used through ORM';
\ No newline at end of file
diff --git a/Server/system/i18n/en_US/pagination.php b/Server/system/i18n/en_US/pagination.php
new file mode 100644 (file)
index 0000000..26d6561
--- /dev/null
@@ -0,0 +1,15 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+$lang = array
+(
+       'undefined_group' => 'The %s group is not defined in your pagination configuration.',
+       'page'     => 'page',
+       'pages'    => 'pages',
+       'item'     => 'item',
+       'items'    => 'items',
+       'of'       => 'of',
+       'first'    => 'first',
+       'last'     => 'last',
+       'previous' => 'previous',
+       'next'     => 'next',
+);
diff --git a/Server/system/i18n/en_US/profiler.php b/Server/system/i18n/en_US/profiler.php
new file mode 100644 (file)
index 0000000..a39c2f5
--- /dev/null
@@ -0,0 +1,15 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+$lang = array
+(
+       'benchmarks'   => 'Benchmarks',
+       'post_data'    => 'Post Data',
+       'no_post'      => 'No post data',
+       'session_data' => 'Session Data',
+       'no_session'   => 'No session data',
+       'queries'      => 'Database Queries',
+       'no_queries'   => 'No queries',
+       'no_database'  => 'Database not loaded',
+       'cookie_data'  => 'Cookie Data',
+       'no_cookie'    => 'No cookie data',
+);
diff --git a/Server/system/i18n/en_US/session.php b/Server/system/i18n/en_US/session.php
new file mode 100644 (file)
index 0000000..ee781c6
--- /dev/null
@@ -0,0 +1,6 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+$lang = array
+(
+       'invalid_session_name' => 'The session_name, %s, is invalid. It must contain only alphanumeric characters and underscores. Also at least one letter must be present.',
+);
\ No newline at end of file
diff --git a/Server/system/i18n/en_US/swift.php b/Server/system/i18n/en_US/swift.php
new file mode 100644 (file)
index 0000000..249c4a8
--- /dev/null
@@ -0,0 +1,6 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+$lang = array
+(
+       'general_error' => 'An error occurred while sending the email message.'
+);
\ No newline at end of file
diff --git a/Server/system/i18n/en_US/upload.php b/Server/system/i18n/en_US/upload.php
new file mode 100644 (file)
index 0000000..7f6e216
--- /dev/null
@@ -0,0 +1,6 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+$lang = array
+(
+       'not_writable' => 'The upload destination folder, %s, does not appear to be writable.',
+);
\ No newline at end of file
diff --git a/Server/system/i18n/en_US/validation.php b/Server/system/i18n/en_US/validation.php
new file mode 100644 (file)
index 0000000..d98e548
--- /dev/null
@@ -0,0 +1,41 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+$lang = array
+(
+       // Class errors
+       'invalid_rule'  => 'Invalid validation rule used: %s',
+       'i18n_array'    => 'The %s i18n key must be an array to be used with the in_lang rule',
+       'not_callable'  => 'Callback %s used for Validation is not callable',
+
+       // General errors
+       'unknown_error' => 'Unknown validation error while validating the %s field.',
+       'required'      => 'The %s field is required.',
+       'min_length'    => 'The %s field must be at least %d characters long.',
+       'max_length'    => 'The %s field must be %d characters or fewer.',
+       'exact_length'  => 'The %s field must be exactly %d characters.',
+       'in_array'      => 'The %s field must be selected from the options listed.',
+       'matches'       => 'The %s field must match the %s field.',
+       'valid_url'     => 'The %s field must contain a valid URL.',
+       'valid_email'   => 'The %s field must contain a valid email address.',
+       'valid_ip'      => 'The %s field must contain a valid IP address.',
+       'valid_type'    => 'The %s field must only contain %s characters.',
+       'range'         => 'The %s field must be between specified ranges.',
+       'regex'         => 'The %s field does not match accepted input.',
+       'depends_on'    => 'The %s field depends on the %s field.',
+
+       // Upload errors
+       'user_aborted'  => 'The %s file was aborted during upload.',
+       'invalid_type'  => 'The %s file is not an allowed file type.',
+       'max_size'      => 'The %s file you uploaded was too large. The maximum size allowed is %s.',
+       'max_width'     => 'The %s file you uploaded was too big. The maximum allowed width is %spx.',
+       'max_height'    => 'The %s file you uploaded was too big. The maximum allowed height is %spx.',
+       'min_width'     => 'The %s file you uploaded was too small. The minimum allowed width is %spx.',
+       'min_height'    => 'The %s file you uploaded was too small. The minimum allowed height is %spx.',
+
+       // Field types
+       'alpha'         => 'alphabetical',
+       'alpha_numeric' => 'alphabetical and numeric',
+       'alpha_dash'    => 'alphabetical, dash, and underscore',
+       'digit'         => 'digit',
+       'numeric'       => 'numeric',
+);
diff --git a/Server/system/libraries/Cache.php b/Server/system/libraries/Cache.php
new file mode 100644 (file)
index 0000000..8a02a90
--- /dev/null
@@ -0,0 +1,208 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Provides a driver-based interface for finding, creating, and deleting cached
+ * resources. Caches are identified by a unique string. Tagging of caches is
+ * also supported, and caches can be found and deleted by id or tag.
+ *
+ * $Id: Cache.php 4321 2009-05-04 01:39:44Z kiall $
+ *
+ * @package    Cache
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Cache_Core {
+
+       protected static $instances = array();
+
+       // For garbage collection
+       protected static $loaded;
+
+       // Configuration
+       protected $config;
+
+       // Driver object
+       protected $driver;
+
+       /**
+        * Returns a singleton instance of Cache.
+        *
+        * @param   string  configuration
+        * @return  Cache_Core
+        */
+       public static function & instance($config = FALSE)
+       {
+               if ( ! isset(Cache::$instances[$config]))
+               {
+                       // Create a new instance
+                       Cache::$instances[$config] = new Cache($config);
+               }
+
+               return Cache::$instances[$config];
+       }
+
+       /**
+        * Loads the configured driver and validates it.
+        *
+        * @param   array|string  custom configuration or config group name
+        * @return  void
+        */
+       public function __construct($config = FALSE)
+       {
+               if (is_string($config))
+               {
+                       $name = $config;
+
+                       // Test the config group name
+                       if (($config = Kohana::config('cache.'.$config)) === NULL)
+                               throw new Kohana_Exception('cache.undefined_group', $name);
+               }
+
+               if (is_array($config))
+               {
+                       // Append the default configuration options
+                       $config += Kohana::config('cache.default');
+               }
+               else
+               {
+                       // Load the default group
+                       $config = Kohana::config('cache.default');
+               }
+
+               // Cache the config in the object
+               $this->config = $config;
+
+               // Set driver name
+               $driver = 'Cache_'.ucfirst($this->config['driver']).'_Driver';
+
+               // Load the driver
+               if ( ! Kohana::auto_load($driver))
+                       throw new Kohana_Exception('core.driver_not_found', $this->config['driver'], get_class($this));
+
+               // Initialize the driver
+               $this->driver = new $driver($this->config['params']);
+
+               // Validate the driver
+               if ( ! ($this->driver instanceof Cache_Driver))
+                       throw new Kohana_Exception('core.driver_implements', $this->config['driver'], get_class($this), 'Cache_Driver');
+
+               Kohana::log('debug', 'Cache Library initialized');
+
+               if (Cache::$loaded !== TRUE)
+               {
+                       $this->config['requests'] = (int) $this->config['requests'];
+
+                       if ($this->config['requests'] > 0 AND mt_rand(1, $this->config['requests']) === 1)
+                       {
+                               // Do garbage collection
+                               $this->driver->delete_expired();
+
+                               Kohana::log('debug', 'Cache: Expired caches deleted.');
+                       }
+
+                       // Cache has been loaded once
+                       Cache::$loaded = TRUE;
+               }
+       }
+
+       /**
+        * Fetches a cache by id. NULL is returned when a cache item is not found.
+        *
+        * @param   string  cache id
+        * @return  mixed   cached data or NULL
+        */
+       public function get($id)
+       {
+               // Sanitize the ID
+               $id = $this->sanitize_id($id);
+
+               return $this->driver->get($id);
+       }
+
+       /**
+        * Fetches all of the caches for a given tag. An empty array will be
+        * returned when no matching caches are found.
+        *
+        * @param   string  cache tag
+        * @return  array   all cache items matching the tag
+        */
+       public function find($tag)
+       {
+               return $this->driver->find($tag);
+       }
+
+       /**
+        * Set a cache item by id. Tags may also be added and a custom lifetime
+        * can be set. Non-string data is automatically serialized.
+        *
+        * @param   string        unique cache id
+        * @param   mixed         data to cache
+        * @param   array|string  tags for this item
+        * @param   integer       number of seconds until the cache expires
+        * @return  boolean
+        */
+       function set($id, $data, $tags = NULL, $lifetime = NULL)
+       {
+               if (is_resource($data))
+                       throw new Kohana_Exception('cache.resources');
+
+               // Sanitize the ID
+               $id = $this->sanitize_id($id);
+
+               if ($lifetime === NULL)
+               {
+                       // Get the default lifetime
+                       $lifetime = $this->config['lifetime'];
+               }
+
+               return $this->driver->set($id, $data, (array) $tags, $lifetime);
+       }
+
+       /**
+        * Delete a cache item by id.
+        *
+        * @param   string   cache id
+        * @return  boolean
+        */
+       public function delete($id)
+       {
+               // Sanitize the ID
+               $id = $this->sanitize_id($id);
+
+               return $this->driver->delete($id);
+       }
+
+       /**
+        * Delete all cache items with a given tag.
+        *
+        * @param   string   cache tag name
+        * @return  boolean
+        */
+       public function delete_tag($tag)
+       {
+               return $this->driver->delete($tag, TRUE);
+       }
+
+       /**
+        * Delete ALL cache items items.
+        *
+        * @return  boolean
+        */
+       public function delete_all()
+       {
+               return $this->driver->delete(TRUE);
+       }
+
+       /**
+        * Replaces troublesome characters with underscores.
+        *
+        * @param   string   cache id
+        * @return  string
+        */
+       protected function sanitize_id($id)
+       {
+               // Change slashes and spaces to underscores
+               return str_replace(array('/', '\\', ' '), '_', $id);
+       }
+
+} // End Cache
diff --git a/Server/system/libraries/Calendar.php b/Server/system/libraries/Calendar.php
new file mode 100644 (file)
index 0000000..193a6fb
--- /dev/null
@@ -0,0 +1,362 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Calendar creation library.
+ *
+ * $Id: Calendar.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package    Calendar
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Calendar_Core extends Event_Subject {
+
+       // Start the calendar on Sunday by default
+       public static $start_monday = FALSE;
+
+       // Month and year to use for calendaring
+       protected $month;
+       protected $year;
+
+       // Week starts on Sunday
+       protected $week_start = 0;
+
+       // Observed data
+       protected $observed_data;
+
+       /**
+        * Returns an array of the names of the days, using the current locale.
+        *
+        * @param   integer  left of day names
+        * @return  array
+        */
+       public static function days($length = TRUE)
+       {
+               // strftime day format
+               $format = ($length > 3) ? '%A' : '%a';
+
+               // Days of the week
+               $days = array('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday');
+
+               if (Calendar::$start_monday === TRUE)
+               {
+                       // Push Sunday to the end of the days
+                       array_push($days, array_shift($days));
+               }
+
+               if (strpos(Kohana::config('locale.language.0'), 'en') !== 0)
+               {
+                       // This is a bit awkward, but it works properly and is reliable
+                       foreach ($days as $i => $day)
+                       {
+                               // Convert the English names to i18n names
+                               $days[$i] = strftime($format, strtotime($day));
+                       }
+               }
+
+               if (is_int($length) OR ctype_digit($length))
+               {
+                       foreach ($days as $i => $day)
+                       {
+                               // Shorten the days to the expected length
+                               $days[$i] = utf8::substr($day, 0, $length);
+                       }
+               }
+
+               return $days;
+       }
+
+       /**
+        * Create a new Calendar instance. A month and year can be specified.
+        * By default, the current month and year are used.
+        *
+        * @param   integer  month number
+        * @param   integer  year number
+        * @return  object
+        */
+       public static function factory($month = NULL, $year = NULL)
+       {
+               return new Calendar($month, $year);
+       }
+
+       /**
+        * Create a new Calendar instance. A month and year can be specified.
+        * By default, the current month and year are used.
+        *
+        * @param   integer  month number
+        * @param   integer  year number
+        * @return  void
+        */
+       public function __construct($month = NULL, $year = NULL)
+       {
+               empty($month) and $month = date('n'); // Current month
+               empty($year)  and $year  = date('Y'); // Current year
+
+               // Set the month and year
+               $this->month = (int) $month;
+               $this->year  = (int) $year;
+
+               if (Calendar::$start_monday === TRUE)
+               {
+                       // Week starts on Monday
+                       $this->week_start = 1;
+               }
+       }
+
+       /**
+        * Allows fetching the current month and year.
+        *
+        * @param   string  key to get
+        * @return  mixed
+        */
+       public function __get($key)
+       {
+               if ($key === 'month' OR $key === 'year')
+               {
+                       return $this->$key;
+               }
+       }
+
+       /**
+        * Calendar_Event factory method.
+        *
+        * @param   string  unique name for the event
+        * @return  object  Calendar_Event
+        */
+       public function event($name = NULL)
+       {
+               return new Calendar_Event($this);
+       }
+
+       /**
+        * Calendar_Event factory method.
+        *
+        * @chainable
+        * @param   string  standard event type
+        * @return  object
+        */
+       public function standard($name)
+       {
+               switch ($name)
+               {
+                       case 'today':
+                               // Add an event for the current day
+                               $this->attach($this->event()->condition('timestamp', strtotime('today'))->add_class('today'));
+                       break;
+                       case 'prev-next':
+                               // Add an event for padding days
+                               $this->attach($this->event()->condition('current', FALSE)->add_class('prev-next'));
+                       break;
+                       case 'holidays':
+                               // Base event
+                               $event = $this->event()->condition('current', TRUE)->add_class('holiday');
+
+                               // Attach New Years
+                               $holiday = clone $event;
+                               $this->attach($holiday->condition('month', 1)->condition('day', 1));
+
+                               // Attach Valentine's Day
+                               $holiday = clone $event;
+                               $this->attach($holiday->condition('month', 2)->condition('day', 14));
+
+                               // Attach St. Patrick's Day
+                               $holiday = clone $event;
+                               $this->attach($holiday->condition('month', 3)->condition('day', 17));
+
+                               // Attach Easter
+                               $holiday = clone $event;
+                               $this->attach($holiday->condition('easter', TRUE));
+
+                               // Attach Memorial Day
+                               $holiday = clone $event;
+                               $this->attach($holiday->condition('month', 5)->condition('day_of_week', 1)->condition('last_occurrence', TRUE));
+
+                               // Attach Independance Day
+                               $holiday = clone $event;
+                               $this->attach($holiday->condition('month', 7)->condition('day', 4));
+
+                               // Attach Labor Day
+                               $holiday = clone $event;
+                               $this->attach($holiday->condition('month', 9)->condition('day_of_week', 1)->condition('occurrence', 1));
+
+                               // Attach Halloween
+                               $holiday = clone $event;
+                               $this->attach($holiday->condition('month', 10)->condition('day', 31));
+
+                               // Attach Thanksgiving
+                               $holiday = clone $event;
+                               $this->attach($holiday->condition('month', 11)->condition('day_of_week', 4)->condition('occurrence', 4));
+
+                               // Attach Christmas
+                               $holiday = clone $event;
+                               $this->attach($holiday->condition('month', 12)->condition('day', 25));
+                       break;
+                       case 'weekends':
+                               // Weekend events
+                               $this->attach($this->event()->condition('weekend', TRUE)->add_class('weekend'));
+                       break;
+               }
+
+               return $this;
+       }
+
+       /**
+        * Returns an array for use with a view. The array contains an array for
+        * each week. Each week contains 7 arrays, with a day number and status:
+        * TRUE if the day is in the month, FALSE if it is padding.
+        *
+        * @return  array
+        */
+       public function weeks()
+       {
+               // First day of the month as a timestamp
+               $first = mktime(1, 0, 0, $this->month, 1, $this->year);
+
+               // Total number of days in this month
+               $total = (int) date('t', $first);
+
+               // Last day of the month as a timestamp
+               $last  = mktime(1, 0, 0, $this->month, $total, $this->year);
+
+               // Make the month and week empty arrays
+               $month = $week = array();
+
+               // Number of days added. When this reaches 7, start a new week
+               $days = 0;
+               $week_number = 1;
+
+               if (($w = (int) date('w', $first) - $this->week_start) < 0)
+               {
+                       $w = 6;
+               }
+
+               if ($w > 0)
+               {
+                       // Number of days in the previous month
+                       $n = (int) date('t', mktime(1, 0, 0, $this->month - 1, 1, $this->year));
+
+                       // i = number of day, t = number of days to pad
+                       for ($i = $n - $w + 1, $t = $w; $t > 0; $t--, $i++)
+                       {
+                               // Notify the listeners
+                               $this->notify(array($this->month - 1, $i, $this->year, $week_number, FALSE));
+
+                               // Add previous month padding days
+                               $week[] = array($i, FALSE, $this->observed_data);
+                               $days++;
+                       }
+               }
+
+               // i = number of day
+               for ($i = 1; $i <= $total; $i++)
+               {
+                       if ($days % 7 === 0)
+                       {
+                               // Start a new week
+                               $month[] = $week;
+                               $week = array();
+
+                               $week_number++;
+                       }
+
+                       // Notify the listeners
+                       $this->notify(array($this->month, $i, $this->year, $week_number, TRUE));
+
+                       // Add days to this month
+                       $week[] = array($i, TRUE, $this->observed_data);
+                       $days++;
+               }
+
+               if (($w = (int) date('w', $last) - $this->week_start) < 0)
+               {
+                       $w = 6;
+               }
+
+               if ($w >= 0)
+               {
+                       // i = number of day, t = number of days to pad
+                       for ($i = 1, $t = 6 - $w; $t > 0; $t--, $i++)
+                       {
+                               // Notify the listeners
+                               $this->notify(array($this->month + 1, $i, $this->year, $week_number, FALSE));
+
+                               // Add next month padding days
+                               $week[] = array($i, FALSE, $this->observed_data);
+                       }
+               }
+
+               if ( ! empty($week))
+               {
+                       // Append the remaining days
+                       $month[] = $week;
+               }
+
+               return $month;
+       }
+
+       /**
+        * Adds new data from an observer. All event data contains and array of CSS
+        * classes and an array of output messages.
+        *
+        * @param   array  observer data.
+        * @return  void
+        */
+       public function add_data(array $data)
+       {
+               // Add new classes
+               $this->observed_data['classes'] += $data['classes'];
+
+               if ( ! empty($data['output']))
+               {
+                       // Only add output if it's not empty
+                       $this->observed_data['output'][] = $data['output'];
+               }
+       }
+
+       /**
+        * Resets the observed data and sends a notify to all attached events.
+        *
+        * @param   array  UNIX timestamp
+        * @return  void
+        */
+       public function notify($data)
+       {
+               // Reset observed data
+               $this->observed_data = array
+               (
+                       'classes' => array(),
+                       'output' => array(),
+               );
+
+               // Send a notify
+               parent::notify($data);
+       }
+
+       /**
+        * Convert the calendar to HTML using the kohana_calendar view.
+        *
+        * @return  string
+        */
+       public function render()
+       {
+               $view =  new View('kohana_calendar', array
+               (
+                       'month'  => $this->month,
+                       'year'   => $this->year,
+                       'weeks'  => $this->weeks(),
+               ));
+
+               return $view->render();
+       }
+
+       /**
+        * Magically convert this object to a string, the rendered calendar.
+        *
+        * @return  string
+        */
+       public function __toString()
+       {
+               return $this->render();
+       }
+
+} // End Calendar
\ No newline at end of file
diff --git a/Server/system/libraries/Calendar_Event.php b/Server/system/libraries/Calendar_Event.php
new file mode 100644 (file)
index 0000000..ba9b5ad
--- /dev/null
@@ -0,0 +1,307 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Calendar event observer class.
+ *
+ * $Id: Calendar_Event.php 4129 2009-03-27 17:47:03Z zombor $
+ *
+ * @package    Calendar
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Calendar_Event_Core extends Event_Observer {
+
+       // Boolean conditions
+       protected $booleans = array
+       (
+               'current',
+               'weekend',
+               'first_day',
+               'last_day',
+               'last_occurrence',
+               'easter',
+       );
+
+       // Rendering conditions
+       protected $conditions = array();
+
+       // Cell classes
+       protected $classes = array();
+
+       // Cell output
+       protected $output = '';
+
+       /**
+        * Adds a condition to the event. The condition can be one of the following:
+        *
+        * timestamp       - UNIX timestamp
+        * day             - day number (1-31)
+        * week            - week number (1-5)
+        * month           - month number (1-12)
+        * year            - year number (4 digits)
+        * day_of_week     - day of week (1-7)
+        * current         - active month (boolean) (only show data for the month being rendered)
+        * weekend         - weekend day (boolean)
+        * first_day       - first day of month (boolean)
+        * last_day        - last day of month (boolean)
+        * occurrence      - occurrence of the week day (1-5) (use with "day_of_week")
+        * last_occurrence - last occurrence of week day (boolean) (use with "day_of_week")
+        * easter          - Easter day (boolean)
+        * callback        - callback test (boolean)
+        *
+        * To unset a condition, call condition with a value of NULL.
+        *
+        * @chainable
+        * @param   string  condition key
+        * @param   mixed   condition value
+        * @return  object
+        */
+       public function condition($key, $value)
+       {
+               if ($value === NULL)
+               {
+                       unset($this->conditions[$key]);
+               }
+               else
+               {
+                       if ($key === 'callback')
+                       {
+                               // Do nothing
+                       }
+                       elseif (in_array($key, $this->booleans))
+                       {
+                               // Make the value boolean
+                               $value = (bool) $value;
+                       }
+                       else
+                       {
+                               // Make the value an int
+                               $value = (int) $value;
+                       }
+
+                       $this->conditions[$key] = $value;
+               }
+
+               return $this;
+       }
+
+       /**
+        * Add a CSS class for this event. This can be called multiple times.
+        *
+        * @chainable
+        * @param   string  CSS class name
+        * @return  object
+        */
+       public function add_class($class)
+       {
+               $this->classes[$class] = $class;
+
+               return $this;
+       }
+
+       /**
+        * Remove a CSS class for this event. This can be called multiple times.
+        *
+        * @chainable
+        * @param   string  CSS class name
+        * @return  object
+        */
+       public function remove_class($class)
+       {
+               unset($this->classes[$class]);
+
+               return $this;
+       }
+
+       /**
+        * Set HTML output for this event.
+        *
+        * @chainable
+        * @param   string  HTML output
+        * @return  object
+        */
+       public function output($str)
+       {
+               $this->output = $str;
+
+               return $this;
+       }
+
+       /**
+        * Add a CSS class for this event. This can be called multiple times.
+        *
+        * @chainable
+        * @param   string  CSS class name
+        * @return  object
+        */
+       public function notify($data)
+       {
+               // Split the date and current status
+               list ($month, $day, $year, $week, $current) = $data;
+
+               // Get a timestamp for the day
+               $timestamp = mktime(0, 0, 0, $month, $day, $year);
+
+               // Date conditionals
+               $condition = array
+               (
+                       'timestamp'   => (int) $timestamp,
+                       'day'         => (int) date('j', $timestamp),
+                       'week'        => (int) $week,
+                       'month'       => (int) date('n', $timestamp),
+                       'year'        => (int) date('Y', $timestamp),
+                       'day_of_week' => (int) date('w', $timestamp),
+                       'current'     => (bool) $current,
+               );
+
+               // Tested conditions
+               $tested = array();
+
+               foreach ($condition as $key => $value)
+               {
+                       // Timestamps need to be handled carefully
+                       if($key === 'timestamp' AND isset($this->conditions['timestamp']))
+                       {
+                               // This adds 23 hours, 59 minutes and 59 seconds to today's timestamp, as 24 hours
+                               // is classed as a new day
+                               $next_day = $timestamp + 86399;
+                               
+                               if($this->conditions['timestamp'] < $timestamp OR $this->conditions['timestamp'] > $next_day)
+                                       return FALSE;
+                       }
+                       // Test basic conditions first
+                       elseif (isset($this->conditions[$key]) AND $this->conditions[$key] !== $value)
+                               return FALSE;
+
+                       // Condition has been tested
+                       $tested[$key] = TRUE;
+               }
+
+               if (isset($this->conditions['weekend']))
+               {
+                       // Weekday vs Weekend
+                       $condition['weekend'] = ($condition['day_of_week'] === 0 OR $condition['day_of_week'] === 6);
+               }
+
+               if (isset($this->conditions['first_day']))
+               {
+                       // First day of month
+                       $condition['first_day'] = ($condition['day'] === 1);
+               }
+
+               if (isset($this->conditions['last_day']))
+               {
+                       // Last day of month
+                       $condition['last_day'] = ($condition['day'] === (int) date('t', $timestamp));
+               }
+
+               if (isset($this->conditions['occurrence']))
+               {
+                       // Get the occurance of the current day
+                       $condition['occurrence'] = $this->day_occurrence($timestamp);
+               }
+
+               if (isset($this->conditions['last_occurrence']))
+               {
+                       // Test if the next occurance of this date is next month
+                       $condition['last_occurrence'] = ((int) date('n', $timestamp + 604800) !== $condition['month']);
+               }
+
+               if (isset($this->conditions['easter']))
+               {
+                       if ($condition['month'] === 3 OR $condition['month'] === 4)
+                       {
+                               // This algorithm is from Practical Astronomy With Your Calculator, 2nd Edition by Peter
+                               // Duffett-Smith. It was originally from Butcher's Ecclesiastical Calendar, published in
+                               // 1876. This algorithm has also been published in the 1922 book General Astronomy by
+                               // Spencer Jones; in The Journal of the British Astronomical Association (Vol.88, page
+                               // 91, December 1977); and in Astronomical Algorithms (1991) by Jean Meeus.
+
+                               $a = $condition['year'] % 19;
+                               $b = (int) ($condition['year'] / 100);
+                               $c = $condition['year'] % 100;
+                               $d = (int) ($b / 4);
+                               $e = $b % 4;
+                               $f = (int) (($b + 8) / 25);
+                               $g = (int) (($b - $f + 1) / 3);
+                               $h = (19 * $a + $b - $d - $g + 15) % 30;
+                               $i = (int) ($c / 4);
+                               $k = $c % 4;
+                               $l = (32 + 2 * $e + 2 * $i - $h - $k) % 7;
+                               $m = (int) (($a + 11 * $h + 22 * $l) / 451);
+                               $p = ($h + $l - 7 * $m + 114) % 31;
+
+                               $month = (int) (($h + $l - 7 * $m + 114) / 31);
+                               $day = $p + 1;
+
+                               $condition['easter'] = ($condition['month'] === $month AND $condition['day'] === $day);
+                       }
+                       else
+                       {
+                               // Easter can only happen in March or April
+                               $condition['easter'] = FALSE;
+                       }
+               }
+
+               if (isset($this->conditions['callback']))
+               {
+                       // Use a callback to determine validity
+                       $condition['callback'] = call_user_func($this->conditions['callback'], $condition, $this);
+               }
+
+               $conditions = array_diff_key($this->conditions, $tested);
+
+               foreach ($conditions as $key => $value)
+               {
+                       if ($key === 'callback')
+                       {
+                               // Callbacks are tested on a TRUE/FALSE basis
+                               $value = TRUE;
+                       }
+
+                       // Test advanced conditions
+                       if ($condition[$key] !== $value)
+                               return FALSE;
+               }
+
+               $this->caller->add_data(array
+               (
+                       'classes' => $this->classes,
+                       'output'  => $this->output,
+               ));
+       }
+
+       /**
+        * Find the week day occurrence for a specific timestamp. The occurrence is
+        * relative to the current month. For example, the second Saturday of any
+        * given month will return "2" as the occurrence. This is used in combination
+        * with the "occurrence" condition.
+        *
+        * @param   integer  UNIX timestamp
+        * @return  integer
+        */
+       protected function day_occurrence($timestamp)
+       {
+               // Get the current month for the timestamp
+               $month = date('m', $timestamp);
+
+               // Default occurrence is one
+               $occurrence = 1;
+
+               // Reduce the timestamp by one week for each loop. This has the added
+               // benefit of preventing an infinite loop.
+               while ($timestamp -= 604800)
+               {
+                       if (date('m', $timestamp) !== $month)
+                       {
+                               // Once the timestamp has gone into the previous month, the
+                               // proper occurrence has been found.
+                               return $occurrence;
+                       }
+
+                       // Increment the occurrence
+                       $occurrence++;
+               }
+       }
+
+} // End Calendar Event
diff --git a/Server/system/libraries/Captcha.php b/Server/system/libraries/Captcha.php
new file mode 100644 (file)
index 0000000..f5f2b46
--- /dev/null
@@ -0,0 +1,279 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Captcha library.
+ *
+ * $Id: Captcha.php 4072 2009-03-13 17:20:38Z jheathco $
+ *
+ * @package    Captcha
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Captcha_Core {
+
+       // Captcha singleton
+       protected static $instance;
+
+       // Style-dependent Captcha driver
+       protected $driver;
+
+       // Config values
+       public static $config = array
+       (
+               'style'      => 'basic',
+               'width'      => 150,
+               'height'     => 50,
+               'complexity' => 4,
+               'background' => '',
+               'fontpath'   => '',
+               'fonts'      => array(),
+               'promote'    => FALSE,
+       );
+
+       /**
+        * Singleton instance of Captcha.
+        *
+        * @return  object
+        */
+       public static function instance()
+       {
+               // Create the instance if it does not exist
+               empty(Captcha::$instance) and new Captcha;
+
+               return Captcha::$instance;
+       }
+
+       /**
+        * Constructs and returns a new Captcha object.
+        *
+        * @param   string  config group name
+        * @return  object
+        */
+       public static function factory($group = NULL)
+       {
+               return new Captcha($group);
+       }
+
+       /**
+        * Constructs a new Captcha object.
+        *
+        * @throws  Kohana_Exception
+        * @param   string  config group name
+        * @return  void
+        */
+       public function __construct($group = NULL)
+       {
+               // Create a singleton instance once
+               empty(Captcha::$instance) and Captcha::$instance = $this;
+
+               // No config group name given
+               if ( ! is_string($group))
+               {
+                       $group = 'default';
+               }
+
+               // Load and validate config group
+               if ( ! is_array($config = Kohana::config('captcha.'.$group)))
+                       throw new Kohana_Exception('captcha.undefined_group', $group);
+
+               // All captcha config groups inherit default config group
+               if ($group !== 'default')
+               {
+                       // Load and validate default config group
+                       if ( ! is_array($default = Kohana::config('captcha.default')))
+                               throw new Kohana_Exception('captcha.undefined_group', 'default');
+
+                       // Merge config group with default config group
+                       $config += $default;
+               }
+
+               // Assign config values to the object
+               foreach ($config as $key => $value)
+               {
+                       if (array_key_exists($key, Captcha::$config))
+                       {
+                               Captcha::$config[$key] = $value;
+                       }
+               }
+
+               // Store the config group name as well, so the drivers can access it
+               Captcha::$config['group'] = $group;
+
+               // If using a background image, check if it exists
+               if ( ! empty($config['background']))
+               {
+                       Captcha::$config['background'] = str_replace('\\', '/', realpath($config['background']));
+
+                       if ( ! is_file(Captcha::$config['background']))
+                               throw new Kohana_Exception('captcha.file_not_found', Captcha::$config['background']);
+               }
+
+               // If using any fonts, check if they exist
+               if ( ! empty($config['fonts']))
+               {
+                       Captcha::$config['fontpath'] = str_replace('\\', '/', realpath($config['fontpath'])).'/';
+
+                       foreach ($config['fonts'] as $font)
+                       {
+                               if ( ! is_file(Captcha::$config['fontpath'].$font))
+                                       throw new Kohana_Exception('captcha.file_not_found', Captcha::$config['fontpath'].$font);
+                       }
+               }
+
+               // Set driver name
+               $driver = 'Captcha_'.ucfirst($config['style']).'_Driver';
+
+               // Load the driver
+               if ( ! Kohana::auto_load($driver))
+                       throw new Kohana_Exception('core.driver_not_found', $config['style'], get_class($this));
+
+               // Initialize the driver
+               $this->driver = new $driver;
+
+               // Validate the driver
+               if ( ! ($this->driver instanceof Captcha_Driver))
+                       throw new Kohana_Exception('core.driver_implements', $config['style'], get_class($this), 'Captcha_Driver');
+
+               Kohana::log('debug', 'Captcha Library initialized');
+       }
+
+       /**
+        * Validates a Captcha response and updates response counter.
+        *
+        * @param   string   captcha response
+        * @return  boolean
+        */
+       public static function valid($response)
+       {
+               // Maximum one count per page load
+               static $counted;
+
+               // User has been promoted, always TRUE and don't count anymore
+               if (Captcha::instance()->promoted())
+                       return TRUE;
+
+               // Challenge result
+               $result = (bool) Captcha::instance()->driver->valid($response);
+
+               // Increment response counter
+               if ($counted !== TRUE)
+               {
+                       $counted = TRUE;
+
+                       // Valid response
+                       if ($result === TRUE)
+                       {
+                               Captcha::instance()->valid_count(Session::instance()->get('captcha_valid_count') + 1);
+                       }
+                       // Invalid response
+                       else
+                       {
+                               Captcha::instance()->invalid_count(Session::instance()->get('captcha_invalid_count') + 1);
+                       }
+               }
+
+               return $result;
+       }
+
+       /**
+        * Gets or sets the number of valid Captcha responses for this session.
+        *
+        * @param   integer  new counter value
+        * @param   boolean  trigger invalid counter (for internal use only)
+        * @return  integer  counter value
+        */
+       public function valid_count($new_count = NULL, $invalid = FALSE)
+       {
+               // Pick the right session to use
+               $session = ($invalid === TRUE) ? 'captcha_invalid_count' : 'captcha_valid_count';
+
+               // Update counter
+               if ($new_count !== NULL)
+               {
+                       $new_count = (int) $new_count;
+
+                       // Reset counter = delete session
+                       if ($new_count < 1)
+                       {
+                               Session::instance()->delete($session);
+                       }
+                       // Set counter to new value
+                       else
+                       {
+                               Session::instance()->set($session, (int) $new_count);
+                       }
+
+                       // Return new count
+                       return (int) $new_count;
+               }
+
+               // Return current count
+               return (int) Session::instance()->get($session);
+       }
+
+       /**
+        * Gets or sets the number of invalid Captcha responses for this session.
+        *
+        * @param   integer  new counter value
+        * @return  integer  counter value
+        */
+       public function invalid_count($new_count = NULL)
+       {
+               return $this->valid_count($new_count, TRUE);
+       }
+
+       /**
+        * Resets the Captcha response counters and removes the count sessions.
+        *
+        * @return  void
+        */
+       public function reset_count()
+       {
+               $this->valid_count(0);
+               $this->valid_count(0, TRUE);
+       }
+
+       /**
+        * Checks whether user has been promoted after having given enough valid responses.
+        *
+        * @param   integer  valid response count threshold
+        * @return  boolean
+        */
+       public function promoted($threshold = NULL)
+       {
+               // Promotion has been disabled
+               if (Captcha::$config['promote'] === FALSE)
+                       return FALSE;
+
+               // Use the config threshold
+               if ($threshold === NULL)
+               {
+                       $threshold = Captcha::$config['promote'];
+               }
+
+               // Compare the valid response count to the threshold
+               return ($this->valid_count() >= $threshold);
+       }
+
+       /**
+        * Returns or outputs the Captcha challenge.
+        *
+        * @param   boolean  TRUE to output html, e.g. <img src="#" />
+        * @return  mixed    html string or void
+        */
+       public function render($html = TRUE)
+       {
+               return $this->driver->render($html);
+       }
+
+       /**
+        * Magically outputs the Captcha challenge.
+        *
+        * @return  mixed
+        */
+       public function __toString()
+       {
+               return $this->render();
+       }
+
+} // End Captcha Class
\ No newline at end of file
diff --git a/Server/system/libraries/Controller.php b/Server/system/libraries/Controller.php
new file mode 100644 (file)
index 0000000..2f64c21
--- /dev/null
@@ -0,0 +1,86 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Kohana Controller class. The controller class must be extended to work
+ * properly, so this class is defined as abstract.
+ *
+ * $Id: Controller.php 4365 2009-05-27 21:09:27Z samsoir $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+abstract class Controller_Core {
+
+       // Allow all controllers to run in production by default
+       const ALLOW_PRODUCTION = TRUE;
+
+       /**
+        * Loads URI, and Input into this controller.
+        *
+        * @return  void
+        */
+       public function __construct()
+       {
+               if (Kohana::$instance == NULL)
+               {
+                       // Set the instance to the first controller loaded
+                       Kohana::$instance = $this;
+               }
+
+               // URI should always be available
+               $this->uri = URI::instance();
+
+               // Input should always be available
+               $this->input = Input::instance();
+       }
+
+       /**
+        * Handles methods that do not exist.
+        *
+        * @param   string  method name
+        * @param   array   arguments
+        * @return  void
+        */
+       public function __call($method, $args)
+       {
+               // Default to showing a 404 page
+               Event::run('system.404');
+       }
+
+       /**
+        * Includes a View within the controller scope.
+        *
+        * @param   string  view filename
+        * @param   array   array of view variables
+        * @return  string
+        */
+       public function _kohana_load_view($kohana_view_filename, $kohana_input_data)
+       {
+               if ($kohana_view_filename == '')
+                       return;
+
+               // Buffering on
+               ob_start();
+
+               // Import the view variables to local namespace
+               extract($kohana_input_data, EXTR_SKIP);
+
+               // Views are straight HTML pages with embedded PHP, so importing them
+               // this way insures that $this can be accessed as if the user was in
+               // the controller, which gives the easiest access to libraries in views
+               try
+               {
+                       include $kohana_view_filename;
+               }
+               catch (Exception $e)
+               {
+                       ob_end_clean();
+                       throw $e;
+               }
+
+               // Fetch the output and close the buffer
+               return ob_get_clean();
+       }
+
+} // End Controller Class
\ No newline at end of file
diff --git a/Server/system/libraries/Database.php b/Server/system/libraries/Database.php
new file mode 100644 (file)
index 0000000..6267f63
--- /dev/null
@@ -0,0 +1,1444 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Provides database access in a platform agnostic way, using simple query building blocks.
+ *
+ * $Id: Database.php 4342 2009-05-08 16:56:01Z jheathco $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Database_Core {
+
+       // Database instances
+       public static $instances = array();
+
+       // Global benchmark
+       public static $benchmarks = array();
+
+       // Configuration
+       protected $config = array
+       (
+               'benchmark'     => TRUE,
+               'persistent'    => FALSE,
+               'connection'    => '',
+               'character_set' => 'utf8',
+               'table_prefix'  => '',
+               'object'        => TRUE,
+               'cache'         => FALSE,
+               'escape'        => TRUE,
+       );
+
+       // Database driver object
+       protected $driver;
+       protected $link;
+
+       // Un-compiled parts of the SQL query
+       protected $select     = array();
+       protected $set        = array();
+       protected $from       = array();
+       protected $join       = array();
+       protected $where      = array();
+       protected $orderby    = array();
+       protected $order      = array();
+       protected $groupby    = array();
+       protected $having     = array();
+       protected $distinct   = FALSE;
+       protected $limit      = FALSE;
+       protected $offset     = FALSE;
+       protected $last_query = '';
+
+       // Stack of queries for push/pop
+       protected $query_history = array();
+
+       /**
+        * Returns a singleton instance of Database.
+        *
+        * @param   mixed   configuration array or DSN
+        * @return  Database_Core
+        */
+       public static function & instance($name = 'default', $config = NULL)
+       {
+               if ( ! isset(Database::$instances[$name]))
+               {
+                       // Create a new instance
+                       Database::$instances[$name] = new Database($config === NULL ? $name : $config);
+               }
+
+               return Database::$instances[$name];
+       }
+
+       /**
+        * Returns the name of a given database instance.
+        *
+        * @param   Database  instance of Database
+        * @return  string
+        */
+       public static function instance_name(Database $db)
+       {
+               return array_search($db, Database::$instances, TRUE);
+       }
+
+       /**
+        * Sets up the database configuration, loads the Database_Driver.
+        *
+        * @throws  Kohana_Database_Exception
+        */
+       public function __construct($config = array())
+       {
+               if (empty($config))
+               {
+                       // Load the default group
+                       $config = Kohana::config('database.default');
+               }
+               elseif (is_array($config) AND count($config) > 0)
+               {
+                       if ( ! array_key_exists('connection', $config))
+                       {
+                               $config = array('connection' => $config);
+                       }
+               }
+               elseif (is_string($config))
+               {
+                       // The config is a DSN string
+                       if (strpos($config, '://') !== FALSE)
+                       {
+                               $config = array('connection' => $config);
+                       }
+                       // The config is a group name
+                       else
+                       {
+                               $name = $config;
+
+                               // Test the config group name
+                               if (($config = Kohana::config('database.'.$config)) === NULL)
+                                       throw new Kohana_Database_Exception('database.undefined_group', $name);
+                       }
+               }
+
+               // Merge the default config with the passed config
+               $this->config = array_merge($this->config, $config);
+
+               if (is_string($this->config['connection']))
+               {
+                       // Make sure the connection is valid
+                       if (strpos($this->config['connection'], '://') === FALSE)
+                               throw new Kohana_Database_Exception('database.invalid_dsn', $this->config['connection']);
+
+                       // Parse the DSN, creating an array to hold the connection parameters
+                       $db = array
+                       (
+                               'type'     => FALSE,
+                               'user'     => FALSE,
+                               'pass'     => FALSE,
+                               'host'     => FALSE,
+                               'port'     => FALSE,
+                               'socket'   => FALSE,
+                               'database' => FALSE
+                       );
+
+                       // Get the protocol and arguments
+                       list ($db['type'], $connection) = explode('://', $this->config['connection'], 2);
+
+                       if (strpos($connection, '@') !== FALSE)
+                       {
+                               // Get the username and password
+                               list ($db['pass'], $connection) = explode('@', $connection, 2);
+                               // Check if a password is supplied
+                               $logindata = explode(':', $db['pass'], 2);
+                               $db['pass'] = (count($logindata) > 1) ? $logindata[1] : '';
+                               $db['user'] = $logindata[0];
+
+                               // Prepare for finding the database
+                               $connection = explode('/', $connection);
+
+                               // Find the database name
+                               $db['database'] = array_pop($connection);
+
+                               // Reset connection string
+                               $connection = implode('/', $connection);
+
+                               // Find the socket
+                               if (preg_match('/^unix\([^)]++\)/', $connection))
+                               {
+                                       // This one is a little hairy: we explode based on the end of
+                                       // the socket, removing the 'unix(' from the connection string
+                                       list ($db['socket'], $connection) = explode(')', substr($connection, 5), 2);
+                               }
+                               elseif (strpos($connection, ':') !== FALSE)
+                               {
+                                       // Fetch the host and port name
+                                       list ($db['host'], $db['port']) = explode(':', $connection, 2);
+                               }
+                               else
+                               {
+                                       $db['host'] = $connection;
+                               }
+                       }
+                       else
+                       {
+                               // File connection
+                               $connection = explode('/', $connection);
+
+                               // Find database file name
+                               $db['database'] = array_pop($connection);
+
+                               // Find database directory name
+                               $db['socket'] = implode('/', $connection).'/';
+                       }
+
+                       // Reset the connection array to the database config
+                       $this->config['connection'] = $db;
+               }
+               // Set driver name
+               $driver = 'Database_'.ucfirst($this->config['connection']['type']).'_Driver';
+
+               // Load the driver
+               if ( ! Kohana::auto_load($driver))
+                       throw new Kohana_Database_Exception('core.driver_not_found', $this->config['connection']['type'], get_class($this));
+
+               // Initialize the driver
+               $this->driver = new $driver($this->config);
+
+               // Validate the driver
+               if ( ! ($this->driver instanceof Database_Driver))
+                       throw new Kohana_Database_Exception('core.driver_implements', $this->config['connection']['type'], get_class($this), 'Database_Driver');
+
+               Kohana::log('debug', 'Database Library initialized');
+       }
+
+       /**
+        * Simple connect method to get the database queries up and running.
+        *
+        * @return  void
+        */
+       public function connect()
+       {
+               // A link can be a resource or an object
+               if ( ! is_resource($this->link) AND ! is_object($this->link))
+               {
+                       $this->link = $this->driver->connect();
+                       if ( ! is_resource($this->link) AND ! is_object($this->link))
+                               throw new Kohana_Database_Exception('database.connection', $this->driver->show_error());
+
+                       // Clear password after successful connect
+                       $this->config['connection']['pass'] = NULL;
+               }
+       }
+
+       /**
+        * Runs a query into the driver and returns the result.
+        *
+        * @param   string  SQL query to execute
+        * @return  Database_Result
+        */
+       public function query($sql = '')
+       {
+               if ($sql == '') return FALSE;
+
+               // No link? Connect!
+               $this->link or $this->connect();
+
+               // Start the benchmark
+               $start = microtime(TRUE);
+
+               if (func_num_args() > 1) //if we have more than one argument ($sql)
+               {
+                       $argv = func_get_args();
+                       $binds = (is_array(next($argv))) ? current($argv) : array_slice($argv, 1);
+               }
+
+               // Compile binds if needed
+               if (isset($binds))
+               {
+                       $sql = $this->compile_binds($sql, $binds);
+               }
+
+               // Fetch the result
+               $result = $this->driver->query($this->last_query = $sql);
+
+               // Stop the benchmark
+               $stop = microtime(TRUE);
+
+               if ($this->config['benchmark'] == TRUE)
+               {
+                       // Benchmark the query
+                       Database::$benchmarks[] = array('query' => $sql, 'time' => $stop - $start, 'rows' => count($result));
+               }
+
+               return $result;
+       }
+
+       /**
+        * Selects the column names for a database query.
+        *
+        * @param   string  string or array of column names to select
+        * @return  Database_Core  This Database object.
+        */
+       public function select($sql = '*')
+       {
+               if (func_num_args() > 1)
+               {
+                       $sql = func_get_args();
+               }
+               elseif (is_string($sql))
+               {
+                       $sql = explode(',', $sql);
+               }
+               else
+               {
+                       $sql = (array) $sql;
+               }
+
+               foreach ($sql as $val)
+               {
+                       if (($val = trim($val)) === '') continue;
+
+                       if (strpos($val, '(') === FALSE AND $val !== '*')
+                       {
+                               if (preg_match('/^DISTINCT\s++(.+)$/i', $val, $matches))
+                               {
+                                       // Only prepend with table prefix if table name is specified
+                                       $val = (strpos($matches[1], '.') !== FALSE) ? $this->config['table_prefix'].$matches[1] : $matches[1];
+
+                                       $this->distinct = TRUE;
+                               }
+                               else
+                               {
+                                       $val = (strpos($val, '.') !== FALSE) ? $this->config['table_prefix'].$val : $val;
+                               }
+
+                               $val = $this->driver->escape_column($val);
+                       }
+
+                       $this->select[] = $val;
+               }
+
+               return $this;
+       }
+
+       /**
+        * Selects the from table(s) for a database query.
+        *
+        * @param   string  string or array of tables to select
+        * @return  Database_Core  This Database object.
+        */
+       public function from($sql)
+       {
+               if (func_num_args() > 1)
+               {
+                       $sql = func_get_args();
+               }
+               elseif (is_string($sql))
+               {
+                       $sql = explode(',', $sql);
+               }
+               else
+               {
+                       $sql = array($sql);
+               }
+
+               foreach ($sql as $val)
+               {
+                       if (is_string($val))
+                       {
+                               if (($val = trim($val)) === '') continue;
+
+                               // TODO: Temporary solution, this should be moved to database driver (AS is checked for twice)
+                               if (stripos($val, ' AS ') !== FALSE)
+                               {
+                                       $val = str_ireplace(' AS ', ' AS ', $val);
+
+                                       list($table, $alias) = explode(' AS ', $val);
+
+                                       // Attach prefix to both sides of the AS
+                                       $val = $this->config['table_prefix'].$table.' AS '.$this->config['table_prefix'].$alias;
+                               }
+                               else
+                               {
+                                       $val = $this->config['table_prefix'].$val;
+                               }
+                       }
+
+                       $this->from[] = $val;
+               }
+
+               return $this;
+       }
+
+       /**
+        * Generates the JOIN portion of the query.
+        *
+        * @param   string        table name
+        * @param   string|array  where key or array of key => value pairs
+        * @param   string        where value
+        * @param   string        type of join
+        * @return  Database_Core        This Database object.
+        */
+       public function join($table, $key, $value = NULL, $type = '')
+       {
+               $join = array();
+
+               if ( ! empty($type))
+               {
+                       $type = strtoupper(trim($type));
+
+                       if ( ! in_array($type, array('LEFT', 'RIGHT', 'OUTER', 'INNER', 'LEFT OUTER', 'RIGHT OUTER'), TRUE))
+                       {
+                               $type = '';
+                       }
+                       else
+                       {
+                               $type .= ' ';
+                       }
+               }
+
+               $cond = array();
+               $keys  = is_array($key) ? $key : array($key => $value);
+               foreach ($keys as $key => $value)
+               {
+                       $key    = (strpos($key, '.') !== FALSE) ? $this->config['table_prefix'].$key : $key;
+
+                       if (is_string($value))
+                       {
+                               // Only escape if it's a string
+                               $value = $this->driver->escape_column($this->config['table_prefix'].$value);
+                       }
+
+                       $cond[] = $this->driver->where($key, $value, 'AND ', count($cond), FALSE);
+               }
+
+               if ( ! is_array($this->join))
+               {
+                       $this->join = array();
+               }
+
+               if ( ! is_array($table))
+               {
+                       $table = array($table);
+               }
+
+               foreach ($table as $t)
+               {
+                       if (is_string($t))
+                       {
+                               // TODO: Temporary solution, this should be moved to database driver (AS is checked for twice)
+                               if (stripos($t, ' AS ') !== FALSE)
+                               {
+                                       $t = str_ireplace(' AS ', ' AS ', $t);
+
+                                       list($table, $alias) = explode(' AS ', $t);
+
+                                       // Attach prefix to both sides of the AS
+                                       $t = $this->config['table_prefix'].$table.' AS '.$this->config['table_prefix'].$alias;
+                               }
+                               else
+                               {
+                                       $t = $this->config['table_prefix'].$t;
+                               }
+                       }
+
+                       $join['tables'][] = $this->driver->escape_column($t);
+               }
+
+               $join['conditions'] = '('.trim(implode(' ', $cond)).')';
+               $join['type'] = $type;
+
+               $this->join[] = $join;
+
+               return $this;
+       }
+
+
+       /**
+        * Selects the where(s) for a database query.
+        *
+        * @param   string|array  key name or array of key => value pairs
+        * @param   string        value to match with key
+        * @param   boolean       disable quoting of WHERE clause
+        * @return  Database_Core        This Database object.
+        */
+       public function where($key, $value = NULL, $quote = TRUE)
+       {
+               $quote = (func_num_args() < 2 AND ! is_array($key)) ? -1 : $quote;
+               if (is_object($key))
+               {
+                       $keys = array((string) $key => '');
+               }
+               elseif ( ! is_array($key))
+               {
+                       $keys = array($key => $value);
+               }
+               else
+               {
+                       $keys = $key;
+               }
+
+               foreach ($keys as $key => $value)
+               {
+                       $key           = (strpos($key, '.') !== FALSE) ? $this->config['table_prefix'].$key : $key;
+                       $this->where[] = $this->driver->where($key, $value, 'AND ', count($this->where), $quote);
+               }
+
+               return $this;
+       }
+
+       /**
+        * Selects the or where(s) for a database query.
+        *
+        * @param   string|array  key name or array of key => value pairs
+        * @param   string        value to match with key
+        * @param   boolean       disable quoting of WHERE clause
+        * @return  Database_Core        This Database object.
+        */
+       public function orwhere($key, $value = NULL, $quote = TRUE)
+       {
+               $quote = (func_num_args() < 2 AND ! is_array($key)) ? -1 : $quote;
+               if (is_object($key))
+               {
+                       $keys = array((string) $key => '');
+               }
+               elseif ( ! is_array($key))
+               {
+                       $keys = array($key => $value);
+               }
+               else
+               {
+                       $keys = $key;
+               }
+
+               foreach ($keys as $key => $value)
+               {
+                       $key           = (strpos($key, '.') !== FALSE) ? $this->config['table_prefix'].$key : $key;
+                       $this->where[] = $this->driver->where($key, $value, 'OR ', count($this->where), $quote);
+               }
+
+               return $this;
+       }
+
+       /**
+        * Selects the like(s) for a database query.
+        *
+        * @param   string|array  field name or array of field => match pairs
+        * @param   string        like value to match with field
+        * @param   boolean       automatically add starting and ending wildcards
+        * @return  Database_Core        This Database object.
+        */
+       public function like($field, $match = '', $auto = TRUE)
+       {
+               $fields = is_array($field) ? $field : array($field => $match);
+
+               foreach ($fields as $field => $match)
+               {
+                       $field         = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
+                       $this->where[] = $this->driver->like($field, $match, $auto, 'AND ', count($this->where));
+               }
+
+               return $this;
+       }
+
+       /**
+        * Selects the or like(s) for a database query.
+        *
+        * @param   string|array  field name or array of field => match pairs
+        * @param   string        like value to match with field
+        * @param   boolean       automatically add starting and ending wildcards
+        * @return  Database_Core        This Database object.
+        */
+       public function orlike($field, $match = '', $auto = TRUE)
+       {
+               $fields = is_array($field) ? $field : array($field => $match);
+
+               foreach ($fields as $field => $match)
+               {
+                       $field         = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
+                       $this->where[] = $this->driver->like($field, $match, $auto, 'OR ', count($this->where));
+               }
+
+               return $this;
+       }
+
+       /**
+        * Selects the not like(s) for a database query.
+        *
+        * @param   string|array  field name or array of field => match pairs
+        * @param   string        like value to match with field
+        * @param   boolean       automatically add starting and ending wildcards
+        * @return  Database_Core        This Database object.
+        */
+       public function notlike($field, $match = '', $auto = TRUE)
+       {
+               $fields = is_array($field) ? $field : array($field => $match);
+
+               foreach ($fields as $field => $match)
+               {
+                       $field         = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
+                       $this->where[] = $this->driver->notlike($field, $match, $auto, 'AND ', count($this->where));
+               }
+
+               return $this;
+       }
+
+       /**
+        * Selects the or not like(s) for a database query.
+        *
+        * @param   string|array  field name or array of field => match pairs
+        * @param   string        like value to match with field
+        * @return  Database_Core        This Database object.
+        */
+       public function ornotlike($field, $match = '', $auto = TRUE)
+       {
+               $fields = is_array($field) ? $field : array($field => $match);
+
+               foreach ($fields as $field => $match)
+               {
+                       $field         = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
+                       $this->where[] = $this->driver->notlike($field, $match, $auto, 'OR ', count($this->where));
+               }
+
+               return $this;
+       }
+
+       /**
+        * Selects the like(s) for a database query.
+        *
+        * @param   string|array  field name or array of field => match pairs
+        * @param   string        like value to match with field
+        * @return  Database_Core        This Database object.
+        */
+       public function regex($field, $match = '')
+       {
+               $fields = is_array($field) ? $field : array($field => $match);
+
+               foreach ($fields as $field => $match)
+               {
+                       $field         = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
+                       $this->where[] = $this->driver->regex($field, $match, 'AND ', count($this->where));
+               }
+
+               return $this;
+       }
+
+       /**
+        * Selects the or like(s) for a database query.
+        *
+        * @param   string|array  field name or array of field => match pairs
+        * @param   string        like value to match with field
+        * @return  Database_Core        This Database object.
+        */
+       public function orregex($field, $match = '')
+       {
+               $fields = is_array($field) ? $field : array($field => $match);
+
+               foreach ($fields as $field => $match)
+               {
+                       $field         = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
+                       $this->where[] = $this->driver->regex($field, $match, 'OR ', count($this->where));
+               }
+
+               return $this;
+       }
+
+       /**
+        * Selects the not regex(s) for a database query.
+        *
+        * @param   string|array  field name or array of field => match pairs
+        * @param   string        regex value to match with field
+        * @return  Database_Core        This Database object.
+        */
+       public function notregex($field, $match = '')
+       {
+               $fields = is_array($field) ? $field : array($field => $match);
+
+               foreach ($fields as $field => $match)
+               {
+                       $field         = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
+                       $this->where[] = $this->driver->notregex($field, $match, 'AND ', count($this->where));
+               }
+
+               return $this;
+       }
+
+       /**
+        * Selects the or not regex(s) for a database query.
+        *
+        * @param   string|array  field name or array of field => match pairs
+        * @param   string        regex value to match with field
+        * @return  Database_Core        This Database object.
+        */
+       public function ornotregex($field, $match = '')
+       {
+               $fields = is_array($field) ? $field : array($field => $match);
+
+               foreach ($fields as $field => $match)
+               {
+                       $field         = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
+                       $this->where[] = $this->driver->notregex($field, $match, 'OR ', count($this->where));
+               }
+
+               return $this;
+       }
+
+       /**
+        * Chooses the column to group by in a select query.
+        *
+        * @param   string  column name to group by
+        * @return  Database_Core  This Database object.
+        */
+       public function groupby($by)
+       {
+               if ( ! is_array($by))
+               {
+                       $by = explode(',', (string) $by);
+               }
+
+               foreach ($by as $val)
+               {
+                       $val = trim($val);
+
+                       if ($val != '')
+                       {
+                               // Add the table prefix if we are using table.column names
+                               if(strpos($val, '.'))
+                               {
+                                       $val = $this->config['table_prefix'].$val;
+                               }
+
+                               $this->groupby[] = $this->driver->escape_column($val);
+                       }
+               }
+
+               return $this;
+       }
+
+       /**
+        * Selects the having(s) for a database query.
+        *
+        * @param   string|array  key name or array of key => value pairs
+        * @param   string        value to match with key
+        * @param   boolean       disable quoting of WHERE clause
+        * @return  Database_Core        This Database object.
+        */
+       public function having($key, $value = '', $quote = TRUE)
+       {
+               $this->having[] = $this->driver->where($key, $value, 'AND', count($this->having), TRUE);
+               return $this;
+       }
+
+       /**
+        * Selects the or having(s) for a database query.
+        *
+        * @param   string|array  key name or array of key => value pairs
+        * @param   string        value to match with key
+        * @param   boolean       disable quoting of WHERE clause
+        * @return  Database_Core        This Database object.
+        */
+       public function orhaving($key, $value = '', $quote = TRUE)
+       {
+               $this->having[] = $this->driver->where($key, $value, 'OR', count($this->having), TRUE);
+               return $this;
+       }
+
+       /**
+        * Chooses which column(s) to order the select query by.
+        *
+        * @param   string|array  column(s) to order on, can be an array, single column, or comma seperated list of columns
+        * @param   string        direction of the order
+        * @return  Database_Core        This Database object.
+        */
+       public function orderby($orderby, $direction = NULL)
+       {
+               if ( ! is_array($orderby))
+               {
+                       $orderby = array($orderby => $direction);
+               }
+
+               foreach ($orderby as $column => $direction)
+               {
+                       $direction = strtoupper(trim($direction));
+
+                       // Add a direction if the provided one isn't valid
+                       if ( ! in_array($direction, array('ASC', 'DESC', 'RAND()', 'RANDOM()', 'NULL')))
+                       {
+                               $direction = 'ASC';
+                       }
+
+                       // Add the table prefix if a table.column was passed
+                       if (strpos($column, '.'))
+                       {
+                               $column = $this->config['table_prefix'].$column;
+                       }
+
+                       $this->orderby[] = $this->driver->escape_column($column).' '.$direction;
+               }
+
+               return $this;
+       }
+
+       /**
+        * Selects the limit section of a query.
+        *
+        * @param   integer  number of rows to limit result to
+        * @param   integer  offset in result to start returning rows from
+        * @return  Database_Core   This Database object.
+        */
+       public function limit($limit, $offset = NULL)
+       {
+               $this->limit  = (int) $limit;
+
+               if ($offset !== NULL OR ! is_int($this->offset))
+               {
+                       $this->offset($offset);
+               }
+
+               return $this;
+       }
+
+       /**
+        * Sets the offset portion of a query.
+        *
+        * @param   integer  offset value
+        * @return  Database_Core   This Database object.
+        */
+       public function offset($value)
+       {
+               $this->offset = (int) $value;
+
+               return $this;
+       }
+
+       /**
+        * Allows key/value pairs to be set for inserting or updating.
+        *
+        * @param   string|array  key name or array of key => value pairs
+        * @param   string        value to match with key
+        * @return  Database_Core        This Database object.
+        */
+       public function set($key, $value = '')
+       {
+               if ( ! is_array($key))
+               {
+                       $key = array($key => $value);
+               }
+
+               foreach ($key as $k => $v)
+               {
+                       // Add a table prefix if the column includes the table.
+                       if (strpos($k, '.'))
+                               $k = $this->config['table_prefix'].$k;
+
+                       $this->set[$k] = $this->driver->escape($v);
+               }
+
+               return $this;
+       }
+
+       /**
+        * Compiles the select statement based on the other functions called and runs the query.
+        *
+        * @param   string  table name
+        * @param   string  limit clause
+        * @param   string  offset clause
+        * @return  Database_Result
+        */
+       public function get($table = '', $limit = NULL, $offset = NULL)
+       {
+               if ($table != '')
+               {
+                       $this->from($table);
+               }
+
+               if ( ! is_null($limit))
+               {
+                       $this->limit($limit, $offset);
+               }
+
+               $sql = $this->driver->compile_select(get_object_vars($this));
+
+               $this->reset_select();
+
+               $result = $this->query($sql);
+
+               $this->last_query = $sql;
+
+               return $result;
+       }
+
+       /**
+        * Compiles the select statement based on the other functions called and runs the query.
+        *
+        * @param   string  table name
+        * @param   array   where clause
+        * @param   string  limit clause
+        * @param   string  offset clause
+        * @return  Database_Core  This Database object.
+        */
+       public function getwhere($table = '', $where = NULL, $limit = NULL, $offset = NULL)
+       {
+               if ($table != '')
+               {
+                       $this->from($table);
+               }
+
+               if ( ! is_null($where))
+               {
+                       $this->where($where);
+               }
+
+               if ( ! is_null($limit))
+               {
+                       $this->limit($limit, $offset);
+               }
+
+               $sql = $this->driver->compile_select(get_object_vars($this));
+
+               $this->reset_select();
+
+               $result = $this->query($sql);
+
+               return $result;
+       }
+
+       /**
+        * Compiles the select statement based on the other functions called and returns the query string.
+        *
+        * @param   string  table name
+        * @param   string  limit clause
+        * @param   string  offset clause
+        * @return  string  sql string
+        */
+       public function compile($table = '', $limit = NULL, $offset = NULL)
+       {
+               if ($table != '')
+               {
+                       $this->from($table);
+               }
+
+               if ( ! is_null($limit))
+               {
+                       $this->limit($limit, $offset);
+               }
+
+               $sql = $this->driver->compile_select(get_object_vars($this));
+
+               $this->reset_select();
+
+               return $sql;
+       }
+
+       /**
+        * Compiles an insert string and runs the query.
+        *
+        * @param   string  table name
+        * @param   array   array of key/value pairs to insert
+        * @return  Database_Result  Query result
+        */
+       public function insert($table = '', $set = NULL)
+       {
+               if ( ! is_null($set))
+               {
+                       $this->set($set);
+               }
+
+               if ($this->set == NULL)
+                       throw new Kohana_Database_Exception('database.must_use_set');
+
+               if ($table == '')
+               {
+                       if ( ! isset($this->from[0]))
+                               throw new Kohana_Database_Exception('database.must_use_table');
+
+                       $table = $this->from[0];
+               }
+
+               // If caching is enabled, clear the cache before inserting
+               ($this->config['cache'] === TRUE) and $this->clear_cache();
+
+               $sql = $this->driver->insert($this->config['table_prefix'].$table, array_keys($this->set), array_values($this->set));
+
+               $this->reset_write();
+
+               return $this->query($sql);
+       }
+
+       /**
+        * Adds an "IN" condition to the where clause
+        *
+        * @param   string  Name of the column being examined
+        * @param   mixed   An array or string to match against
+        * @param   bool    Generate a NOT IN clause instead
+        * @return  Database_Core  This Database object.
+        */
+       public function in($field, $values, $not = FALSE)
+       {
+               if (is_array($values))
+               {
+                       $escaped_values = array();
+                       foreach ($values as $v)
+                       {
+                               if (is_numeric($v))
+                               {
+                                       $escaped_values[] = $v;
+                               }
+                               else
+                               {
+                                       $escaped_values[] = "'".$this->driver->escape_str($v)."'";
+                               }
+                       }
+                       $values = implode(",", $escaped_values);
+               }
+
+               $where = $this->driver->escape_column(((strpos($field,'.') !== FALSE) ? $this->config['table_prefix'] : ''). $field).' '.($not === TRUE ? 'NOT ' : '').'IN ('.$values.')';
+               $this->where[] = $this->driver->where($where, '', 'AND ', count($this->where), -1);
+
+               return $this;
+       }
+
+       /**
+        * Adds a "NOT IN" condition to the where clause
+        *
+        * @param   string  Name of the column being examined
+        * @param   mixed   An array or string to match against
+        * @return  Database_Core  This Database object.
+        */
+       public function notin($field, $values)
+       {
+               return $this->in($field, $values, TRUE);
+       }
+
+       /**
+        * Compiles a merge string and runs the query.
+        *
+        * @param   string  table name
+        * @param   array   array of key/value pairs to merge
+        * @return  Database_Result  Query result
+        */
+       public function merge($table = '', $set = NULL)
+       {
+               if ( ! is_null($set))
+               {
+                       $this->set($set);
+               }
+
+               if ($this->set == NULL)
+                       throw new Kohana_Database_Exception('database.must_use_set');
+
+               if ($table == '')
+               {
+                       if ( ! isset($this->from[0]))
+                               throw new Kohana_Database_Exception('database.must_use_table');
+
+                       $table = $this->from[0];
+               }
+
+               $sql = $this->driver->merge($this->config['table_prefix'].$table, array_keys($this->set), array_values($this->set));
+
+               $this->reset_write();
+               return $this->query($sql);
+       }
+
+       /**
+        * Compiles an update string and runs the query.
+        *
+        * @param   string  table name
+        * @param   array   associative array of update values
+        * @param   array   where clause
+        * @return  Database_Result  Query result
+        */
+       public function update($table = '', $set = NULL, $where = NULL)
+       {
+               if ( is_array($set))
+               {
+                       $this->set($set);
+               }
+
+               if ( ! is_null($where))
+               {
+                       $this->where($where);
+               }
+
+               if ($this->set == FALSE)
+                       throw new Kohana_Database_Exception('database.must_use_set');
+
+               if ($table == '')
+               {
+                       if ( ! isset($this->from[0]))
+                               throw new Kohana_Database_Exception('database.must_use_table');
+
+                       $table = $this->from[0];
+               }
+
+               $sql = $this->driver->update($this->config['table_prefix'].$table, $this->set, $this->where);
+
+               $this->reset_write();
+               return $this->query($sql);
+       }
+
+       /**
+        * Compiles a delete string and runs the query.
+        *
+        * @param   string  table name
+        * @param   array   where clause
+        * @return  Database_Result  Query result
+        */
+       public function delete($table = '', $where = NULL)
+       {
+               if ($table == '')
+               {
+                       if ( ! isset($this->from[0]))
+                               throw new Kohana_Database_Exception('database.must_use_table');
+
+                       $table = $this->from[0];
+               }
+               else
+               {
+                       $table = $this->config['table_prefix'].$table;
+               }
+
+               if (! is_null($where))
+               {
+                       $this->where($where);
+               }
+
+               if (count($this->where) < 1)
+                       throw new Kohana_Database_Exception('database.must_use_where');
+
+               $sql = $this->driver->delete($table, $this->where);
+
+               $this->reset_write();
+               return $this->query($sql);
+       }
+
+       /**
+        * Returns the last query run.
+        *
+        * @return  string SQL
+        */
+       public function last_query()
+       {
+          return $this->last_query;
+       }
+
+       /**
+        * Count query records.
+        *
+        * @param   string   table name
+        * @param   array    where clause
+        * @return  integer
+        */
+       public function count_records($table = FALSE, $where = NULL)
+       {
+               if (count($this->from) < 1)
+               {
+                       if ($table == FALSE)
+                               throw new Kohana_Database_Exception('database.must_use_table');
+
+                       $this->from($table);
+               }
+
+               if ($where !== NULL)
+               {
+                       $this->where($where);
+               }
+
+               $query = $this->select('COUNT(*) AS '.$this->escape_column('records_found'))->get()->result(TRUE);
+
+               return (int) $query->current()->records_found;
+       }
+
+       /**
+        * Resets all private select variables.
+        *
+        * @return  void
+        */
+       protected function reset_select()
+       {
+               $this->select   = array();
+               $this->from     = array();
+               $this->join     = array();
+               $this->where    = array();
+               $this->orderby  = array();
+               $this->groupby  = array();
+               $this->having   = array();
+               $this->distinct = FALSE;
+               $this->limit    = FALSE;
+               $this->offset   = FALSE;
+       }
+
+       /**
+        * Resets all private insert and update variables.
+        *
+        * @return  void
+        */
+       protected function reset_write()
+       {
+               $this->set   = array();
+               $this->from  = array();
+               $this->where = array();
+       }
+
+       /**
+        * Lists all the tables in the current database.
+        *
+        * @return  array
+        */
+       public function list_tables()
+       {
+               $this->link or $this->connect();
+
+               return $this->driver->list_tables();
+       }
+
+       /**
+        * See if a table exists in the database.
+        *
+        * @param   string   table name
+        * @param   boolean  True to attach table prefix
+        * @return  boolean
+        */
+       public function table_exists($table_name, $prefix = TRUE)
+       {
+               if ($prefix)
+                       return in_array($this->config['table_prefix'].$table_name, $this->list_tables());
+               else
+                       return in_array($table_name, $this->list_tables());
+       }
+
+       /**
+        * Combine a SQL statement with the bind values. Used for safe queries.
+        *
+        * @param   string  query to bind to the values
+        * @param   array   array of values to bind to the query
+        * @return  string
+        */
+       public function compile_binds($sql, $binds)
+       {
+               foreach ((array) $binds as $val)
+               {
+                       // If the SQL contains no more bind marks ("?"), we're done.
+                       if (($next_bind_pos = strpos($sql, '?')) === FALSE)
+                               break;
+
+                       // Properly escape the bind value.
+                       $val = $this->driver->escape($val);
+
+                       // Temporarily replace possible bind marks ("?"), in the bind value itself, with a placeholder.
+                       $val = str_replace('?', '{%B%}', $val);
+
+                       // Replace the first bind mark ("?") with its corresponding value.
+                       $sql = substr($sql, 0, $next_bind_pos).$val.substr($sql, $next_bind_pos + 1);
+               }
+
+               // Restore placeholders.
+               return str_replace('{%B%}', '?', $sql);
+       }
+
+       /**
+        * Get the field data for a database table, along with the field's attributes.
+        *
+        * @param   string  table name
+        * @return  array
+        */
+       public function field_data($table = '')
+       {
+               $this->link or $this->connect();
+
+               return $this->driver->field_data($this->config['table_prefix'].$table);
+       }
+
+       /**
+        * Get the field data for a database table, along with the field's attributes.
+        *
+        * @param   string  table name
+        * @return  array
+        */
+       public function list_fields($table = '')
+       {
+               $this->link or $this->connect();
+
+               return $this->driver->list_fields($this->config['table_prefix'].$table);
+       }
+
+       /**
+        * Escapes a value for a query.
+        *
+        * @param   mixed   value to escape
+        * @return  string
+        */
+       public function escape($value)
+       {
+               return $this->driver->escape($value);
+       }
+
+       /**
+        * Escapes a string for a query.
+        *
+        * @param   string  string to escape
+        * @return  string
+        */
+       public function escape_str($str)
+       {
+               return $this->driver->escape_str($str);
+       }
+
+       /**
+        * Escapes a table name for a query.
+        *
+        * @param   string  string to escape
+        * @return  string
+        */
+       public function escape_table($table)
+       {
+               return $this->driver->escape_table($table);
+       }
+
+       /**
+        * Escapes a column name for a query.
+        *
+        * @param   string  string to escape
+        * @return  string
+        */
+       public function escape_column($table)
+       {
+               return $this->driver->escape_column($table);
+       }
+
+       /**
+        * Returns table prefix of current configuration.
+        *
+        * @return  string
+        */
+       public function table_prefix()
+       {
+               return $this->config['table_prefix'];
+       }
+
+       /**
+        * Clears the query cache.
+        *
+        * @param   string|TRUE  clear cache by SQL statement or TRUE for last query
+        * @return  Database_Core       This Database object.
+        */
+       public function clear_cache($sql = NULL)
+       {
+               if ($sql === TRUE)
+               {
+                       $this->driver->clear_cache($this->last_query);
+               }
+               elseif (is_string($sql))
+               {
+                       $this->driver->clear_cache($sql);
+               }
+               else
+               {
+                       $this->driver->clear_cache();
+               }
+
+               return $this;
+       }
+
+       /**
+        * Pushes existing query space onto the query stack.  Use push
+        * and pop to prevent queries from clashing before they are
+        * executed
+        *
+        * @return Database_Core This Databaes object
+        */
+       public function push()
+       {
+               array_push($this->query_history, array(
+                       $this->select,
+                       $this->from,
+                       $this->join,
+                       $this->where,
+                       $this->orderby,
+                       $this->order,
+                       $this->groupby,
+                       $this->having,
+                       $this->distinct,
+                       $this->limit,
+                       $this->offset
+               ));
+
+               $this->reset_select();
+
+               return $this;
+       }
+
+       /**
+        * Pops from query stack into the current query space.
+        *
+        * @return Database_Core This Databaes object
+        */
+       public function pop()
+       {
+               if (count($this->query_history) == 0)
+               {
+                       // No history
+                       return $this;
+               }
+
+               list(
+                       $this->select,
+                       $this->from,
+                       $this->join,
+                       $this->where,
+                       $this->orderby,
+                       $this->order,
+                       $this->groupby,
+                       $this->having,
+                       $this->distinct,
+                       $this->limit,
+                       $this->offset
+               ) = array_pop($this->query_history);
+
+               return $this;
+       }
+
+       /**
+        * Count the number of records in the last query, without LIMIT or OFFSET applied.
+        *
+        * @return  integer
+        */
+       public function count_last_query()
+       {
+               if ($sql = $this->last_query())
+               {
+                       if (stripos($sql, 'LIMIT') !== FALSE)
+                       {
+                               // Remove LIMIT from the SQL
+                               $sql = preg_replace('/\sLIMIT\s+[^a-z]+/i', ' ', $sql);
+                       }
+
+                       if (stripos($sql, 'OFFSET') !== FALSE)
+                       {
+                               // Remove OFFSET from the SQL
+                               $sql = preg_replace('/\sOFFSET\s+\d+/i', '', $sql);
+                       }
+
+                       // Get the total rows from the last query executed
+                       $result = $this->query
+                       (
+                               'SELECT COUNT(*) AS '.$this->escape_column('total_rows').' '.
+                               'FROM ('.trim($sql).') AS '.$this->escape_table('counted_results')
+                       );
+
+                       // Return the total number of rows from the query
+                       return (int) $result->current()->total_rows;
+               }
+
+               return FALSE;
+       }
+
+} // End Database Class
+
+
+/**
+ * Sets the code for a Database exception.
+ */
+class Kohana_Database_Exception extends Kohana_Exception {
+
+       protected $code = E_DATABASE_ERROR;
+
+} // End Kohana Database Exception
diff --git a/Server/system/libraries/Database_Expression.php b/Server/system/libraries/Database_Expression.php
new file mode 100644 (file)
index 0000000..940a636
--- /dev/null
@@ -0,0 +1,26 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Database expression class to allow for explicit joins and where expressions.
+ *
+ * $Id: Database_Expression.php 4037 2009-03-04 23:35:53Z jheathco $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2009 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Database_Expression_Core {
+
+       protected $expression;
+
+       public function __construct($expression)
+       {
+               $this->expression = $expression;
+       }
+
+       public function __toString()
+       {
+               return (string) $this->expression;
+       }
+
+} // End Database Expr Class
\ No newline at end of file
diff --git a/Server/system/libraries/Encrypt.php b/Server/system/libraries/Encrypt.php
new file mode 100644 (file)
index 0000000..3d564f9
--- /dev/null
@@ -0,0 +1,164 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * The Encrypt library provides two-way encryption of text and binary strings
+ * using the MCrypt extension.
+ * @see http://php.net/mcrypt
+ *
+ * $Id: Encrypt.php 4072 2009-03-13 17:20:38Z jheathco $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Encrypt_Core {
+
+       // OS-dependant RAND type to use
+       protected static $rand;
+
+       // Configuration
+       protected $config;
+
+       /**
+        * Returns a singleton instance of Encrypt.
+        *
+        * @param   array  configuration options
+        * @return  Encrypt_Core
+        */
+       public static function instance($config = NULL)
+       {
+               static $instance;
+
+               // Create the singleton
+               empty($instance) and $instance = new Encrypt((array) $config);
+
+               return $instance;
+       }
+
+       /**
+        * Loads encryption configuration and validates the data.
+        *
+        * @param   array|string      custom configuration or config group name
+        * @throws  Kohana_Exception
+        */
+       public function __construct($config = FALSE)
+       {
+               if ( ! defined('MCRYPT_ENCRYPT'))
+                       throw new Kohana_Exception('encrypt.requires_mcrypt');
+
+               if (is_string($config))
+               {
+                       $name = $config;
+
+                       // Test the config group name
+                       if (($config = Kohana::config('encryption.'.$config)) === NULL)
+                               throw new Kohana_Exception('encrypt.undefined_group', $name);
+               }
+
+               if (is_array($config))
+               {
+                       // Append the default configuration options
+                       $config += Kohana::config('encryption.default');
+               }
+               else
+               {
+                       // Load the default group
+                       $config = Kohana::config('encryption.default');
+               }
+
+               if (empty($config['key']))
+                       throw new Kohana_Exception('encrypt.no_encryption_key');
+
+               // Find the max length of the key, based on cipher and mode
+               $size = mcrypt_get_key_size($config['cipher'], $config['mode']);
+
+               if (strlen($config['key']) > $size)
+               {
+                       // Shorten the key to the maximum size
+                       $config['key'] = substr($config['key'], 0, $size);
+               }
+
+               // Find the initialization vector size
+               $config['iv_size'] = mcrypt_get_iv_size($config['cipher'], $config['mode']);
+
+               // Cache the config in the object
+               $this->config = $config;
+
+               Kohana::log('debug', 'Encrypt Library initialized');
+       }
+
+       /**
+        * Encrypts a string and returns an encrypted string that can be decoded.
+        *
+        * @param   string  data to be encrypted
+        * @return  string  encrypted data
+        */
+       public function encode($data)
+       {
+               // Set the rand type if it has not already been set
+               if (Encrypt::$rand === NULL)
+               {
+                       if (KOHANA_IS_WIN)
+                       {
+                               // Windows only supports the system random number generator
+                               Encrypt::$rand = MCRYPT_RAND;
+                       }
+                       else
+                       {
+                               if (defined('MCRYPT_DEV_URANDOM'))
+                               {
+                                       // Use /dev/urandom
+                                       Encrypt::$rand = MCRYPT_DEV_URANDOM;
+                               }
+                               elseif (defined('MCRYPT_DEV_RANDOM'))
+                               {
+                                       // Use /dev/random
+                                       Encrypt::$rand = MCRYPT_DEV_RANDOM;
+                               }
+                               else
+                               {
+                                       // Use the system random number generator
+                                       Encrypt::$rand = MCRYPT_RAND;
+                               }
+                       }
+               }
+
+               if (Encrypt::$rand === MCRYPT_RAND)
+               {
+                       // The system random number generator must always be seeded each
+                       // time it is used, or it will not produce true random results
+                       mt_srand();
+               }
+
+               // Create a random initialization vector of the proper size for the current cipher
+               $iv = mcrypt_create_iv($this->config['iv_size'], Encrypt::$rand);
+
+               // Encrypt the data using the configured options and generated iv
+               $data = mcrypt_encrypt($this->config['cipher'], $this->config['key'], $data, $this->config['mode'], $iv);
+
+               // Use base64 encoding to convert to a string
+               return base64_encode($iv.$data);
+       }
+
+       /**
+        * Decrypts an encoded string back to its original value.
+        *
+        * @param   string  encoded string to be decrypted
+        * @return  string  decrypted data
+        */
+       public function decode($data)
+       {
+               // Convert the data back to binary
+               $data = base64_decode($data);
+
+               // Extract the initialization vector from the data
+               $iv = substr($data, 0, $this->config['iv_size']);
+
+               // Remove the iv from the data
+               $data = substr($data, $this->config['iv_size']);
+
+               // Return the decrypted data, trimming the \0 padding bytes from the end of the data
+               return rtrim(mcrypt_decrypt($this->config['cipher'], $this->config['key'], $data, $this->config['mode'], $iv), "\0");
+       }
+
+} // End Encrypt
diff --git a/Server/system/libraries/Event_Observer.php b/Server/system/libraries/Event_Observer.php
new file mode 100644 (file)
index 0000000..086c8a2
--- /dev/null
@@ -0,0 +1,70 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Kohana event observer. Uses the SPL observer pattern.
+ *
+ * $Id: Event_Observer.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+abstract class Event_Observer implements SplObserver {
+
+       // Calling object
+       protected $caller;
+
+       /**
+        * Initializes a new observer and attaches the subject as the caller.
+        *
+        * @param   object  Event_Subject
+        * @return  void
+        */
+       public function __construct(SplSubject $caller)
+       {
+               // Update the caller
+               $this->update($caller);
+       }
+
+       /**
+        * Updates the observer subject with a new caller.
+        *
+        * @chainable
+        * @param   object  Event_Subject
+        * @return  object
+        */
+       public function update(SplSubject $caller)
+       {
+               if ( ! ($caller instanceof Event_Subject))
+                       throw new Kohana_Exception('event.invalid_subject', get_class($caller), get_class($this));
+
+               // Update the caller
+               $this->caller = $caller;
+
+               return $this;
+       }
+
+       /**
+        * Detaches this observer from the subject.
+        *
+        * @chainable
+        * @return  object
+        */
+       public function remove()
+       {
+               // Detach this observer from the caller
+               $this->caller->detach($this);
+
+               return $this;
+       }
+
+       /**
+        * Notify the observer of a new message. This function must be defined in
+        * all observers and must take exactly one parameter of any type.
+        *
+        * @param   mixed   message string, object, or array
+        * @return  void
+        */
+       abstract public function notify($message);
+
+} // End Event Observer
\ No newline at end of file
diff --git a/Server/system/libraries/Event_Subject.php b/Server/system/libraries/Event_Subject.php
new file mode 100644 (file)
index 0000000..d1ccc54
--- /dev/null
@@ -0,0 +1,67 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Kohana event subject. Uses the SPL observer pattern.
+ *
+ * $Id: Event_Subject.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+abstract class Event_Subject implements SplSubject {
+
+       // Attached subject listeners
+       protected $listeners = array();
+
+       /**
+        * Attach an observer to the object.
+        *
+        * @chainable
+        * @param   object  Event_Observer
+        * @return  object
+        */
+       public function attach(SplObserver $obj)
+       {
+               if ( ! ($obj instanceof Event_Observer))
+                       throw new Kohana_Exception('eventable.invalid_observer', get_class($obj), get_class($this));
+
+               // Add a new listener
+               $this->listeners[spl_object_hash($obj)] = $obj;
+
+               return $this;
+       }
+
+       /**
+        * Detach an observer from the object.
+        *
+        * @chainable
+        * @param   object  Event_Observer
+        * @return  object
+        */
+       public function detach(SplObserver $obj)
+       {
+               // Remove the listener
+               unset($this->listeners[spl_object_hash($obj)]);
+
+               return $this;
+       }
+
+       /**
+        * Notify all attached observers of a new message.
+        *
+        * @chainable
+        * @param   mixed   message string, object, or array
+        * @return  object
+        */
+       public function notify($message)
+       {
+               foreach ($this->listeners as $obj)
+               {
+                       $obj->notify($message);
+               }
+
+               return $this;
+       }
+
+} // End Event Subject
\ No newline at end of file
diff --git a/Server/system/libraries/Image.php b/Server/system/libraries/Image.php
new file mode 100644 (file)
index 0000000..2de0658
--- /dev/null
@@ -0,0 +1,431 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Manipulate images using standard methods such as resize, crop, rotate, etc.
+ * This class must be re-initialized for every image you wish to manipulate.
+ *
+ * $Id: Image.php 4072 2009-03-13 17:20:38Z jheathco $
+ *
+ * @package    Image
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Image_Core {
+
+       // Master Dimension
+       const NONE = 1;
+       const AUTO = 2;
+       const HEIGHT = 3;
+       const WIDTH = 4;
+       // Flip Directions
+       const HORIZONTAL = 5;
+       const VERTICAL = 6;
+
+       // Allowed image types
+       public static $allowed_types = array
+       (
+               IMAGETYPE_GIF => 'gif',
+               IMAGETYPE_JPEG => 'jpg',
+               IMAGETYPE_PNG => 'png',
+               IMAGETYPE_TIFF_II => 'tiff',
+               IMAGETYPE_TIFF_MM => 'tiff',
+       );
+
+       // Driver instance
+       protected $driver;
+
+       // Driver actions
+       protected $actions = array();
+
+       // Reference to the current image filename
+       protected $image = '';
+
+       /**
+        * Creates a new Image instance and returns it.
+        *
+        * @param   string   filename of image
+        * @param   array    non-default configurations
+        * @return  object
+        */
+       public static function factory($image, $config = NULL)
+       {
+               return new Image($image, $config);
+       }
+
+       /**
+        * Creates a new image editor instance.
+        *
+        * @throws  Kohana_Exception
+        * @param   string   filename of image
+        * @param   array    non-default configurations
+        * @return  void
+        */
+       public function __construct($image, $config = NULL)
+       {
+               static $check;
+
+               // Make the check exactly once
+               ($check === NULL) and $check = function_exists('getimagesize');
+
+               if ($check === FALSE)
+                       throw new Kohana_Exception('image.getimagesize_missing');
+
+               // Check to make sure the image exists
+               if ( ! is_file($image))
+                       throw new Kohana_Exception('image.file_not_found', $image);
+
+               // Disable error reporting, to prevent PHP warnings
+               $ER = error_reporting(0);
+
+               // Fetch the image size and mime type
+               $image_info = getimagesize($image);
+
+               // Turn on error reporting again
+               error_reporting($ER);
+
+               // Make sure that the image is readable and valid
+               if ( ! is_array($image_info) OR count($image_info) < 3)
+                       throw new Kohana_Exception('image.file_unreadable', $image);
+
+               // Check to make sure the image type is allowed
+               if ( ! isset(Image::$allowed_types[$image_info[2]]))
+                       throw new Kohana_Exception('image.type_not_allowed', $image);
+
+               // Image has been validated, load it
+               $this->image = array
+               (
+                       'file' => str_replace('\\', '/', realpath($image)),
+                       'width' => $image_info[0],
+                       'height' => $image_info[1],
+                       'type' => $image_info[2],
+                       'ext' => Image::$allowed_types[$image_info[2]],
+                       'mime' => $image_info['mime']
+               );
+
+               // Load configuration
+               $this->config = (array) $config + Kohana::config('image');
+
+               // Set driver class name
+               $driver = 'Image_'.ucfirst($this->config['driver']).'_Driver';
+
+               // Load the driver
+               if ( ! Kohana::auto_load($driver))
+                       throw new Kohana_Exception('core.driver_not_found', $this->config['driver'], get_class($this));
+
+               // Initialize the driver
+               $this->driver = new $driver($this->config['params']);
+
+               // Validate the driver
+               if ( ! ($this->driver instanceof Image_Driver))
+                       throw new Kohana_Exception('core.driver_implements', $this->config['driver'], get_class($this), 'Image_Driver');
+       }
+
+       /**
+        * Handles retrieval of pre-save image properties
+        *
+        * @param   string  property name
+        * @return  mixed
+        */
+       public function __get($property)
+       {
+               if (isset($this->image[$property]))
+               {
+                       return $this->image[$property];
+               }
+               else
+               {
+                       throw new Kohana_Exception('core.invalid_property', $property, get_class($this));
+               }
+       }
+
+       /**
+        * Resize an image to a specific width and height. By default, Kohana will
+        * maintain the aspect ratio using the width as the master dimension. If you
+        * wish to use height as master dim, set $image->master_dim = Image::HEIGHT
+        * This method is chainable.
+        *
+        * @throws  Kohana_Exception
+        * @param   integer  width
+        * @param   integer  height
+        * @param   integer  one of: Image::NONE, Image::AUTO, Image::WIDTH, Image::HEIGHT
+        * @return  object
+        */
+       public function resize($width, $height, $master = NULL)
+       {
+               if ( ! $this->valid_size('width', $width))
+                       throw new Kohana_Exception('image.invalid_width', $width);
+
+               if ( ! $this->valid_size('height', $height))
+                       throw new Kohana_Exception('image.invalid_height', $height);
+
+               if (empty($width) AND empty($height))
+                       throw new Kohana_Exception('image.invalid_dimensions', __FUNCTION__);
+
+               if ($master === NULL)
+               {
+                       // Maintain the aspect ratio by default
+                       $master = Image::AUTO;
+               }
+               elseif ( ! $this->valid_size('master', $master))
+                       throw new Kohana_Exception('image.invalid_master');
+
+               $this->actions['resize'] = array
+               (
+                       'width'  => $width,
+                       'height' => $height,
+                       'master' => $master,
+               );
+
+               return $this;
+       }
+
+       /**
+        * Crop an image to a specific width and height. You may also set the top
+        * and left offset.
+        * This method is chainable.
+        *
+        * @throws  Kohana_Exception
+        * @param   integer  width
+        * @param   integer  height
+        * @param   integer  top offset, pixel value or one of: top, center, bottom
+        * @param   integer  left offset, pixel value or one of: left, center, right
+        * @return  object
+        */
+       public function crop($width, $height, $top = 'center', $left = 'center')
+       {
+               if ( ! $this->valid_size('width', $width))
+                       throw new Kohana_Exception('image.invalid_width', $width);
+
+               if ( ! $this->valid_size('height', $height))
+                       throw new Kohana_Exception('image.invalid_height', $height);
+
+               if ( ! $this->valid_size('top', $top))
+                       throw new Kohana_Exception('image.invalid_top', $top);
+
+               if ( ! $this->valid_size('left', $left))
+                       throw new Kohana_Exception('image.invalid_left', $left);
+
+               if (empty($width) AND empty($height))
+                       throw new Kohana_Exception('image.invalid_dimensions', __FUNCTION__);
+
+               $this->actions['crop'] = array
+               (
+                       'width'  => $width,
+                       'height' => $height,
+                       'top'    => $top,
+                       'left'   => $left,
+               );
+
+               return $this;
+       }
+
+       /**
+        * Allows rotation of an image by 180 degrees clockwise or counter clockwise.
+        *
+        * @param   integer  degrees
+        * @return  object
+        */
+       public function rotate($degrees)
+       {
+               $degrees = (int) $degrees;
+
+               if ($degrees > 180)
+               {
+                       do
+                       {
+                               // Keep subtracting full circles until the degrees have normalized
+                               $degrees -= 360;
+                       }
+                       while($degrees > 180);
+               }
+
+               if ($degrees < -180)
+               {
+                       do
+                       {
+                               // Keep adding full circles until the degrees have normalized
+                               $degrees += 360;
+                       }
+                       while($degrees < -180);
+               }
+
+               $this->actions['rotate'] = $degrees;
+
+               return $this;
+       }
+
+       /**
+        * Flip an image horizontally or vertically.
+        *
+        * @throws  Kohana_Exception
+        * @param   integer  direction
+        * @return  object
+        */
+       public function flip($direction)
+       {
+               if ($direction !== Image::HORIZONTAL AND $direction !== Image::VERTICAL)
+                       throw new Kohana_Exception('image.invalid_flip');
+
+               $this->actions['flip'] = $direction;
+
+               return $this;
+       }
+
+       /**
+        * Change the quality of an image.
+        *
+        * @param   integer  quality as a percentage
+        * @return  object
+        */
+       public function quality($amount)
+       {
+               $this->actions['quality'] = max(1, min($amount, 100));
+
+               return $this;
+       }
+
+       /**
+        * Sharpen an image.
+        *
+        * @param   integer  amount to sharpen, usually ~20 is ideal
+        * @return  object
+        */
+       public function sharpen($amount)
+       {
+               $this->actions['sharpen'] = max(1, min($amount, 100));
+
+               return $this;
+       }
+
+       /**
+        * Save the image to a new image or overwrite this image.
+        *
+        * @throws  Kohana_Exception
+        * @param   string   new image filename
+        * @param   integer  permissions for new image
+        * @param   boolean  keep or discard image process actions
+        * @return  object
+        */
+       public function save($new_image = FALSE, $chmod = 0644, $keep_actions = FALSE)
+       {
+               // If no new image is defined, use the current image
+               empty($new_image) and $new_image = $this->image['file'];
+
+               // Separate the directory and filename
+               $dir  = pathinfo($new_image, PATHINFO_DIRNAME);
+               $file = pathinfo($new_image, PATHINFO_BASENAME);
+
+               // Normalize the path
+               $dir = str_replace('\\', '/', realpath($dir)).'/';
+
+               if ( ! is_writable($dir))
+                       throw new Kohana_Exception('image.directory_unwritable', $dir);
+
+               if ($status = $this->driver->process($this->image, $this->actions, $dir, $file))
+               {
+                       if ($chmod !== FALSE)
+                       {
+                               // Set permissions
+                               chmod($new_image, $chmod);
+                       }
+               }
+
+               // Reset actions. Subsequent save() or render() will not apply previous actions.
+               if ($keep_actions === FALSE)
+                       $this->actions = array();
+
+               return $status;
+       }
+
+       /**
+        * Output the image to the browser.
+        *
+        * @param   boolean  keep or discard image process actions
+        * @return      object
+        */
+       public function render($keep_actions = FALSE)
+       {
+               $new_image = $this->image['file'];
+
+               // Separate the directory and filename
+               $dir  = pathinfo($new_image, PATHINFO_DIRNAME);
+               $file = pathinfo($new_image, PATHINFO_BASENAME);
+
+               // Normalize the path
+               $dir = str_replace('\\', '/', realpath($dir)).'/';
+
+               // Process the image with the driver
+               $status = $this->driver->process($this->image, $this->actions, $dir, $file, $render = TRUE);
+
+               // Reset actions. Subsequent save() or render() will not apply previous actions.
+               if ($keep_actions === FALSE)
+                       $this->actions = array();
+
+               return $status;
+       }
+
+       /**
+        * Sanitize a given value type.
+        *
+        * @param   string   type of property
+        * @param   mixed    property value
+        * @return  boolean
+        */
+       protected function valid_size($type, & $value)
+       {
+               if (is_null($value))
+                       return TRUE;
+
+               if ( ! is_scalar($value))
+                       return FALSE;
+
+               switch ($type)
+               {
+                       case 'width':
+                       case 'height':
+                               if (is_string($value) AND ! ctype_digit($value))
+                               {
+                                       // Only numbers and percent signs
+                                       if ( ! preg_match('/^[0-9]++%$/D', $value))
+                                               return FALSE;
+                               }
+                               else
+                               {
+                                       $value = (int) $value;
+                               }
+                       break;
+                       case 'top':
+                               if (is_string($value) AND ! ctype_digit($value))
+                               {
+                                       if ( ! in_array($value, array('top', 'bottom', 'center')))
+                                               return FALSE;
+                               }
+                               else
+                               {
+                                       $value = (int) $value;
+                               }
+                       break;
+                       case 'left':
+                               if (is_string($value) AND ! ctype_digit($value))
+                               {
+                                       if ( ! in_array($value, array('left', 'right', 'center')))
+                                               return FALSE;
+                               }
+                               else
+                               {
+                                       $value = (int) $value;
+                               }
+                       break;
+                       case 'master':
+                               if ($value !== Image::NONE AND
+                                   $value !== Image::AUTO AND
+                                   $value !== Image::WIDTH AND
+                                   $value !== Image::HEIGHT)
+                                       return FALSE;
+                       break;
+               }
+
+               return TRUE;
+       }
+
+} // End Image
\ No newline at end of file
diff --git a/Server/system/libraries/Input.php b/Server/system/libraries/Input.php
new file mode 100644 (file)
index 0000000..0e23c80
--- /dev/null
@@ -0,0 +1,452 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Input library.
+ *
+ * $Id: Input.php 4346 2009-05-11 17:08:15Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Input_Core {
+
+       // Enable or disable automatic XSS cleaning
+       protected $use_xss_clean = FALSE;
+
+       // Are magic quotes enabled?
+       protected $magic_quotes_gpc = FALSE;
+
+       // IP address of current user
+       public $ip_address;
+
+       // Input singleton
+       protected static $instance;
+
+       /**
+        * Retrieve a singleton instance of Input. This will always be the first
+        * created instance of this class.
+        *
+        * @return  object
+        */
+       public static function instance()
+       {
+               if (Input::$instance === NULL)
+               {
+                       // Create a new instance
+                       return new Input;
+               }
+
+               return Input::$instance;
+       }
+
+       /**
+        * Sanitizes global GET, POST and COOKIE data. Also takes care of
+        * magic_quotes and register_globals, if they have been enabled.
+        *
+        * @return  void
+        */
+       public function __construct()
+       {
+               // Use XSS clean?
+               $this->use_xss_clean = (bool) Kohana::config('core.global_xss_filtering');
+
+               if (Input::$instance === NULL)
+               {
+                       // magic_quotes_runtime is enabled
+                       if (get_magic_quotes_runtime())
+                       {
+                               set_magic_quotes_runtime(0);
+                               Kohana::log('debug', 'Disable magic_quotes_runtime! It is evil and deprecated: http://php.net/magic_quotes');
+                       }
+
+                       // magic_quotes_gpc is enabled
+                       if (get_magic_quotes_gpc())
+                       {
+                               $this->magic_quotes_gpc = TRUE;
+                               Kohana::log('debug', 'Disable magic_quotes_gpc! It is evil and deprecated: http://php.net/magic_quotes');
+                       }
+
+                       // register_globals is enabled
+                       if (ini_get('register_globals'))
+                       {
+                               if (isset($_REQUEST['GLOBALS']))
+                               {
+                                       // Prevent GLOBALS override attacks
+                                       exit('Global variable overload attack.');
+                               }
+
+                               // Destroy the REQUEST global
+                               $_REQUEST = array();
+
+                               // These globals are standard and should not be removed
+                               $preserve = array('GLOBALS', '_REQUEST', '_GET', '_POST', '_FILES', '_COOKIE', '_SERVER', '_ENV', '_SESSION');
+
+                               // This loop has the same effect as disabling register_globals
+                               foreach (array_diff(array_keys($GLOBALS), $preserve) as $key)
+                               {
+                                       global $$key;
+                                       $$key = NULL;
+
+                                       // Unset the global variable
+                                       unset($GLOBALS[$key], $$key);
+                               }
+
+                               // Warn the developer about register globals
+                               Kohana::log('debug', 'Disable register_globals! It is evil and deprecated: http://php.net/register_globals');
+                       }
+
+                       if (is_array($_GET))
+                       {
+                               foreach ($_GET as $key => $val)
+                               {
+                                       // Sanitize $_GET
+                                       $_GET[$this->clean_input_keys($key)] = $this->clean_input_data($val);
+                               }
+                       }
+                       else
+                       {
+                               $_GET = array();
+                       }
+
+                       if (is_array($_POST))
+                       {
+                               foreach ($_POST as $key => $val)
+                               {
+                                       // Sanitize $_POST
+                                       $_POST[$this->clean_input_keys($key)] = $this->clean_input_data($val);
+                               }
+                       }
+                       else
+                       {
+                               $_POST = array();
+                       }
+
+                       if (is_array($_COOKIE))
+                       {
+                               foreach ($_COOKIE as $key => $val)
+                               {
+                                       // Ignore special attributes in RFC2109 compliant cookies
+                                       if ($key == '$Version' OR $key == '$Path' OR $key == '$Domain')
+                                               continue;
+
+                                       // Sanitize $_COOKIE
+                                       $_COOKIE[$this->clean_input_keys($key)] = $this->clean_input_data($val);
+                               }
+                       }
+                       else
+                       {
+                               $_COOKIE = array();
+                       }
+
+                       // Create a singleton
+                       Input::$instance = $this;
+
+                       Kohana::log('debug', 'Global GET, POST and COOKIE data sanitized');
+               }
+       }
+
+       /**
+        * Fetch an item from the $_GET array.
+        *
+        * @param   string   key to find
+        * @param   mixed    default value
+        * @param   boolean  XSS clean the value
+        * @return  mixed
+        */
+       public function get($key = array(), $default = NULL, $xss_clean = FALSE)
+       {
+               return $this->search_array($_GET, $key, $default, $xss_clean);
+       }
+
+       /**
+        * Fetch an item from the $_POST array.
+        *
+        * @param   string   key to find
+        * @param   mixed    default value
+        * @param   boolean  XSS clean the value
+        * @return  mixed
+        */
+       public function post($key = array(), $default = NULL, $xss_clean = FALSE)
+       {
+               return $this->search_array($_POST, $key, $default, $xss_clean);
+       }
+
+       /**
+        * Fetch an item from the $_COOKIE array.
+        *
+        * @param   string   key to find
+        * @param   mixed    default value
+        * @param   boolean  XSS clean the value
+        * @return  mixed
+        */
+       public function cookie($key = array(), $default = NULL, $xss_clean = FALSE)
+       {
+               return $this->search_array($_COOKIE, $key, $default, $xss_clean);
+       }
+
+       /**
+        * Fetch an item from the $_SERVER array.
+        *
+        * @param   string   key to find
+        * @param   mixed    default value
+        * @param   boolean  XSS clean the value
+        * @return  mixed
+        */
+       public function server($key = array(), $default = NULL, $xss_clean = FALSE)
+       {
+               return $this->search_array($_SERVER, $key, $default, $xss_clean);
+       }
+
+       /**
+        * Fetch an item from a global array.
+        *
+        * @param   array    array to search
+        * @param   string   key to find
+        * @param   mixed    default value
+        * @param   boolean  XSS clean the value
+        * @return  mixed
+        */
+       protected function search_array($array, $key, $default = NULL, $xss_clean = FALSE)
+       {
+               if ($key === array())
+                       return $array;
+
+               if ( ! isset($array[$key]))
+                       return $default;
+
+               // Get the value
+               $value = $array[$key];
+
+               if ($this->use_xss_clean === FALSE AND $xss_clean === TRUE)
+               {
+                       // XSS clean the value
+                       $value = $this->xss_clean($value);
+               }
+
+               return $value;
+       }
+
+       /**
+        * Fetch the IP Address.
+        *
+        * @return string
+        */
+       public function ip_address()
+       {
+               if ($this->ip_address !== NULL)
+                       return $this->ip_address;
+
+               // Server keys that could contain the client IP address
+               $keys = array('HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'REMOTE_ADDR');
+
+               foreach ($keys as $key)
+               {
+                       if ($ip = $this->server($key))
+                       {
+                               $this->ip_address = $ip;
+
+                               // An IP address has been found
+                               break;
+                       }
+               }
+
+               if ($comma = strrpos($this->ip_address, ',') !== FALSE)
+               {
+                       $this->ip_address = substr($this->ip_address, $comma + 1);
+               }
+
+               if ( ! valid::ip($this->ip_address))
+               {
+                       // Use an empty IP
+                       $this->ip_address = '0.0.0.0';
+               }
+
+               return $this->ip_address;
+       }
+
+       /**
+        * Clean cross site scripting exploits from string.
+        * HTMLPurifier may be used if installed, otherwise defaults to built in method.
+        * Note - This function should only be used to deal with data upon submission.
+        * It's not something that should be used for general runtime processing
+        * since it requires a fair amount of processing overhead.
+        *
+        * @param   string  data to clean
+        * @param   string  xss_clean method to use ('htmlpurifier' or defaults to built-in method)
+        * @return  string
+        */
+       public function xss_clean($data, $tool = NULL)
+       {
+               if ($tool === NULL)
+               {
+                       // Use the default tool
+                       $tool = Kohana::config('core.global_xss_filtering');
+               }
+
+               if (is_array($data))
+               {
+                       foreach ($data as $key => $val)
+                       {
+                               $data[$key] = $this->xss_clean($val, $tool);
+                       }
+
+                       return $data;
+               }
+
+               // Do not clean empty strings
+               if (trim($data) === '')
+                       return $data;
+
+               if ($tool === TRUE)
+               {
+                       // NOTE: This is necessary because switch is NOT type-sensative!
+                       $tool = 'default';
+               }
+
+               switch ($tool)
+               {
+                       case 'htmlpurifier':
+                               /**
+                                * @todo License should go here, http://htmlpurifier.org/
+                                */
+                               if ( ! class_exists('HTMLPurifier_Config', FALSE))
+                               {
+                                       // Load HTMLPurifier
+                                       require Kohana::find_file('vendor', 'htmlpurifier/HTMLPurifier.auto', TRUE);
+                                       require 'HTMLPurifier.func.php';
+                               }
+
+                               // Set configuration
+                               $config = HTMLPurifier_Config::createDefault();
+                               $config->set('HTML', 'TidyLevel', 'none'); // Only XSS cleaning now
+
+                               // Run HTMLPurifier
+                               $data = HTMLPurifier($data, $config);
+                       break;
+                       default:
+                               // http://svn.bitflux.ch/repos/public/popoon/trunk/classes/externalinput.php
+                               // +----------------------------------------------------------------------+
+                               // | Copyright (c) 2001-2006 Bitflux GmbH                                 |
+                               // +----------------------------------------------------------------------+
+                               // | Licensed under the Apache License, Version 2.0 (the "License");      |
+                               // | you may not use this file except in compliance with the License.     |
+                               // | You may obtain a copy of the License at                              |
+                               // | http://www.apache.org/licenses/LICENSE-2.0                           |
+                               // | Unless required by applicable law or agreed to in writing, software  |
+                               // | distributed under the License is distributed on an "AS IS" BASIS,    |
+                               // | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or      |
+                               // | implied. See the License for the specific language governing         |
+                               // | permissions and limitations under the License.                       |
+                               // +----------------------------------------------------------------------+
+                               // | Author: Christian Stocker <chregu@bitflux.ch>                        |
+                               // +----------------------------------------------------------------------+
+                               //
+                               // Kohana Modifications:
+                               // * Changed double quotes to single quotes, changed indenting and spacing
+                               // * Removed magic_quotes stuff
+                               // * Increased regex readability:
+                               //   * Used delimeters that aren't found in the pattern
+                               //   * Removed all unneeded escapes
+                               //   * Deleted U modifiers and swapped greediness where needed
+                               // * Increased regex speed:
+                               //   * Made capturing parentheses non-capturing where possible
+                               //   * Removed parentheses where possible
+                               //   * Split up alternation alternatives
+                               //   * Made some quantifiers possessive
+
+                               // Fix &entity\n;
+                               $data = str_replace(array('&amp;','&lt;','&gt;'), array('&amp;amp;','&amp;lt;','&amp;gt;'), $data);
+                               $data = preg_replace('/(&#*\w+)[\x00-\x20]+;/u', '$1;', $data);
+                               $data = preg_replace('/(&#x*[0-9A-F]+);*/iu', '$1;', $data);
+                               $data = html_entity_decode($data, ENT_COMPAT, 'UTF-8');
+
+                               // Remove any attribute starting with "on" or xmlns
+                               $data = preg_replace('#(<[^>]+?[\x00-\x20"\'])(?:on|xmlns)[^>]*+>#iu', '$1>', $data);
+
+                               // Remove javascript: and vbscript: protocols
+                               $data = preg_replace('#([a-z]*)[\x00-\x20]*=[\x00-\x20]*([`\'"]*)[\x00-\x20]*j[\x00-\x20]*a[\x00-\x20]*v[\x00-\x20]*a[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2nojavascript...', $data);
+                               $data = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*v[\x00-\x20]*b[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2novbscript...', $data);
+                               $data = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*-moz-binding[\x00-\x20]*:#u', '$1=$2nomozbinding...', $data);
+
+                               // Only works in IE: <span style="width: expression(alert('Ping!'));"></span>
+                               $data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?expression[\x00-\x20]*\([^>]*+>#i', '$1>', $data);
+                               $data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?behaviour[\x00-\x20]*\([^>]*+>#i', '$1>', $data);
+                               $data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:*[^>]*+>#iu', '$1>', $data);
+
+                               // Remove namespaced elements (we do not need them)
+                               $data = preg_replace('#</*\w+:\w[^>]*+>#i', '', $data);
+
+                               do
+                               {
+                                       // Remove really unwanted tags
+                                       $old_data = $data;
+                                       $data = preg_replace('#</*(?:applet|b(?:ase|gsound|link)|embed|frame(?:set)?|i(?:frame|layer)|l(?:ayer|ink)|meta|object|s(?:cript|tyle)|title|xml)[^>]*+>#i', '', $data);
+                               }
+                               while ($old_data !== $data);
+                       break;
+               }
+
+               return $data;
+       }
+
+       /**
+        * This is a helper method. It enforces W3C specifications for allowed
+        * key name strings, to prevent malicious exploitation.
+        *
+        * @param   string  string to clean
+        * @return  string
+        */
+       public function clean_input_keys($str)
+       {
+               $chars = PCRE_UNICODE_PROPERTIES ? '\pL' : 'a-zA-Z';
+
+               if ( ! preg_match('#^['.$chars.'0-9:_.-]++$#uD', $str))
+               {
+                       exit('Disallowed key characters in global data.');
+               }
+
+               return $str;
+       }
+
+       /**
+        * This is a helper method. It escapes data and forces all newline
+        * characters to "\n".
+        *
+        * @param   unknown_type  string to clean
+        * @return  string
+        */
+       public function clean_input_data($str)
+       {
+               if (is_array($str))
+               {
+                       $new_array = array();
+                       foreach ($str as $key => $val)
+                       {
+                               // Recursion!
+                               $new_array[$this->clean_input_keys($key)] = $this->clean_input_data($val);
+                       }
+                       return $new_array;
+               }
+
+               if ($this->magic_quotes_gpc === TRUE)
+               {
+                       // Remove annoying magic quotes
+                       $str = stripslashes($str);
+               }
+
+               if ($this->use_xss_clean === TRUE)
+               {
+                       $str = $this->xss_clean($str);
+               }
+
+               if (strpos($str, "\r") !== FALSE)
+               {
+                       // Standardize newlines
+                       $str = str_replace(array("\r\n", "\r"), "\n", $str);
+               }
+
+               return $str;
+       }
+
+} // End Input Class
diff --git a/Server/system/libraries/Model.php b/Server/system/libraries/Model.php
new file mode 100644 (file)
index 0000000..0c9fd8d
--- /dev/null
@@ -0,0 +1,31 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Model base class.
+ *
+ * $Id: Model.php 4007 2009-02-20 01:54:00Z jheathco $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2009 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Model_Core {
+
+       // Database object
+       protected $db = 'default';
+
+       /**
+        * Loads the database instance, if the database is not already loaded.
+        *
+        * @return  void
+        */
+       public function __construct()
+       {
+               if ( ! is_object($this->db))
+               {
+                       // Load the default database
+                       $this->db = Database::instance($this->db);
+               }
+       }
+
+} // End Model
\ No newline at end of file
diff --git a/Server/system/libraries/ORM.php b/Server/system/libraries/ORM.php
new file mode 100644 (file)
index 0000000..c104860
--- /dev/null
@@ -0,0 +1,1431 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * [Object Relational Mapping][ref-orm] (ORM) is a method of abstracting database
+ * access to standard PHP calls. All table rows are represented as model objects,
+ * with object properties representing row data. ORM in Kohana generally follows
+ * the [Active Record][ref-act] pattern.
+ *
+ * [ref-orm]: http://wikipedia.org/wiki/Object-relational_mapping
+ * [ref-act]: http://wikipedia.org/wiki/Active_record
+ *
+ * $Id: ORM.php 4354 2009-05-15 16:51:37Z kiall $
+ *
+ * @package    ORM
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class ORM_Core {
+
+       // Current relationships
+       protected $has_one                 = array();
+       protected $belongs_to              = array();
+       protected $has_many                = array();
+       protected $has_and_belongs_to_many = array();
+
+       // Relationships that should always be joined
+       protected $load_with = array();
+
+       // Current object
+       protected $object  = array();
+       protected $changed = array();
+       protected $related = array();
+       protected $loaded  = FALSE;
+       protected $saved   = FALSE;
+       protected $sorting;
+
+       // Related objects
+       protected $object_relations = array();
+       protected $changed_relations = array();
+
+       // Model table information
+       protected $object_name;
+       protected $object_plural;
+       protected $table_name;
+       protected $table_columns;
+       protected $ignored_columns;
+
+       // Table primary key and value
+       protected $primary_key = 'id';
+       protected $primary_val = 'name';
+
+       // Array of foreign key name overloads
+       protected $foreign_key = array();
+
+       // Model configuration
+       protected $table_names_plural = TRUE;
+       protected $reload_on_wakeup   = TRUE;
+
+       // Database configuration
+       protected $db = 'default';
+       protected $db_applied = array();
+
+       // With calls already applied
+       protected $with_applied = array();
+
+       // Stores column information for ORM models
+       protected static $column_cache = array();
+
+       /**
+        * Creates and returns a new model.
+        *
+        * @chainable
+        * @param   string  model name
+        * @param   mixed   parameter for find()
+        * @return  ORM
+        */
+       public static function factory($model, $id = NULL)
+       {
+               // Set class name
+               $model = ucfirst($model).'_Model';
+
+               return new $model($id);
+       }
+
+       /**
+        * Prepares the model database connection and loads the object.
+        *
+        * @param   mixed  parameter for find or object to load
+        * @return  void
+        */
+       public function __construct($id = NULL)
+       {
+               // Set the object name and plural name
+               $this->object_name   = strtolower(substr(get_class($this), 0, -6));
+               $this->object_plural = inflector::plural($this->object_name);
+
+               if (!isset($this->sorting))
+               {
+                       // Default sorting
+                       $this->sorting = array($this->primary_key => 'asc');
+               }
+
+               // Initialize database
+               $this->__initialize();
+
+               // Clear the object
+               $this->clear();
+
+               if (is_object($id))
+               {
+                       // Load an object
+                       $this->load_values((array) $id);
+               }
+               elseif (!empty($id))
+               {
+                       // Find an object
+                       $this->find($id);
+               }
+       }
+
+       /**
+        * Prepares the model database connection, determines the table name,
+        * and loads column information.
+        *
+        * @return  void
+        */
+       public function __initialize()
+       {
+               if ( ! is_object($this->db))
+               {
+                       // Get database instance
+                       $this->db = Database::instance($this->db);
+               }
+
+               if (empty($this->table_name))
+               {
+                       // Table name is the same as the object name
+                       $this->table_name = $this->object_name;
+
+                       if ($this->table_names_plural === TRUE)
+                       {
+                               // Make the table name plural
+                               $this->table_name = inflector::plural($this->table_name);
+                       }
+               }
+
+               if (is_array($this->ignored_columns))
+               {
+                       // Make the ignored columns mirrored = mirrored
+                       $this->ignored_columns = array_combine($this->ignored_columns, $this->ignored_columns);
+               }
+
+               // Load column information
+               $this->reload_columns();
+       }
+
+       /**
+        * Allows serialization of only the object data and state, to prevent
+        * "stale" objects being unserialized, which also requires less memory.
+        *
+        * @return  array
+        */
+       public function __sleep()
+       {
+               // Store only information about the object
+               return array('object_name', 'object', 'changed', 'loaded', 'saved', 'sorting');
+       }
+
+       /**
+        * Prepares the database connection and reloads the object.
+        *
+        * @return  void
+        */
+       public function __wakeup()
+       {
+               // Initialize database
+               $this->__initialize();
+
+               if ($this->reload_on_wakeup === TRUE)
+               {
+                       // Reload the object
+                       $this->reload();
+               }
+       }
+
+       /**
+        * Handles pass-through to database methods. Calls to query methods
+        * (query, get, insert, update) are not allowed. Query builder methods
+        * are chainable.
+        *
+        * @param   string  method name
+        * @param   array   method arguments
+        * @return  mixed
+        */
+       public function __call($method, array $args)
+       {
+               if (method_exists($this->db, $method))
+               {
+                       if (in_array($method, array('query', 'get', 'insert', 'update', 'delete')))
+                               throw new Kohana_Exception('orm.query_methods_not_allowed');
+
+                       // Method has been applied to the database
+                       $this->db_applied[$method] = $method;
+
+                       // Number of arguments passed
+                       $num_args = count($args);
+
+                       if ($method === 'select' AND $num_args > 3)
+                       {
+                               // Call select() manually to avoid call_user_func_array
+                               $this->db->select($args);
+                       }
+                       else
+                       {
+                               // We use switch here to manually call the database methods. This is
+                               // done for speed: call_user_func_array can take over 300% longer to
+                               // make calls. Most database methods are 4 arguments or less, so this
+                               // avoids almost any calls to call_user_func_array.
+
+                               switch ($num_args)
+                               {
+                                       case 0:
+                                               if (in_array($method, array('open_paren', 'close_paren', 'enable_cache', 'disable_cache')))
+                                               {
+                                                       // Should return ORM, not Database
+                                                       $this->db->$method();
+                                               }
+                                               else
+                                               {
+                                                       // Support for things like reset_select, reset_write, list_tables
+                                                       return $this->db->$method();
+                                               }
+                                       break;
+                                       case 1:
+                                               $this->db->$method($args[0]);
+                                       break;
+                                       case 2:
+                                               $this->db->$method($args[0], $args[1]);
+                                       break;
+                                       case 3:
+                                               $this->db->$method($args[0], $args[1], $args[2]);
+                                       break;
+                                       case 4:
+                                               $this->db->$method($args[0], $args[1], $args[2], $args[3]);
+                                       break;
+                                       default:
+                                               // Here comes the snail...
+                                               call_user_func_array(array($this->db, $method), $args);
+                                       break;
+                               }
+                       }
+
+                       return $this;
+               }
+               else
+               {
+                       throw new Kohana_Exception('core.invalid_method', $method, get_class($this));
+               }
+       }
+
+       /**
+        * Handles retrieval of all model values, relationships, and metadata.
+        *
+        * @param   string  column name
+        * @return  mixed
+        */
+       public function __get($column)
+       {
+               if (array_key_exists($column, $this->object))
+               {
+                       return $this->object[$column];
+               }
+               elseif (isset($this->related[$column]))
+               {
+                       return $this->related[$column];
+               }
+               elseif ($column === 'primary_key_value')
+               {
+                       return $this->object[$this->primary_key];
+               }
+               elseif ($model = $this->related_object($column))
+               {
+                       // This handles the has_one and belongs_to relationships
+
+                       if (in_array($model->object_name, $this->belongs_to) OR ! array_key_exists($this->foreign_key($column), $model->object))
+                       {
+                               // Foreign key lies in this table (this model belongs_to target model) OR an invalid has_one relationship
+                               $where = array($model->table_name.'.'.$model->primary_key => $this->object[$this->foreign_key($column)]);
+                       }
+                       else
+                       {
+                               // Foreign key lies in the target table (this model has_one target model)
+                               $where = array($this->foreign_key($column, $model->table_name) => $this->primary_key_value);
+                       }
+
+                       // one<>alias:one relationship
+                       return $this->related[$column] = $model->find($where);
+               }
+               elseif (isset($this->has_many[$column]))
+               {
+                       // Load the "middle" model
+                       $through = ORM::factory(inflector::singular($this->has_many[$column]));
+
+                       // Load the "end" model
+                       $model = ORM::factory(inflector::singular($column));
+
+                       // Join ON target model's primary key set to 'through' model's foreign key
+                       // User-defined foreign keys must be defined in the 'through' model
+                       $join_table = $through->table_name;
+                       $join_col1  = $through->foreign_key($model->object_name, $join_table);
+                       $join_col2  = $model->table_name.'.'.$model->primary_key;
+
+                       // one<>alias:many relationship
+                       return $this->related[$column] = $model
+                               ->join($join_table, $join_col1, $join_col2)
+                               ->where($through->foreign_key($this->object_name, $join_table), $this->object[$this->primary_key])
+                               ->find_all();
+               }
+               elseif (in_array($column, $this->has_many))
+               {
+                       // one<>many relationship
+                       $model = ORM::factory(inflector::singular($column));
+                       return $this->related[$column] = $model
+                               ->where($this->foreign_key($column, $model->table_name), $this->object[$this->primary_key])
+                               ->find_all();
+               }
+               elseif (in_array($column, $this->has_and_belongs_to_many))
+               {
+                       // Load the remote model, always singular
+                       $model = ORM::factory(inflector::singular($column));
+
+                       if ($this->has($model, TRUE))
+                       {
+                               // many<>many relationship
+                               return $this->related[$column] = $model
+                                       ->in($model->table_name.'.'.$model->primary_key, $this->changed_relations[$column])
+                                       ->find_all();
+                       }
+                       else
+                       {
+                               // empty many<>many relationship
+                               return $this->related[$column] = $model
+                                       ->where($model->table_name.'.'.$model->primary_key, NULL)
+                                       ->find_all();
+                       }
+               }
+               elseif (isset($this->ignored_columns[$column]))
+               {
+                       return NULL;
+               }
+               elseif (in_array($column, array
+                       (
+                               'object_name', 'object_plural', // Object
+                               'primary_key', 'primary_val', 'table_name', 'table_columns', // Table
+                               'loaded', 'saved', // Status
+                               'has_one', 'belongs_to', 'has_many', 'has_and_belongs_to_many', 'load_with' // Relationships
+                       )))
+               {
+                       // Model meta information
+                       return $this->$column;
+               }
+               else
+               {
+                       throw new Kohana_Exception('core.invalid_property', $column, get_class($this));
+               }
+       }
+
+       /**
+        * Handles setting of all model values, and tracks changes between values.
+        *
+        * @param   string  column name
+        * @param   mixed   column value
+        * @return  void
+        */
+       public function __set($column, $value)
+       {
+               if (isset($this->ignored_columns[$column]))
+               {
+                       return NULL;
+               }
+               elseif (isset($this->object[$column]) OR array_key_exists($column, $this->object))
+               {
+                       if (isset($this->table_columns[$column]))
+                       {
+                               // Data has changed
+                               $this->changed[$column] = $column;
+
+                               // Object is no longer saved
+                               $this->saved = FALSE;
+                       }
+
+                       $this->object[$column] = $this->load_type($column, $value);
+               }
+               elseif (in_array($column, $this->has_and_belongs_to_many) AND is_array($value))
+               {
+                       // Load relations
+                       $model = ORM::factory(inflector::singular($column));
+
+                       if ( ! isset($this->object_relations[$column]))
+                       {
+                               // Load relations
+                               $this->has($model);
+                       }
+
+                       // Change the relationships
+                       $this->changed_relations[$column] = $value;
+
+                       if (isset($this->related[$column]))
+                       {
+                               // Force a reload of the relationships
+                               unset($this->related[$column]);
+                       }
+               }
+               else
+               {
+                       throw new Kohana_Exception('core.invalid_property', $column, get_class($this));
+               }
+       }
+
+       /**
+        * Checks if object data is set.
+        *
+        * @param   string  column name
+        * @return  boolean
+        */
+       public function __isset($column)
+       {
+               return (isset($this->object[$column]) OR isset($this->related[$column]));
+       }
+
+       /**
+        * Unsets object data.
+        *
+        * @param   string  column name
+        * @return  void
+        */
+       public function __unset($column)
+       {
+               unset($this->object[$column], $this->changed[$column], $this->related[$column]);
+       }
+
+       /**
+        * Displays the primary key of a model when it is converted to a string.
+        *
+        * @return  string
+        */
+       public function __toString()
+       {
+               return (string) $this->object[$this->primary_key];
+       }
+
+       /**
+        * Returns the values of this object as an array.
+        *
+        * @return  array
+        */
+       public function as_array()
+       {
+               $object = array();
+
+               foreach ($this->object as $key => $val)
+               {
+                       // Reconstruct the array (calls __get)
+                       $object[$key] = $this->$key;
+               }
+
+               return $object;
+       }
+
+       /**
+        * Binds another one-to-one object to this model.  One-to-one objects
+        * can be nested using 'object1:object2' syntax
+        *
+        * @param string $target_path
+        * @return void
+        */
+       public function with($target_path)
+       {
+               if (isset($this->with_applied[$target_path]))
+               {
+                       // Don't join anything already joined
+                       return $this;
+               }
+
+               // Split object parts
+               $objects = explode(':', $target_path);
+               $target  = $this;
+               foreach ($objects as $object)
+               {
+                       // Go down the line of objects to find the given target
+                       $parent = $target;
+                       $target = $parent->related_object($object);
+
+                       if ( ! $target)
+                       {
+                               // Can't find related object
+                               return $this;
+                       }
+               }
+
+               $target_name = $object;
+
+               // Pop-off top object to get the parent object (user:photo:tag becomes user:photo - the parent table prefix)
+               array_pop($objects);
+               $parent_path = implode(':', $objects);
+
+               if (empty($parent_path))
+               {
+                       // Use this table name itself for the parent object
+                       $parent_path = $this->table_name;
+               }
+               else
+               {
+                       if( ! isset($this->with_applied[$parent_path]))
+                       {
+                               // If the parent object hasn't been joined yet, do it first (otherwise LEFT JOINs fail)
+                               $this->with($parent_path);
+                       }
+               }
+
+               // Add to with_applied to prevent duplicate joins
+               $this->with_applied[$target_path] = TRUE;
+
+               // Use the keys of the empty object to determine the columns
+               $select = array_keys($target->object);
+               foreach ($select as $i => $column)
+               {
+                       // Add the prefix so that load_result can determine the relationship
+                       $select[$i] = $target_path.'.'.$column.' AS '.$target_path.':'.$column;
+               }
+
+
+               // Select all of the prefixed keys in the object
+               $this->db->select($select);
+
+               if (in_array($target->object_name, $parent->belongs_to) OR ! isset($target->object[$parent->foreign_key($target_name)]))
+               {
+                       // Parent belongs_to target, use target's primary key as join column
+                       $join_col1 = $target->foreign_key(TRUE, $target_path);
+                       $join_col2 = $parent->foreign_key($target_name, $parent_path);
+               }
+               else
+               {
+                       // Parent has_one target, use parent's primary key as join column
+                       $join_col2 = $parent->foreign_key(TRUE, $parent_path);
+                       $join_col1 = $parent->foreign_key($target_name, $target_path);
+               }
+
+               // This allows for models to use different table prefixes (sharing the same database)
+               $join_table = new Database_Expression($target->db->table_prefix().$target->table_name.' AS '.$this->db->table_prefix().$target_path);
+
+               // Join the related object into the result
+               $this->db->join($join_table, $join_col1, $join_col2, 'LEFT');
+
+               return $this;
+       }
+
+       /**
+        * Finds and loads a single database row into the object.
+        *
+        * @chainable
+        * @param   mixed  primary key or an array of clauses
+        * @return  ORM
+        */
+       public function find($id = NULL)
+       {
+               if ($id !== NULL)
+               {
+                       if (is_array($id))
+                       {
+                               // Search for all clauses
+                               $this->db->where($id);
+                       }
+                       else
+                       {
+                               // Search for a specific column
+                               $this->db->where($this->table_name.'.'.$this->unique_key($id), $id);
+                       }
+               }
+
+               return $this->load_result();
+       }
+
+       /**
+        * Finds multiple database rows and returns an iterator of the rows found.
+        *
+        * @chainable
+        * @param   integer  SQL limit
+        * @param   integer  SQL offset
+        * @return  ORM_Iterator
+        */
+       public function find_all($limit = NULL, $offset = NULL)
+       {
+               if ($limit !== NULL AND ! isset($this->db_applied['limit']))
+               {
+                       // Set limit
+                       $this->limit($limit);
+               }
+
+               if ($offset !== NULL AND ! isset($this->db_applied['offset']))
+               {
+                       // Set offset
+                       $this->offset($offset);
+               }
+
+               return $this->load_result(TRUE);
+       }
+
+       /**
+        * Creates a key/value array from all of the objects available. Uses find_all
+        * to find the objects.
+        *
+        * @param   string  key column
+        * @param   string  value column
+        * @return  array
+        */
+       public function select_list($key = NULL, $val = NULL)
+       {
+               if ($key === NULL)
+               {
+                       $key = $this->primary_key;
+               }
+
+               if ($val === NULL)
+               {
+                       $val = $this->primary_val;
+               }
+
+               // Return a select list from the results
+               return $this->select($key, $val)->find_all()->select_list($key, $val);
+       }
+
+       /**
+        * Validates the current object. This method should generally be called
+        * via the model, after the $_POST Validation object has been created.
+        *
+        * @param   object   Validation array
+        * @return  boolean
+        */
+       public function validate(Validation $array, $save = FALSE)
+       {
+               $safe_array = $array->safe_array();
+
+               if ( ! $array->submitted())
+               {
+                       foreach ($safe_array as $key => $value)
+                       {
+                               // Get the value from this object
+                               $value = $this->$key;
+
+                               if (is_object($value) AND $value instanceof ORM_Iterator)
+                               {
+                                       // Convert the value to an array of primary keys
+                                       $value = $value->primary_key_array();
+                               }
+
+                               // Pre-fill data
+                               $array[$key] = $value;
+                       }
+               }
+
+               // Validate the array
+               if ($status = $array->validate())
+               {
+                       // Grab only set fields (excludes missing data, unlike safe_array)
+                       $fields = $array->as_array();
+
+                       foreach ($fields as $key => $value)
+                       {
+                               if (isset($safe_array[$key]))
+                               {
+                                       // Set new data, ignoring any missing fields or fields without rules
+                                       $this->$key = $value;
+                               }
+                       }
+
+                       if ($save === TRUE OR is_string($save))
+                       {
+                               // Save this object
+                               $this->save();
+
+                               if (is_string($save))
+                               {
+                                       // Redirect to the saved page
+                                       url::redirect($save);
+                               }
+                       }
+               }
+
+               // Return validation status
+               return $status;
+       }
+
+       /**
+        * Saves the current object.
+        *
+        * @chainable
+        * @return  ORM
+        */
+       public function save()
+       {
+               if ( ! empty($this->changed))
+               {
+                       $data = array();
+                       foreach ($this->changed as $column)
+                       {
+                               // Compile changed data
+                               $data[$column] = $this->object[$column];
+                       }
+
+                       if ($this->loaded === TRUE)
+                       {
+                               $query = $this->db
+                                       ->where($this->primary_key, $this->object[$this->primary_key])
+                                       ->update($this->table_name, $data);
+
+                               // Object has been saved
+                               $this->saved = TRUE;
+                       }
+                       else
+                       {
+                               $query = $this->db
+                                       ->insert($this->table_name, $data);
+
+                               if ($query->count() > 0)
+                               {
+                                       if (empty($this->object[$this->primary_key]))
+                                       {
+                                               // Load the insert id as the primary key
+                                               $this->object[$this->primary_key] = $query->insert_id();
+                                       }
+
+                                       // Object is now loaded and saved
+                                       $this->loaded = $this->saved = TRUE;
+                               }
+                       }
+
+                       if ($this->saved === TRUE)
+                       {
+                               // All changes have been saved
+                               $this->changed = array();
+                       }
+               }
+
+               if ($this->saved === TRUE AND ! empty($this->changed_relations))
+               {
+                       foreach ($this->changed_relations as $column => $values)
+                       {
+                               // All values that were added
+                               $added = array_diff($values, $this->object_relations[$column]);
+
+                               // All values that were saved
+                               $removed = array_diff($this->object_relations[$column], $values);
+
+                               if (empty($added) AND empty($removed))
+                               {
+                                       // No need to bother
+                                       continue;
+                               }
+
+                               // Clear related columns
+                               unset($this->related[$column]);
+
+                               // Load the model
+                               $model = ORM::factory(inflector::singular($column));
+
+                               if (($join_table = array_search($column, $this->has_and_belongs_to_many)) === FALSE)
+                                       continue;
+
+                               if (is_int($join_table))
+                               {
+                                       // No "through" table, load the default JOIN table
+                                       $join_table = $model->join_table($this->table_name);
+                               }
+
+                               // Foreign keys for the join table
+                               $object_fk  = $this->foreign_key(NULL);
+                               $related_fk = $model->foreign_key(NULL);
+
+                               if ( ! empty($added))
+                               {
+                                       foreach ($added as $id)
+                                       {
+                                               // Insert the new relationship
+                                               $this->db->insert($join_table, array
+                                               (
+                                                       $object_fk  => $this->object[$this->primary_key],
+                                                       $related_fk => $id,
+                                               ));
+                                       }
+                               }
+
+                               if ( ! empty($removed))
+                               {
+                                       $this->db
+                                               ->where($object_fk, $this->object[$this->primary_key])
+                                               ->in($related_fk, $removed)
+                                               ->delete($join_table);
+                               }
+
+                               // Clear all relations for this column
+                               unset($this->object_relations[$column], $this->changed_relations[$column]);
+                       }
+               }
+
+               return $this;
+       }
+
+       /**
+        * Deletes the current object from the database. This does NOT destroy
+        * relationships that have been created with other objects.
+        *
+        * @chainable
+        * @return  ORM
+        */
+       public function delete($id = NULL)
+       {
+               if ($id === NULL AND $this->loaded)
+               {
+                       // Use the the primary key value
+                       $id = $this->object[$this->primary_key];
+               }
+
+               // Delete this object
+               $this->db->where($this->primary_key, $id)->delete($this->table_name);
+
+               return $this->clear();
+       }
+
+       /**
+        * Delete all objects in the associated table. This does NOT destroy
+        * relationships that have been created with other objects.
+        *
+        * @chainable
+        * @param   array  ids to delete
+        * @return  ORM
+        */
+       public function delete_all($ids = NULL)
+       {
+               if (is_array($ids))
+               {
+                       // Delete only given ids
+                       $this->db->in($this->primary_key, $ids);
+               }
+               elseif (is_null($ids))
+               {
+                       // Delete all records
+                       $this->db->where('1=1');
+               }
+               else
+               {
+                       // Do nothing - safeguard
+                       return $this;
+               }
+
+               // Delete all objects
+               $this->db->delete($this->table_name);
+
+               return $this->clear();
+       }
+
+       /**
+        * Unloads the current object and clears the status.
+        *
+        * @chainable
+        * @return  ORM
+        */
+       public function clear()
+       {
+               // Create an array with all the columns set to NULL
+               $columns = array_keys($this->table_columns);
+               $values  = array_combine($columns, array_fill(0, count($columns), NULL));
+
+               // Replace the current object with an empty one
+               $this->load_values($values);
+
+               return $this;
+       }
+
+       /**
+        * Reloads the current object from the database.
+        *
+        * @chainable
+        * @return  ORM
+        */
+       public function reload()
+       {
+               return $this->find($this->object[$this->primary_key]);
+       }
+
+       /**
+        * Reload column definitions.
+        *
+        * @chainable
+        * @param   boolean  force reloading
+        * @return  ORM
+        */
+       public function reload_columns($force = FALSE)
+       {
+               if ($force === TRUE OR empty($this->table_columns))
+               {
+                       if (isset(ORM::$column_cache[$this->object_name]))
+                       {
+                               // Use cached column information
+                               $this->table_columns = ORM::$column_cache[$this->object_name];
+                       }
+                       else
+                       {
+                               // Load table columns
+                               ORM::$column_cache[$this->object_name] = $this->table_columns = $this->list_fields();
+                       }
+               }
+
+               return $this;
+       }
+
+       /**
+        * Tests if this object has a relationship to a different model.
+        *
+        * @param   object   related ORM model
+        * @param   boolean  check for any relations to given model
+        * @return  boolean
+        */
+       public function has(ORM $model, $any = FALSE)
+       {
+               // Determine plural or singular relation name
+               $related = ($model->table_names_plural === TRUE) ? $model->object_plural : $model->object_name;
+
+               if (($join_table = array_search($related, $this->has_and_belongs_to_many)) === FALSE)
+                       return FALSE;
+
+               if (is_int($join_table))
+               {
+                       // No "through" table, load the default JOIN table
+                       $join_table = $model->join_table($this->table_name);
+               }
+
+               if ( ! isset($this->object_relations[$related]))
+               {
+                       // Load the object relationships
+                       $this->changed_relations[$related] = $this->object_relations[$related] = $this->load_relations($join_table, $model);
+               }
+
+               if ( ! $model->empty_primary_key())
+               {
+                       // Check if a specific object exists
+                       return in_array($model->primary_key_value, $this->changed_relations[$related]);
+               }
+               elseif ($any)
+               {
+                       // Check if any relations to given model exist
+                       return ! empty($this->changed_relations[$related]);
+               }
+               else
+               {
+                       return FALSE;
+               }
+       }
+
+       /**
+        * Adds a new relationship to between this model and another.
+        *
+        * @param   object   related ORM model
+        * @return  boolean
+        */
+       public function add(ORM $model)
+       {
+               if ($this->has($model))
+                       return TRUE;
+
+               // Get the faked column name
+               $column = $model->object_plural;
+
+               // Add the new relation to the update
+               $this->changed_relations[$column][] = $model->primary_key_value;
+
+               if (isset($this->related[$column]))
+               {
+                       // Force a reload of the relationships
+                       unset($this->related[$column]);
+               }
+
+               return TRUE;
+       }
+
+       /**
+        * Adds a new relationship to between this model and another.
+        *
+        * @param   object   related ORM model
+        * @return  boolean
+        */
+       public function remove(ORM $model)
+       {
+               if ( ! $this->has($model))
+                       return FALSE;
+
+               // Get the faked column name
+               $column = $model->object_plural;
+
+               if (($key = array_search($model->primary_key_value, $this->changed_relations[$column])) === FALSE)
+                       return FALSE;
+
+               // Remove the relationship
+               unset($this->changed_relations[$column][$key]);
+
+               if (isset($this->related[$column]))
+               {
+                       // Force a reload of the relationships
+                       unset($this->related[$column]);
+               }
+
+               return TRUE;
+       }
+
+       /**
+        * Count the number of records in the table.
+        *
+        * @return  integer
+        */
+       public function count_all()
+       {
+               // Return the total number of records in a table
+               return $this->db->count_records($this->table_name);
+       }
+
+       /**
+        * Proxy method to Database list_fields.
+        *
+        * @param   string  table name or NULL to use this table
+        * @return  array
+        */
+       public function list_fields($table = NULL)
+       {
+               if ($table === NULL)
+               {
+                       $table = $this->table_name;
+               }
+
+               // Proxy to database
+               return $this->db->list_fields($table);
+       }
+
+       /**
+        * Proxy method to Database field_data.
+        *
+        * @param   string  table name
+        * @return  array
+        */
+       public function field_data($table)
+       {
+               // Proxy to database
+               return $this->db->field_data($table);
+       }
+
+       /**
+        * Proxy method to Database field_data.
+        *
+        * @chainable
+        * @param   string  SQL query to clear
+        * @return  ORM
+        */
+       public function clear_cache($sql = NULL)
+       {
+               // Proxy to database
+               $this->db->clear_cache($sql);
+
+               ORM::$column_cache = array();
+
+               return $this;
+       }
+
+       /**
+        * Returns the unique key for a specific value. This method is expected
+        * to be overloaded in models if the model has other unique columns.
+        *
+        * @param   mixed   unique value
+        * @return  string
+        */
+       public function unique_key($id)
+       {
+               return $this->primary_key;
+       }
+
+       /**
+        * Determines the name of a foreign key for a specific table.
+        *
+        * @param   string  related table name
+        * @param   string  prefix table name (used for JOINs)
+        * @return  string
+        */
+       public function foreign_key($table = NULL, $prefix_table = NULL)
+       {
+               if ($table === TRUE)
+               {
+                       if (is_string($prefix_table))
+                       {
+                               // Use prefix table name and this table's PK
+                               return $prefix_table.'.'.$this->primary_key;
+                       }
+                       else
+                       {
+                               // Return the name of this table's PK
+                               return $this->table_name.'.'.$this->primary_key;
+                       }
+               }
+
+               if (is_string($prefix_table))
+               {
+                       // Add a period for prefix_table.column support
+                       $prefix_table .= '.';
+               }
+
+               if (isset($this->foreign_key[$table]))
+               {
+                       // Use the defined foreign key name, no magic here!
+                       $foreign_key = $this->foreign_key[$table];
+               }
+               else
+               {
+                       if ( ! is_string($table) OR ! array_key_exists($table.'_'.$this->primary_key, $this->object))
+                       {
+                               // Use this table
+                               $table = $this->table_name;
+
+                               if (strpos($table, '.') !== FALSE)
+                               {
+                                       // Hack around support for PostgreSQL schemas
+                                       list ($schema, $table) = explode('.', $table, 2);
+                               }
+
+                               if ($this->table_names_plural === TRUE)
+                               {
+                                       // Make the key name singular
+                                       $table = inflector::singular($table);
+                               }
+                       }
+
+                       $foreign_key = $table.'_'.$this->primary_key;
+               }
+
+               return $prefix_table.$foreign_key;
+       }
+
+       /**
+        * This uses alphabetical comparison to choose the name of the table.
+        *
+        * Example: The joining table of users and roles would be roles_users,
+        * because "r" comes before "u". Joining products and categories would
+        * result in categories_products, because "c" comes before "p".
+        *
+        * Example: zoo > zebra > robber > ocean > angel > aardvark
+        *
+        * @param   string  table name
+        * @return  string
+        */
+       public function join_table($table)
+       {
+               if ($this->table_name > $table)
+               {
+                       $table = $table.'_'.$this->table_name;
+               }
+               else
+               {
+                       $table = $this->table_name.'_'.$table;
+               }
+
+               return $table;
+       }
+
+       /**
+        * Returns an ORM model for the given object name;
+        *
+        * @param   string  object name
+        * @return  ORM
+        */
+       protected function related_object($object)
+       {
+               if (isset($this->has_one[$object]))
+               {
+                       $object = ORM::factory($this->has_one[$object]);
+               }
+               elseif (isset($this->belongs_to[$object]))
+               {
+                       $object = ORM::factory($this->belongs_to[$object]);
+               }
+               elseif (in_array($object, $this->has_one) OR in_array($object, $this->belongs_to))
+               {
+                       $object = ORM::factory($object);
+               }
+               else
+               {
+                       return FALSE;
+               }
+
+               return $object;
+       }
+
+       /**
+        * Loads an array of values into into the current object.
+        *
+        * @chainable
+        * @param   array  values to load
+        * @return  ORM
+        */
+       public function load_values(array $values)
+       {
+               if (array_key_exists($this->primary_key, $values))
+               {
+                       // Replace the object and reset the object status
+                       $this->object = $this->changed = $this->related = array();
+
+                       // Set the loaded and saved object status based on the primary key
+                       $this->loaded = $this->saved = ($values[$this->primary_key] !== NULL);
+               }
+
+               // Related objects
+               $related = array();
+
+               foreach ($values as $column => $value)
+               {
+                       if (strpos($column, ':') === FALSE)
+                       {
+                               if (isset($this->table_columns[$column]))
+                               {
+                                       // The type of the value can be determined, convert the value
+                                       $value = $this->load_type($column, $value);
+                               }
+
+                               $this->object[$column] = $value;
+                       }
+                       else
+                       {
+                               list ($prefix, $column) = explode(':', $column, 2);
+
+                               $related[$prefix][$column] = $value;
+                       }
+               }
+
+               if ( ! empty($related))
+               {
+                       foreach ($related as $object => $values)
+                       {
+                               // Load the related objects with the values in the result
+                               $this->related[$object] = $this->related_object($object)->load_values($values);
+                       }
+               }
+
+               return $this;
+       }
+
+       /**
+        * Loads a value according to the types defined by the column metadata.
+        *
+        * @param   string  column name
+        * @param   mixed   value to load
+        * @return  mixed
+        */
+       protected function load_type($column, $value)
+       {
+               $type = gettype($value);
+               if ($type == 'object' OR $type == 'array' OR ! isset($this->table_columns[$column]))
+                       return $value;
+
+               // Load column data
+               $column = $this->table_columns[$column];
+
+               if ($value === NULL AND ! empty($column['null']))
+                       return $value;
+
+               if ( ! empty($column['binary']) AND ! empty($column['exact']) AND (int) $column['length'] === 1)
+               {
+                       // Use boolean for BINARY(1) fields
+                       $column['type'] = 'boolean';
+               }
+
+               switch ($column['type'])
+               {
+                       case 'int':
+                               if ($value === '' AND ! empty($column['null']))
+                               {
+                                       // Forms will only submit strings, so empty integer values must be null
+                                       $value = NULL;
+                               }
+                               elseif ((float) $value > PHP_INT_MAX)
+                               {
+                                       // This number cannot be represented by a PHP integer, so we convert it to a string
+                                       $value = (string) $value;
+                               }
+                               else
+                               {
+                                       $value = (int) $value;
+                               }
+                       break;
+                       case 'float':
+                               $value = (float) $value;
+                       break;
+                       case 'boolean':
+                               $value = (bool) $value;
+                       break;
+                       case 'string':
+                               $value = (string) $value;
+                       break;
+               }
+
+               return $value;
+       }
+
+       /**
+        * Loads a database result, either as a new object for this model, or as
+        * an iterator for multiple rows.
+        *
+        * @chainable
+        * @param   boolean       return an iterator or load a single row
+        * @return  ORM           for single rows
+        * @return  ORM_Iterator  for multiple rows
+        */
+       protected function load_result($array = FALSE)
+       {
+               if ($array === FALSE)
+               {
+                       // Only fetch 1 record
+                       $this->db->limit(1);
+               }
+
+               if ( ! isset($this->db_applied['select']))
+               {
+                       // Select all columns by default
+                       $this->db->select($this->table_name.'.*');
+               }
+
+               if ( ! empty($this->load_with))
+               {
+                       foreach ($this->load_with as $alias => $object)
+                       {
+                               // Join each object into the results
+                               if (is_string($alias))
+                               {
+                                       // Use alias
+                                       $this->with($alias);
+                               }
+                               else
+                               {
+                                       // Use object
+                                       $this->with($object);
+                               }
+                       }
+               }
+
+               if ( ! isset($this->db_applied['orderby']) AND ! empty($this->sorting))
+               {
+                       $sorting = array();
+                       foreach ($this->sorting as $column => $direction)
+                       {
+                               if (strpos($column, '.') === FALSE)
+                               {
+                                       // Keeps sorting working properly when using JOINs on
+                                       // tables with columns of the same name
+                                       $column = $this->table_name.'.'.$column;
+                               }
+
+                               $sorting[$column] = $direction;
+                       }
+
+                       // Apply the user-defined sorting
+                       $this->db->orderby($sorting);
+               }
+
+               // Load the result
+               $result = $this->db->get($this->table_name);
+
+               if ($array === TRUE)
+               {
+                       // Return an iterated result
+                       return new ORM_Iterator($this, $result);
+               }
+
+               if ($result->count() === 1)
+               {
+                       // Load object values
+                       $this->load_values($result->result(FALSE)->current());
+               }
+               else
+               {
+                       // Clear the object, nothing was found
+                       $this->clear();
+               }
+
+               return $this;
+       }
+
+       /**
+        * Return an array of all the primary keys of the related table.
+        *
+        * @param   string  table name
+        * @param   object  ORM model to find relations of
+        * @return  array
+        */
+       protected function load_relations($table, ORM $model)
+       {
+               // Save the current query chain (otherwise the next call will clash)
+               $this->db->push();
+
+               $query = $this->db
+                       ->select($model->foreign_key(NULL).' AS id')
+                       ->from($table)
+                       ->where($this->foreign_key(NULL, $table), $this->object[$this->primary_key])
+                       ->get()
+                       ->result(TRUE);
+
+               $this->db->pop();
+
+               $relations = array();
+               foreach ($query as $row)
+               {
+                       $relations[] = $row->id;
+               }
+
+               return $relations;
+       }
+
+       /**
+        * Returns whether or not primary key is empty
+        *
+        * @return bool
+        */
+       protected function empty_primary_key()
+       {
+               return (empty($this->object[$this->primary_key]) AND $this->object[$this->primary_key] !== '0');
+       }
+
+} // End ORM
diff --git a/Server/system/libraries/ORM_Iterator.php b/Server/system/libraries/ORM_Iterator.php
new file mode 100644 (file)
index 0000000..41aa806
--- /dev/null
@@ -0,0 +1,228 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+* Object Relational Mapping (ORM) result iterator.
+*
+* $Id: ORM_Iterator.php 3769 2008-12-15 00:48:56Z zombor $
+*
+* @package    ORM
+* @author     Kohana Team
+* @copyright  (c) 2007-2008 Kohana Team
+* @license    http://kohanaphp.com/license.html
+*/
+class ORM_Iterator_Core implements Iterator, ArrayAccess, Countable {
+
+       // Class attributes
+       protected $class_name;
+       protected $primary_key;
+       protected $primary_val;
+
+       // Database result object
+       protected $result;
+
+       public function __construct(ORM $model, Database_Result $result)
+       {
+               // Class attributes
+               $this->class_name  = get_class($model);
+               $this->primary_key = $model->primary_key;
+               $this->primary_val = $model->primary_val;
+
+               // Database result
+               $this->result = $result->result(TRUE);
+       }
+
+       /**
+        * Returns an array of the results as ORM objects.
+        *
+        * @return  array
+        */
+       public function as_array()
+       {
+               $array = array();
+
+               if ($results = $this->result->result_array())
+               {
+                       // Import class name
+                       $class = $this->class_name;
+
+                       foreach ($results as $obj)
+                       {
+                               $array[] = new $class($obj);
+                       }
+               }
+
+               return $array;
+       }
+
+       /**
+        * Return an array of all of the primary keys for this object.
+        *
+        * @return  array
+        */
+       public function primary_key_array()
+       {
+               $ids = array();
+               foreach ($this->result as $row)
+               {
+                       $ids[] = $row->{$this->primary_key};
+               }
+               return $ids;
+       }
+
+       /**
+        * Create a key/value array from the results.
+        *
+        * @param   string  key column
+        * @param   string  value column
+        * @return  array
+        */
+       public function select_list($key = NULL, $val = NULL)
+       {
+               if ($key === NULL)
+               {
+                       // Use the default key
+                       $key = $this->primary_key;
+               }
+
+               if ($val === NULL)
+               {
+                       // Use the default value
+                       $val = $this->primary_val;
+               }
+
+               $array = array();
+               foreach ($this->result->result_array() as $row)
+               {
+                       $array[$row->$key] = $row->$val;
+               }
+               return $array;
+       }
+
+       /**
+        * Return a range of offsets.
+        *
+        * @param   integer  start
+        * @param   integer  end
+        * @return  array
+        */
+       public function range($start, $end)
+       {
+               // Array of objects
+               $array = array();
+
+               if ($this->result->offsetExists($start))
+               {
+                       // Import the class name
+                       $class = $this->class_name;
+
+                       // Set the end offset
+                       $end = $this->result->offsetExists($end) ? $end : $this->count();
+
+                       for ($i = $start; $i < $end; $i++)
+                       {
+                               // Insert each object in the range
+                               $array[] = new $class($this->result->offsetGet($i));
+                       }
+               }
+
+               return $array;
+       }
+
+       /**
+        * Countable: count
+        */
+       public function count()
+       {
+               return $this->result->count();
+       }
+
+       /**
+        * Iterator: current
+        */
+       public function current()
+       {
+               if ($row = $this->result->current())
+               {
+                       // Import class name
+                       $class = $this->class_name;
+
+                       $row = new $class($row);
+               }
+
+               return $row;
+       }
+
+       /**
+        * Iterator: key
+        */
+       public function key()
+       {
+               return $this->result->key();
+       }
+
+       /**
+        * Iterator: next
+        */
+       public function next()
+       {
+               return $this->result->next();
+       }
+
+       /**
+        * Iterator: rewind
+        */
+       public function rewind()
+       {
+               $this->result->rewind();
+       }
+
+       /**
+        * Iterator: valid
+        */
+       public function valid()
+       {
+               return $this->result->valid();
+       }
+
+       /**
+        * ArrayAccess: offsetExists
+        */
+       public function offsetExists($offset)
+       {
+               return $this->result->offsetExists($offset);
+       }
+
+       /**
+        * ArrayAccess: offsetGet
+        */
+       public function offsetGet($offset)
+       {
+               if ($this->result->offsetExists($offset))
+               {
+                       // Import class name
+                       $class = $this->class_name;
+
+                       return new $class($this->result->offsetGet($offset));
+               }
+       }
+
+       /**
+        * ArrayAccess: offsetSet
+        *
+        * @throws  Kohana_Database_Exception
+        */
+       public function offsetSet($offset, $value)
+       {
+               throw new Kohana_Database_Exception('database.result_read_only');
+       }
+
+       /**
+        * ArrayAccess: offsetUnset
+        *
+        * @throws  Kohana_Database_Exception
+        */
+       public function offsetUnset($offset)
+       {
+               throw new Kohana_Database_Exception('database.result_read_only');
+       }
+
+} // End ORM Iterator
\ No newline at end of file
diff --git a/Server/system/libraries/ORM_Tree.php b/Server/system/libraries/ORM_Tree.php
new file mode 100644 (file)
index 0000000..cdb09fd
--- /dev/null
@@ -0,0 +1,76 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Object Relational Mapping (ORM) "tree" extension. Allows ORM objects to act
+ * as trees, with parents and children.
+ *
+ * $Id: ORM_Tree.php 3923 2009-01-22 15:37:04Z samsoir $
+ *
+ * @package    ORM
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class ORM_Tree_Core extends ORM {
+
+       // Name of the child
+       protected $ORM_Tree_children;
+
+       // Parent keyword name
+       protected $ORM_Tree_parent_key = 'parent_id';
+
+       /**
+        * Overload ORM::__get to support "parent" and "children" properties.
+        *
+        * @param   string  column name
+        * @return  mixed
+        */
+       public function __get($column)
+       {
+               if ($column === 'parent')
+               {
+                       if (empty($this->related[$column]))
+                       {
+                               // Load child model
+                               $model = ORM::factory(inflector::singular($this->ORM_Tree_children));
+
+                               if (array_key_exists($this->ORM_Tree_parent_key, $this->object))
+                               {
+                                       // Find children of this parent
+                                       $model->where($model->primary_key, $this->object[$this->ORM_Tree_parent_key])->find();
+                               }
+
+                               $this->related[$column] = $model;
+                       }
+
+                       return $this->related[$column];
+               }
+               elseif ($column === 'children')
+               {
+                       if (empty($this->related[$column]))
+                       {
+                               $model = ORM::factory(inflector::singular($this->ORM_Tree_children));
+
+                               if ($this->ORM_Tree_children === $this->table_name)
+                               {
+                                       // Load children within this table
+                                       $this->related[$column] = $model
+                                               ->where($this->ORM_Tree_parent_key, $this->object[$this->primary_key])
+                                               ->find_all();
+                               }
+                               else
+                               {
+                                       // Find first selection of children
+                                       $this->related[$column] = $model
+                                               ->where($this->foreign_key(), $this->object[$this->primary_key])
+                                               ->where($this->ORM_Tree_parent_key, NULL)
+                                               ->find_all();
+                               }
+                       }
+
+                       return $this->related[$column];
+               }
+
+               return parent::__get($column);
+       }
+
+} // End ORM Tree
\ No newline at end of file
diff --git a/Server/system/libraries/ORM_Versioned.php b/Server/system/libraries/ORM_Versioned.php
new file mode 100644 (file)
index 0000000..7c3ee5d
--- /dev/null
@@ -0,0 +1,143 @@
+<?php
+/**
+ * Object Relational Mapping (ORM) "versioned" extension. Allows ORM objects to
+ * be revisioned instead of updated.
+ *
+ * $Id$
+ *
+ * @package    ORM
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class ORM_Versioned_Core extends ORM {
+
+       protected $last_version = NULL;
+
+       /**
+        * Overload ORM::save() to support versioned data
+        *
+        * @chainable
+        * @return  ORM
+        */
+       public function save()
+       {
+               $this->last_version = 1 + ($this->last_version === NULL ? $this->object['version'] : $this->last_version);
+               $this->__set('version', $this->last_version);
+
+               parent::save();
+
+               if ($this->saved)
+               {
+                       $data = array();
+                       foreach ($this->object as $key => $value)
+                       {
+                               if ($key === 'id')
+                                       continue;
+
+                               $data[$key] = $value;
+                       }
+                       $data[$this->foreign_key()] = $this->id;
+
+                       $this->db->insert($this->table_name.'_versions', $data);
+               }
+
+               return $this;
+       }
+
+       /**
+        * Loads previous version from current object
+        *
+        * @chainable
+        * @return  ORM
+        */
+       public function previous()
+       {
+               if ( ! $this->loaded)
+                       return $this;
+
+               $this->last_version = ($this->last_version === NULL) ? $this->object['version'] : $this->last_version;
+               $version = $this->last_version - 1;
+
+               $query = $this->db
+                       ->where($this->foreign_key(), $this->object[$this->primary_key])
+                       ->where('version', $version)
+                       ->limit(1)
+                       ->get($this->table_name.'_versions');
+
+               if ($query->count())
+               {
+                       $this->load_values($query->result(FALSE)->current());
+               }
+
+               return $this;
+       }
+
+       /**
+        * Restores the object with data from stored version
+        *
+        * @param   integer  version number you want to restore
+        * @return  ORM
+        */
+       public function restore($version)
+       {
+               if ( ! $this->loaded)
+                       return $this;
+
+               $query = $this->db
+                       ->where($this->foreign_key(), $this->object[$this->primary_key])
+                       ->where('version', $version)
+                       ->limit(1)
+                       ->get($this->table_name.'_versions');
+
+               if ($query->count())
+               {
+                       $row = $query->result(FALSE)->current();
+
+                       foreach ($row as $key => $value)
+                       {
+                               if ($key === $this->primary_key OR $key === $this->foreign_key())
+                               {
+                                       // Do not overwrite the primary key
+                                       continue;
+                               }
+
+                               if ($key === 'version')
+                               {
+                                       // Always use the current version
+                                       $value = $this->version;
+                               }
+
+                               $this->__set($key, $value);
+                       }
+
+                       $this->save();
+               }
+
+               return $this;
+       }
+
+       /**
+        * Overloads ORM::delete() to delete all versioned entries of current object
+        * and the object itself
+        *
+        * @param   integer  id of the object you want to delete
+        * @return  ORM
+        */
+       public function delete($id = NULL)
+       {
+               if ($id === NULL)
+               {
+                       // Use the current object id
+                       $id = $this->object[$this->primary_key];
+               }
+
+               if ($status = parent::delete($id))
+               {
+                       $this->db->where($this->foreign_key(), $id)->delete($this->table_name.'_versions');
+               }
+
+               return $status;
+       }
+
+}
\ No newline at end of file
diff --git a/Server/system/libraries/Pagination.php b/Server/system/libraries/Pagination.php
new file mode 100644 (file)
index 0000000..a8f7bb1
--- /dev/null
@@ -0,0 +1,236 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Pagination library.
+ *
+ * $Id: Pagination.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Pagination_Core {
+
+       // Config values
+       protected $base_url       = '';
+       protected $directory      = 'pagination';
+       protected $style          = 'classic';
+       protected $uri_segment    = 3;
+       protected $query_string   = '';
+       protected $items_per_page = 20;
+       protected $total_items    = 0;
+       protected $auto_hide      = FALSE;
+
+       // Autogenerated values
+       protected $url;
+       protected $current_page;
+       protected $total_pages;
+       protected $current_first_item;
+       protected $current_last_item;
+       protected $first_page;
+       protected $last_page;
+       protected $previous_page;
+       protected $next_page;
+       protected $sql_offset;
+       protected $sql_limit;
+
+       /**
+        * Constructs and returns a new Pagination object.
+        *
+        * @param   array   configuration settings
+        * @return  object
+        */
+       public function factory($config = array())
+       {
+               return new Pagination($config);
+       }
+
+       /**
+        * Constructs a new Pagination object.
+        *
+        * @param   array  configuration settings
+        * @return  void
+        */
+       public function __construct($config = array())
+       {
+               // No custom group name given
+               if ( ! isset($config['group']))
+               {
+                       $config['group'] = 'default';
+               }
+
+               // Pagination setup
+               $this->initialize($config);
+
+               Kohana::log('debug', 'Pagination Library initialized');
+       }
+
+       /**
+        * Sets config values.
+        *
+        * @throws  Kohana_Exception
+        * @param   array  configuration settings
+        * @return  void
+        */
+       public function initialize($config = array())
+       {
+               // Load config group
+               if (isset($config['group']))
+               {
+                       // Load and validate config group
+                       if ( ! is_array($group_config = Kohana::config('pagination.'.$config['group'])))
+                               throw new Kohana_Exception('pagination.undefined_group', $config['group']);
+
+                       // All pagination config groups inherit default config group
+                       if ($config['group'] !== 'default')
+                       {
+                               // Load and validate default config group
+                               if ( ! is_array($default_config = Kohana::config('pagination.default')))
+                                       throw new Kohana_Exception('pagination.undefined_group', 'default');
+
+                               // Merge config group with default config group
+                               $group_config += $default_config;
+                       }
+
+                       // Merge custom config items with config group
+                       $config += $group_config;
+               }
+
+               // Assign config values to the object
+               foreach ($config as $key => $value)
+               {
+                       if (property_exists($this, $key))
+                       {
+                               $this->$key = $value;
+                       }
+               }
+
+               // Clean view directory
+               $this->directory = trim($this->directory, '/').'/';
+
+               // Build generic URL with page in query string
+               if ($this->query_string !== '')
+               {
+                       // Extract current page
+                       $this->current_page = isset($_GET[$this->query_string]) ? (int) $_GET[$this->query_string] : 1;
+
+                       // Insert {page} placeholder
+                       $_GET[$this->query_string] = '{page}';
+
+                       // Create full URL
+                       $base_url = ($this->base_url === '') ? Router::$current_uri : $this->base_url;
+                       $this->url = url::site($base_url).'?'.str_replace('%7Bpage%7D', '{page}', http_build_query($_GET));
+
+                       // Reset page number
+                       $_GET[$this->query_string] = $this->current_page;
+               }
+
+               // Build generic URL with page as URI segment
+               else
+               {
+                       // Use current URI if no base_url set
+                       $this->url = ($this->base_url === '') ? Router::$segments : explode('/', trim($this->base_url, '/'));
+
+                       // Convert uri 'label' to corresponding integer if needed
+                       if (is_string($this->uri_segment))
+                       {
+                               if (($key = array_search($this->uri_segment, $this->url)) === FALSE)
+                               {
+                                       // If uri 'label' is not found, auto add it to base_url
+                                       $this->url[] = $this->uri_segment;
+                                       $this->uri_segment = count($this->url) + 1;
+                               }
+                               else
+                               {
+                                       $this->uri_segment = $key + 2;
+                               }
+                       }
+
+                       // Insert {page} placeholder
+                       $this->url[$this->uri_segment - 1] = '{page}';
+
+                       // Create full URL
+                       $this->url = url::site(implode('/', $this->url)).Router::$query_string;
+
+                       // Extract current page
+                       $this->current_page = URI::instance()->segment($this->uri_segment);
+               }
+
+               // Core pagination values
+               $this->total_items        = (int) max(0, $this->total_items);
+               $this->items_per_page     = (int) max(1, $this->items_per_page);
+               $this->total_pages        = (int) ceil($this->total_items / $this->items_per_page);
+               $this->current_page       = (int) min(max(1, $this->current_page), max(1, $this->total_pages));
+               $this->current_first_item = (int) min((($this->current_page - 1) * $this->items_per_page) + 1, $this->total_items);
+               $this->current_last_item  = (int) min($this->current_first_item + $this->items_per_page - 1, $this->total_items);
+
+               // If there is no first/last/previous/next page, relative to the
+               // current page, value is set to FALSE. Valid page number otherwise.
+               $this->first_page         = ($this->current_page === 1) ? FALSE : 1;
+               $this->last_page          = ($this->current_page >= $this->total_pages) ? FALSE : $this->total_pages;
+               $this->previous_page      = ($this->current_page > 1) ? $this->current_page - 1 : FALSE;
+               $this->next_page          = ($this->current_page < $this->total_pages) ? $this->current_page + 1 : FALSE;
+
+               // SQL values
+               $this->sql_offset         = (int) ($this->current_page - 1) * $this->items_per_page;
+               $this->sql_limit          = sprintf(' LIMIT %d OFFSET %d ', $this->items_per_page, $this->sql_offset);
+       }
+
+       /**
+        * Generates the HTML for the chosen pagination style.
+        *
+        * @param   string  pagination style
+        * @return  string  pagination html
+        */
+       public function render($style = NULL)
+       {
+               // Hide single page pagination
+               if ($this->auto_hide === TRUE AND $this->total_pages <= 1)
+                       return '';
+
+               if ($style === NULL)
+               {
+                       // Use default style
+                       $style = $this->style;
+               }
+
+               // Return rendered pagination view
+               return View::factory($this->directory.$style, get_object_vars($this))->render();
+       }
+
+       /**
+        * Magically converts Pagination object to string.
+        *
+        * @return  string  pagination html
+        */
+       public function __toString()
+       {
+               return $this->render();
+       }
+
+       /**
+        * Magically gets a pagination variable.
+        *
+        * @param   string  variable key
+        * @return  mixed   variable value if the key is found
+        * @return  void    if the key is not found
+        */
+       public function __get($key)
+       {
+               if (isset($this->$key))
+                       return $this->$key;
+       }
+
+       /**
+        * Adds a secondary interface for accessing properties, e.g. $pagination->total_pages().
+        * Note that $pagination->total_pages is the recommended way to access properties.
+        *
+        * @param   string  function name
+        * @return  string
+        */
+       public function __call($func, $args = NULL)
+       {
+               return $this->__get($func);
+       }
+
+} // End Pagination Class
\ No newline at end of file
diff --git a/Server/system/libraries/Profiler.php b/Server/system/libraries/Profiler.php
new file mode 100644 (file)
index 0000000..9da053f
--- /dev/null
@@ -0,0 +1,271 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Adds useful information to the bottom of the current page for debugging and optimization purposes.
+ *
+ * Benchmarks   - The times and memory usage of benchmarks run by the Benchmark library.
+ * Database     - The raw SQL and number of affected rows of Database queries.
+ * Session Data - Data stored in the current session if using the Session library.
+ * POST Data    - The name and values of any POST data submitted to the current page.
+ * Cookie Data  - All cookies sent for the current request.
+ *
+ * $Id: Profiler.php 4383 2009-06-03 00:17:24Z ixmatus $
+ *
+ * @package    Profiler
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Profiler_Core {
+
+       protected $profiles = array();
+       protected $show;
+
+       public function __construct()
+       {
+               // Add all built in profiles to event
+               Event::add('profiler.run', array($this, 'benchmarks'));
+               Event::add('profiler.run', array($this, 'database'));
+               Event::add('profiler.run', array($this, 'session'));
+               Event::add('profiler.run', array($this, 'post'));
+               Event::add('profiler.run', array($this, 'cookies'));
+
+               // Add profiler to page output automatically
+               Event::add('system.display', array($this, 'render'));
+
+               Kohana::log('debug', 'Profiler Library initialized');
+       }
+
+       /**
+        * Magic __call method. Creates a new profiler section object.
+        *
+        * @param   string   input type
+        * @param   string   input name
+        * @return  object
+        */
+       public function __call($method, $args)
+       {
+               if ( ! $this->show OR (is_array($this->show) AND ! in_array($args[0], $this->show)))
+                       return FALSE;
+
+               // Class name
+               $class = 'Profiler_'.ucfirst($method);
+
+               $class = new $class();
+
+               $this->profiles[$args[0]] = $class;
+
+               return $class;
+       }
+
+       /**
+        * Disables the profiler for this page only.
+        * Best used when profiler is autoloaded.
+        *
+        * @return  void
+        */
+       public function disable()
+       {
+               // Removes itself from the event queue
+               Event::clear('system.display', array($this, 'render'));
+       }
+
+       /**
+        * Render the profiler. Output is added to the bottom of the page by default.
+        *
+        * @param   boolean  return the output if TRUE
+        * @return  void|string
+        */
+       public function render($return = FALSE)
+       {
+               $start = microtime(TRUE);
+
+               $get = isset($_GET['profiler']) ? explode(',', $_GET['profiler']) : array();
+               $this->show = empty($get) ? Kohana::config('profiler.show') : $get;
+
+               Event::run('profiler.run', $this);
+
+               $styles = '';
+               foreach ($this->profiles as $profile)
+               {
+                       $styles .= $profile->styles();
+               }
+
+               // Don't display if there's no profiles
+               if (empty($this->profiles))
+                       return;
+
+               // Load the profiler view
+               $data = array
+               (
+                       'profiles' => $this->profiles,
+                       'styles'   => $styles,
+                       'execution_time' => microtime(TRUE) - $start
+               );
+               $view = new View('kohana_profiler', $data);
+
+               // Return rendered view if $return is TRUE
+               if ($return === TRUE)
+                       return $view->render();
+
+               // Add profiler data to the output
+               if (stripos(Kohana::$output, '</body>') !== FALSE)
+               {
+                       // Closing body tag was found, insert the profiler data before it
+                       Kohana::$output = str_ireplace('</body>', $view->render().'</body>', Kohana::$output);
+               }
+               else
+               {
+                       // Append the profiler data to the output
+                       Kohana::$output .= $view->render();
+               }
+       }
+
+       /**
+        * Benchmark times and memory usage from the Benchmark library.
+        *
+        * @return  void
+        */
+       public function benchmarks()
+       {
+               if ( ! $table = $this->table('benchmarks'))
+                       return;
+
+               $table->add_column();
+               $table->add_column('kp-column kp-data');
+               $table->add_column('kp-column kp-data');
+               $table->add_column('kp-column kp-data');
+               $table->add_row(array('Benchmarks', 'Time', 'Count', 'Memory'), 'kp-title', 'background-color: #FFE0E0');
+
+               $benchmarks = Benchmark::get(TRUE);
+
+               // Moves the first benchmark (total execution time) to the end of the array
+               $benchmarks = array_slice($benchmarks, 1) + array_slice($benchmarks, 0, 1);
+
+               text::alternate();
+               foreach ($benchmarks as $name => $benchmark)
+               {
+                       // Clean unique id from system benchmark names
+                       $name = ucwords(str_replace(array('_', '-'), ' ', str_replace(SYSTEM_BENCHMARK.'_', '', $name)));
+
+                       $data = array($name, number_format($benchmark['time'], 3), $benchmark['count'], number_format($benchmark['memory'] / 1024 / 1024, 2).'MB');
+                       $class = text::alternate('', 'kp-altrow');
+
+                       if ($name == 'Total Execution')
+                               $class = 'kp-totalrow';
+
+                       $table->add_row($data, $class);
+               }
+       }
+
+       /**
+        * Database query benchmarks.
+        *
+        * @return  void
+        */
+       public function database()
+       {
+               if ( ! $table = $this->table('database'))
+                       return;
+
+               $table->add_column();
+               $table->add_column('kp-column kp-data');
+               $table->add_column('kp-column kp-data');
+               $table->add_row(array('Queries', 'Time', 'Rows'), 'kp-title', 'background-color: #E0FFE0');
+
+               $queries = Database::$benchmarks;
+
+               text::alternate();
+               $total_time = $total_rows = 0;
+               foreach ($queries as $query)
+               {
+                       $data = array($query['query'], number_format($query['time'], 3), $query['rows']);
+                       $class = text::alternate('', 'kp-altrow');
+                       $table->add_row($data, $class);
+                       $total_time += $query['time'];
+                       $total_rows += $query['rows'];
+               }
+
+               $data = array('Total: ' . count($queries), number_format($total_time, 3), $total_rows);
+               $table->add_row($data, 'kp-totalrow');
+       }
+
+       /**
+        * Session data.
+        *
+        * @return  void
+        */
+       public function session()
+       {
+               if (empty($_SESSION)) return;
+
+               if ( ! $table = $this->table('session'))
+                       return;
+
+               $table->add_column('kp-name');
+               $table->add_column();
+               $table->add_row(array('Session', 'Value'), 'kp-title', 'background-color: #CCE8FB');
+
+               text::alternate();
+               foreach($_SESSION as $name => $value)
+               {
+                       if (is_object($value))
+                       {
+                               $value = get_class($value).' [object]';
+                       }
+
+                       $data = array($name, $value);
+                       $class = text::alternate('', 'kp-altrow');
+                       $table->add_row($data, $class);
+               }
+       }
+
+       /**
+        * POST data.
+        *
+        * @return  void
+        */
+       public function post()
+       {
+               if (empty($_POST)) return;
+
+               if ( ! $table = $this->table('post'))
+                       return;
+
+               $table->add_column('kp-name');
+               $table->add_column();
+               $table->add_row(array('POST', 'Value'), 'kp-title', 'background-color: #E0E0FF');
+
+               text::alternate();
+               foreach($_POST as $name => $value)
+               {
+                       $data = array($name, $value);
+                       $class = text::alternate('', 'kp-altrow');
+                       $table->add_row($data, $class);
+               }
+       }
+
+       /**
+        * Cookie data.
+        *
+        * @return  void
+        */
+       public function cookies()
+       {
+               if (empty($_COOKIE)) return;
+
+               if ( ! $table = $this->table('cookies'))
+                       return;
+
+               $table->add_column('kp-name');
+               $table->add_column();
+               $table->add_row(array('Cookies', 'Value'), 'kp-title', 'background-color: #FFF4D7');
+
+               text::alternate();
+               foreach($_COOKIE as $name => $value)
+               {
+                       $data = array($name, $value);
+                       $class = text::alternate('', 'kp-altrow');
+                       $table->add_row($data, $class);
+               }
+       }
+}
\ No newline at end of file
diff --git a/Server/system/libraries/Profiler_Table.php b/Server/system/libraries/Profiler_Table.php
new file mode 100644 (file)
index 0000000..a0058a5
--- /dev/null
@@ -0,0 +1,69 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Provides a table layout for sections in the Profiler library.
+ *
+ * $Id$
+ *
+ * @package    Profiler
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Profiler_Table_Core {
+
+       protected $columns = array();
+       protected $rows = array();
+
+       /**
+        * Get styles for table.
+        *
+        * @return  string
+        */
+       public function styles()
+       {
+               static $styles_output;
+
+               if ( ! $styles_output)
+               {
+                       $styles_output = TRUE;
+                       return file_get_contents(Kohana::find_file('views', 'kohana_profiler_table', FALSE, 'css'));
+               }
+
+               return '';
+       }
+
+       /**
+        * Add column to table.
+        *
+        * @param  string  CSS class
+        * @param  string  CSS style
+        */
+       public function add_column($class = '', $style = '')
+       {
+               $this->columns[] = array('class' => $class, 'style' => $style);
+       }
+
+       /**
+        * Add row to table.
+        *
+        * @param  array   data to go in table cells
+        * @param  string  CSS class
+        * @param  string  CSS style
+        */
+       public function add_row($data, $class = '', $style = '')
+       {
+               $this->rows[] = array('data' => $data, 'class' => $class, 'style' => $style);
+       }
+
+       /**
+        * Render table.
+        *
+        * @return  string
+        */
+       public function render()
+       {
+               $data['rows'] = $this->rows;
+               $data['columns'] = $this->columns;
+               return View::factory('kohana_profiler_table', $data)->render();
+       }
+}
\ No newline at end of file
diff --git a/Server/system/libraries/Router.php b/Server/system/libraries/Router.php
new file mode 100644 (file)
index 0000000..ef0e1e4
--- /dev/null
@@ -0,0 +1,304 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Router
+ *
+ * $Id: Router.php 4391 2009-06-04 03:10:12Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Router_Core {
+
+       protected static $routes;
+
+       public static $current_uri  = '';
+       public static $query_string = '';
+       public static $complete_uri = '';
+       public static $routed_uri   = '';
+       public static $url_suffix   = '';
+
+       public static $segments;
+       public static $rsegments;
+
+       public static $controller;
+       public static $controller_path;
+
+       public static $method    = 'index';
+       public static $arguments = array();
+
+       /**
+        * Router setup routine. Automatically called during Kohana setup process.
+        *
+        * @return  void
+        */
+       public static function setup()
+       {
+               if ( ! empty($_SERVER['QUERY_STRING']))
+               {
+                       // Set the query string to the current query string
+                       Router::$query_string = '?'.trim($_SERVER['QUERY_STRING'], '&/');
+               }
+
+               if (Router::$routes === NULL)
+               {
+                       // Load routes
+                       Router::$routes = Kohana::config('routes');
+               }
+
+               // Default route status
+               $default_route = FALSE;
+
+               if (Router::$current_uri === '')
+               {
+                       // Make sure the default route is set
+                       if ( ! isset(Router::$routes['_default']))
+                               throw new Kohana_Exception('core.no_default_route');
+
+                       // Use the default route when no segments exist
+                       Router::$current_uri = Router::$routes['_default'];
+
+                       // Default route is in use
+                       $default_route = TRUE;
+               }
+
+               // Make sure the URL is not tainted with HTML characters
+               Router::$current_uri = html::specialchars(Router::$current_uri, FALSE);
+
+               // Remove all dot-paths from the URI, they are not valid
+               Router::$current_uri = preg_replace('#\.[\s./]*/#', '', Router::$current_uri);
+
+               // At this point segments, rsegments, and current URI are all the same
+               Router::$segments = Router::$rsegments = Router::$current_uri = trim(Router::$current_uri, '/');
+
+               // Set the complete URI
+               Router::$complete_uri = Router::$current_uri.Router::$query_string;
+
+               // Explode the segments by slashes
+               Router::$segments = ($default_route === TRUE OR Router::$segments === '') ? array() : explode('/', Router::$segments);
+
+               if ($default_route === FALSE AND count(Router::$routes) > 1)
+               {
+                       // Custom routing
+                       Router::$rsegments = Router::routed_uri(Router::$current_uri);
+               }
+
+               // The routed URI is now complete
+               Router::$routed_uri = Router::$rsegments;
+
+               // Routed segments will never be empty
+               Router::$rsegments = explode('/', Router::$rsegments);
+
+               // Prepare to find the controller
+               $controller_path = '';
+               $method_segment  = NULL;
+
+               // Paths to search
+               $paths = Kohana::include_paths();
+
+               foreach (Router::$rsegments as $key => $segment)
+               {
+                       // Add the segment to the search path
+                       $controller_path .= $segment;
+
+                       $found = FALSE;
+                       foreach ($paths as $dir)
+                       {
+                               // Search within controllers only
+                               $dir .= 'controllers/';
+
+                               if (is_dir($dir.$controller_path) OR is_file($dir.$controller_path.EXT))
+                               {
+                                       // Valid path
+                                       $found = TRUE;
+
+                                       // The controller must be a file that exists with the search path
+                                       if ($c = str_replace('\\', '/', realpath($dir.$controller_path.EXT))
+                                           AND is_file($c) AND strpos($c, $dir) === 0)
+                                       {
+                                               // Set controller name
+                                               Router::$controller = $segment;
+
+                                               // Change controller path
+                                               Router::$controller_path = $c;
+
+                                               // Set the method segment
+                                               $method_segment = $key + 1;
+
+                                               // Stop searching
+                                               break;
+                                       }
+                               }
+                       }
+
+                       if ($found === FALSE)
+                       {
+                               // Maximum depth has been reached, stop searching
+                               break;
+                       }
+
+                       // Add another slash
+                       $controller_path .= '/';
+               }
+
+               if ($method_segment !== NULL AND isset(Router::$rsegments[$method_segment]))
+               {
+                       // Set method
+                       Router::$method = Router::$rsegments[$method_segment];
+
+                       if (isset(Router::$rsegments[$method_segment + 1]))
+                       {
+                               // Set arguments
+                               Router::$arguments = array_slice(Router::$rsegments, $method_segment + 1);
+                       }
+               }
+
+               // Last chance to set routing before a 404 is triggered
+               Event::run('system.post_routing');
+
+               if (Router::$controller === NULL)
+               {
+                       // No controller was found, so no page can be rendered
+                       Event::run('system.404');
+               }
+       }
+
+       /**
+        * Attempts to determine the current URI using CLI, GET, PATH_INFO, ORIG_PATH_INFO, or PHP_SELF.
+        *
+        * @return  void
+        */
+       public static function find_uri()
+       {
+               if (PHP_SAPI === 'cli')
+               {
+                       // Command line requires a bit of hacking
+                       if (isset($_SERVER['argv'][1]))
+                       {
+                               Router::$current_uri = $_SERVER['argv'][1];
+
+                               // Remove GET string from segments
+                               if (($query = strpos(Router::$current_uri, '?')) !== FALSE)
+                               {
+                                       list (Router::$current_uri, $query) = explode('?', Router::$current_uri, 2);
+
+                                       // Parse the query string into $_GET
+                                       parse_str($query, $_GET);
+
+                                       // Convert $_GET to UTF-8
+                                       $_GET = utf8::clean($_GET);
+                               }
+                       }
+               }
+               elseif (isset($_GET['kohana_uri']))
+               {
+                       // Use the URI defined in the query string
+                       Router::$current_uri = $_GET['kohana_uri'];
+
+                       // Remove the URI from $_GET
+                       unset($_GET['kohana_uri']);
+
+                       // Remove the URI from $_SERVER['QUERY_STRING']
+                       $_SERVER['QUERY_STRING'] = preg_replace('~\bkohana_uri\b[^&]*+&?~', '', $_SERVER['QUERY_STRING']);
+               }
+               elseif (isset($_SERVER['PATH_INFO']) AND $_SERVER['PATH_INFO'])
+               {
+                       Router::$current_uri = $_SERVER['PATH_INFO'];
+               }
+               elseif (isset($_SERVER['ORIG_PATH_INFO']) AND $_SERVER['ORIG_PATH_INFO'])
+               {
+                       Router::$current_uri = $_SERVER['ORIG_PATH_INFO'];
+               }
+               elseif (isset($_SERVER['PHP_SELF']) AND $_SERVER['PHP_SELF'])
+               {
+                       Router::$current_uri = $_SERVER['PHP_SELF'];
+               }
+               
+               if (($strpos_fc = strpos(Router::$current_uri, KOHANA)) !== FALSE)
+               {
+                       // Remove the front controller from the current uri
+                       Router::$current_uri = (string) substr(Router::$current_uri, $strpos_fc + strlen(KOHANA));
+               }
+               
+               // Remove slashes from the start and end of the URI
+               Router::$current_uri = trim(Router::$current_uri, '/');
+               
+               if (Router::$current_uri !== '')
+               {
+                       if ($suffix = Kohana::config('core.url_suffix') AND strpos(Router::$current_uri, $suffix) !== FALSE)
+                       {
+                               // Remove the URL suffix
+                               Router::$current_uri = preg_replace('#'.preg_quote($suffix).'$#u', '', Router::$current_uri);
+
+                               // Set the URL suffix
+                               Router::$url_suffix = $suffix;
+                       }
+
+                       // Reduce multiple slashes into single slashes
+                       Router::$current_uri = preg_replace('#//+#', '/', Router::$current_uri);
+               }
+       }
+
+       /**
+        * Generates routed URI from given URI.
+        *
+        * @param  string  URI to convert
+        * @return string  Routed uri
+        */
+       public static function routed_uri($uri)
+       {
+               if (Router::$routes === NULL)
+               {
+                       // Load routes
+                       Router::$routes = Kohana::config('routes');
+               }
+
+               // Prepare variables
+               $routed_uri = $uri = trim($uri, '/');
+
+               if (isset(Router::$routes[$uri]))
+               {
+                       // Literal match, no need for regex
+                       $routed_uri = Router::$routes[$uri];
+               }
+               else
+               {
+                       // Loop through the routes and see if anything matches
+                       foreach (Router::$routes as $key => $val)
+                       {
+                               if ($key === '_default') continue;
+
+                               // Trim slashes
+                               $key = trim($key, '/');
+                               $val = trim($val, '/');
+
+                               if (preg_match('#^'.$key.'$#u', $uri))
+                               {
+                                       if (strpos($val, '$') !== FALSE)
+                                       {
+                                               // Use regex routing
+                                               $routed_uri = preg_replace('#^'.$key.'$#u', $val, $uri);
+                                       }
+                                       else
+                                       {
+                                               // Standard routing
+                                               $routed_uri = $val;
+                                       }
+
+                                       // A valid route has been found
+                                       break;
+                               }
+                       }
+               }
+
+               if (isset(Router::$routes[$routed_uri]))
+               {
+                       // Check for double routing (without regex)
+                       $routed_uri = Router::$routes[$routed_uri];
+               }
+
+               return trim($routed_uri, '/');
+       }
+
+} // End Router
\ No newline at end of file
diff --git a/Server/system/libraries/Session.php b/Server/system/libraries/Session.php
new file mode 100644 (file)
index 0000000..e03f5df
--- /dev/null
@@ -0,0 +1,458 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Session library.
+ *
+ * $Id: Session.php 4073 2009-03-13 17:53:58Z Shadowhand $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Session_Core {
+
+       // Session singleton
+       protected static $instance;
+
+       // Protected key names (cannot be set by the user)
+       protected static $protect = array('session_id', 'user_agent', 'last_activity', 'ip_address', 'total_hits', '_kf_flash_');
+
+       // Configuration and driver
+       protected static $config;
+       protected static $driver;
+
+       // Flash variables
+       protected static $flash;
+
+       // Input library
+       protected $input;
+
+       /**
+        * Singleton instance of Session.
+        */
+       public static function instance()
+       {
+               if (Session::$instance == NULL)
+               {
+                       // Create a new instance
+                       new Session;
+               }
+
+               return Session::$instance;
+       }
+
+       /**
+        * On first session instance creation, sets up the driver and creates session.
+        */
+       public function __construct()
+       {
+               $this->input = Input::instance();
+
+               // This part only needs to be run once
+               if (Session::$instance === NULL)
+               {
+                       // Load config
+                       Session::$config = Kohana::config('session');
+
+                       // Makes a mirrored array, eg: foo=foo
+                       Session::$protect = array_combine(Session::$protect, Session::$protect);
+
+                       // Configure garbage collection
+                       ini_set('session.gc_probability', (int) Session::$config['gc_probability']);
+                       ini_set('session.gc_divisor', 100);
+                       ini_set('session.gc_maxlifetime', (Session::$config['expiration'] == 0) ? 86400 : Session::$config['expiration']);
+
+                       // Create a new session
+                       $this->create();
+
+                       if (Session::$config['regenerate'] > 0 AND ($_SESSION['total_hits'] % Session::$config['regenerate']) === 0)
+                       {
+                               // Regenerate session id and update session cookie
+                               $this->regenerate();
+                       }
+                       else
+                       {
+                               // Always update session cookie to keep the session alive
+                               cookie::set(Session::$config['name'], $_SESSION['session_id'], Session::$config['expiration']);
+                       }
+
+                       // Close the session just before sending the headers, so that
+                       // the session cookie(s) can be written.
+                       Event::add('system.send_headers', array($this, 'write_close'));
+
+                       // Make sure that sessions are closed before exiting
+                       register_shutdown_function(array($this, 'write_close'));
+
+                       // Singleton instance
+                       Session::$instance = $this;
+               }
+
+               Kohana::log('debug', 'Session Library initialized');
+       }
+
+       /**
+        * Get the session id.
+        *
+        * @return  string
+        */
+       public function id()
+       {
+               return $_SESSION['session_id'];
+       }
+
+       /**
+        * Create a new session.
+        *
+        * @param   array  variables to set after creation
+        * @return  void
+        */
+       public function create($vars = NULL)
+       {
+               // Destroy any current sessions
+               $this->destroy();
+
+               if (Session::$config['driver'] !== 'native')
+               {
+                       // Set driver name
+                       $driver = 'Session_'.ucfirst(Session::$config['driver']).'_Driver';
+
+                       // Load the driver
+                       if ( ! Kohana::auto_load($driver))
+                               throw new Kohana_Exception('core.driver_not_found', Session::$config['driver'], get_class($this));
+
+                       // Initialize the driver
+                       Session::$driver = new $driver();
+
+                       // Validate the driver
+                       if ( ! (Session::$driver instanceof Session_Driver))
+                               throw new Kohana_Exception('core.driver_implements', Session::$config['driver'], get_class($this), 'Session_Driver');
+
+                       // Register non-native driver as the session handler
+                       session_set_save_handler
+                       (
+                               array(Session::$driver, 'open'),
+                               array(Session::$driver, 'close'),
+                               array(Session::$driver, 'read'),
+                               array(Session::$driver, 'write'),
+                               array(Session::$driver, 'destroy'),
+                               array(Session::$driver, 'gc')
+                       );
+               }
+
+               // Validate the session name
+               if ( ! preg_match('~^(?=.*[a-z])[a-z0-9_]++$~iD', Session::$config['name']))
+                       throw new Kohana_Exception('session.invalid_session_name', Session::$config['name']);
+
+               // Name the session, this will also be the name of the cookie
+               session_name(Session::$config['name']);
+
+               // Set the session cookie parameters
+               session_set_cookie_params
+               (
+                       Session::$config['expiration'],
+                       Kohana::config('cookie.path'),
+                       Kohana::config('cookie.domain'),
+                       Kohana::config('cookie.secure'),
+                       Kohana::config('cookie.httponly')
+               );
+
+               // Start the session!
+               session_start();
+
+               // Put session_id in the session variable
+               $_SESSION['session_id'] = session_id();
+
+               // Set defaults
+               if ( ! isset($_SESSION['_kf_flash_']))
+               {
+                       $_SESSION['total_hits'] = 0;
+                       $_SESSION['_kf_flash_'] = array();
+
+                       $_SESSION['user_agent'] = Kohana::$user_agent;
+                       $_SESSION['ip_address'] = $this->input->ip_address();
+               }
+
+               // Set up flash variables
+               Session::$flash =& $_SESSION['_kf_flash_'];
+
+               // Increase total hits
+               $_SESSION['total_hits'] += 1;
+
+               // Validate data only on hits after one
+               if ($_SESSION['total_hits'] > 1)
+               {
+                       // Validate the session
+                       foreach (Session::$config['validate'] as $valid)
+                       {
+                               switch ($valid)
+                               {
+                                       // Check user agent for consistency
+                                       case 'user_agent':
+                                               if ($_SESSION[$valid] !== Kohana::$user_agent)
+                                                       return $this->create();
+                                       break;
+
+                                       // Check ip address for consistency
+                                       case 'ip_address':
+                                               if ($_SESSION[$valid] !== $this->input->$valid())
+                                                       return $this->create();
+                                       break;
+
+                                       // Check expiration time to prevent users from manually modifying it
+                                       case 'expiration':
+                                               if (time() - $_SESSION['last_activity'] > ini_get('session.gc_maxlifetime'))
+                                                       return $this->create();
+                                       break;
+                               }
+                       }
+               }
+
+               // Expire flash keys
+               $this->expire_flash();
+
+               // Update last activity
+               $_SESSION['last_activity'] = time();
+
+               // Set the new data
+               Session::set($vars);
+       }
+
+       /**
+        * Regenerates the global session id.
+        *
+        * @return  void
+        */
+       public function regenerate()
+       {
+               if (Session::$config['driver'] === 'native')
+               {
+                       // Generate a new session id
+                       // Note: also sets a new session cookie with the updated id
+                       session_regenerate_id(TRUE);
+
+                       // Update session with new id
+                       $_SESSION['session_id'] = session_id();
+               }
+               else
+               {
+                       // Pass the regenerating off to the driver in case it wants to do anything special
+                       $_SESSION['session_id'] = Session::$driver->regenerate();
+               }
+
+               // Get the session name
+               $name = session_name();
+
+               if (isset($_COOKIE[$name]))
+               {
+                       // Change the cookie value to match the new session id to prevent "lag"
+                       $_COOKIE[$name] = $_SESSION['session_id'];
+               }
+       }
+
+       /**
+        * Destroys the current session.
+        *
+        * @return  void
+        */
+       public function destroy()
+       {
+               if (session_id() !== '')
+               {
+                       // Get the session name
+                       $name = session_name();
+
+                       // Destroy the session
+                       session_destroy();
+
+                       // Re-initialize the array
+                       $_SESSION = array();
+
+                       // Delete the session cookie
+                       cookie::delete($name);
+               }
+       }
+
+       /**
+        * Runs the system.session_write event, then calls session_write_close.
+        *
+        * @return  void
+        */
+       public function write_close()
+       {
+               static $run;
+
+               if ($run === NULL)
+               {
+                       $run = TRUE;
+
+                       // Run the events that depend on the session being open
+                       Event::run('system.session_write');
+
+                       // Expire flash keys
+                       $this->expire_flash();
+
+                       // Close the session
+                       session_write_close();
+               }
+       }
+
+       /**
+        * Set a session variable.
+        *
+        * @param   string|array  key, or array of values
+        * @param   mixed         value (if keys is not an array)
+        * @return  void
+        */
+       public function set($keys, $val = FALSE)
+       {
+               if (empty($keys))
+                       return FALSE;
+
+               if ( ! is_array($keys))
+               {
+                       $keys = array($keys => $val);
+               }
+
+               foreach ($keys as $key => $val)
+               {
+                       if (isset(Session::$protect[$key]))
+                               continue;
+
+                       // Set the key
+                       $_SESSION[$key] = $val;
+               }
+       }
+
+       /**
+        * Set a flash variable.
+        *
+        * @param   string|array  key, or array of values
+        * @param   mixed         value (if keys is not an array)
+        * @return  void
+        */
+       public function set_flash($keys, $val = FALSE)
+       {
+               if (empty($keys))
+                       return FALSE;
+
+               if ( ! is_array($keys))
+               {
+                       $keys = array($keys => $val);
+               }
+
+               foreach ($keys as $key => $val)
+               {
+                       if ($key == FALSE)
+                               continue;
+
+                       Session::$flash[$key] = 'new';
+                       Session::set($key, $val);
+               }
+       }
+
+       /**
+        * Freshen one, multiple or all flash variables.
+        *
+        * @param   string  variable key(s)
+        * @return  void
+        */
+       public function keep_flash($keys = NULL)
+       {
+               $keys = ($keys === NULL) ? array_keys(Session::$flash) : func_get_args();
+
+               foreach ($keys as $key)
+               {
+                       if (isset(Session::$flash[$key]))
+                       {
+                               Session::$flash[$key] = 'new';
+                       }
+               }
+       }
+
+       /**
+        * Expires old flash data and removes it from the session.
+        *
+        * @return  void
+        */
+       public function expire_flash()
+       {
+               static $run;
+
+               // Method can only be run once
+               if ($run === TRUE)
+                       return;
+
+               if ( ! empty(Session::$flash))
+               {
+                       foreach (Session::$flash as $key => $state)
+                       {
+                               if ($state === 'old')
+                               {
+                                       // Flash has expired
+                                       unset(Session::$flash[$key], $_SESSION[$key]);
+                               }
+                               else
+                               {
+                                       // Flash will expire
+                                       Session::$flash[$key] = 'old';
+                               }
+                       }
+               }
+
+               // Method has been run
+               $run = TRUE;
+       }
+
+       /**
+        * Get a variable. Access to sub-arrays is supported with key.subkey.
+        *
+        * @param   string  variable key
+        * @param   mixed   default value returned if variable does not exist
+        * @return  mixed   Variable data if key specified, otherwise array containing all session data.
+        */
+       public function get($key = FALSE, $default = FALSE)
+       {
+               if (empty($key))
+                       return $_SESSION;
+
+               $result = isset($_SESSION[$key]) ? $_SESSION[$key] : Kohana::key_string($_SESSION, $key);
+
+               return ($result === NULL) ? $default : $result;
+       }
+
+       /**
+        * Get a variable, and delete it.
+        *
+        * @param   string  variable key
+        * @param   mixed   default value returned if variable does not exist
+        * @return  mixed
+        */
+       public function get_once($key, $default = FALSE)
+       {
+               $return = Session::get($key, $default);
+               Session::delete($key);
+
+               return $return;
+       }
+
+       /**
+        * Delete one or more variables.
+        *
+        * @param   string  variable key(s)
+        * @return  void
+        */
+       public function delete($keys)
+       {
+               $args = func_get_args();
+
+               foreach ($args as $key)
+               {
+                       if (isset(Session::$protect[$key]))
+                               continue;
+
+                       // Unset the key
+                       unset($_SESSION[$key]);
+               }
+       }
+
+} // End Session Class
diff --git a/Server/system/libraries/Tagcloud.php b/Server/system/libraries/Tagcloud.php
new file mode 100644 (file)
index 0000000..8fd7bfc
--- /dev/null
@@ -0,0 +1,130 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * [Tag cloud][ref-tcl] creation library.
+ *
+ * [ref-tcl]: http://en.wikipedia.org/wiki/Tag_cloud
+ *
+ * $Id: Tagcloud.php 3824 2008-12-20 17:13:06Z samsoir $
+ *
+ * @package    Tagcloud
+ * @author     Kohana Team
+ * @copyright  (c) 2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Tagcloud_Core {
+
+       /**
+        * Creates a new Tagcloud instance and returns it.
+        *
+        * @chainable
+        * @param   array    elements of the tagcloud
+        * @param   integer  minimum font size
+        * @param   integer  maximum font size
+        * @return  Tagcloud
+        */
+       public static function factory(array $elements, $min_size = NULL, $max_size = NULL, $shuffle = FALSE)
+       {
+               return new Tagcloud($elements, $min_size, $max_size, $shuffle);
+       }
+
+       public $min_size   = 80;
+       public $max_size   = 140;
+       public $attributes = array('class' => 'tag');
+       public $shuffle    = FALSE;
+
+       // Tag elements, biggest and smallest values
+       protected $elements;
+       protected $biggest;
+       protected $smallest;
+
+       /**
+        * Construct a new tagcloud. The elements must be passed in as an array,
+        * with each entry in the array having a "title" ,"link", and "count" key.
+        * Font sizes will be applied via the "style" attribute as a percentage.
+        *
+        * @param   array    elements of the tagcloud
+        * @param   integer  minimum font size
+        * @param   integer  maximum font size
+        * @return  void
+        */
+       public function __construct(array $elements, $min_size = NULL, $max_size = NULL, $shuffle = FALSE)
+       {
+               $this->elements = $elements;
+               
+               if($shuffle !== FALSE)
+               {
+                       $this->shuffle = TRUE;
+               }
+
+               $counts = array();
+               foreach ($elements as $data)
+               {
+                       $counts[] = $data['count'];
+               }
+
+               // Find the biggest and smallest values of the elements
+               $this->biggest  = max($counts);
+               $this->smallest = min($counts);
+
+               if ($min_size !== NULL)
+               {
+                       $this->min_size = $min_size;
+               }
+
+               if ($max_size !== NULL)
+               {
+                       $this->max_size = $max_size;
+               }
+       }
+
+       /**
+        * Magic __toString method. Returns all of the links as a single string.
+        *
+        * @return  string
+        */
+       public function __toString()
+       {
+               return implode("\n", $this->render());
+       }
+
+       /**
+        * Renders the elements of the tagcloud into an array of links.
+        *
+        * @return  array
+        */
+       public function render()
+       {
+               if ($this->shuffle === TRUE)
+               {
+                       shuffle($this->elements);
+               }
+
+               // Minimum values must be 1 to prevent divide by zero errors
+               $range = max($this->biggest  - $this->smallest, 1);
+               $scale = max($this->max_size - $this->min_size, 1);
+
+               // Import the attributes locally to prevent overwrites
+               $attr = $this->attributes;
+
+               $output = array();
+               foreach ($this->elements as $data)
+               {
+                       if (strpos($data['title'], ' ') !== FALSE)
+                       {
+                               // Replace spaces with non-breaking spaces to prevent line wrapping
+                               // in the middle of a link
+                               $data['title'] = str_replace(' ', '&nbsp;', $data['title']);
+                       }
+
+                       // Determine the size based on the min/max scale and the smallest/biggest range
+                       $size = ((($data['count'] - $this->smallest) * $scale) / $range) + $this->min_size;
+
+                       $attr['style'] = 'font-size: '.round($size, 0).'%';
+
+                       $output[] = html::anchor($data['link'], $data['title'], $attr)."\n";
+               }
+
+               return $output;
+       }
+
+} // End Tagcloud
\ No newline at end of file
diff --git a/Server/system/libraries/URI.php b/Server/system/libraries/URI.php
new file mode 100644 (file)
index 0000000..d9ccdcf
--- /dev/null
@@ -0,0 +1,279 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * URI library.
+ *
+ * $Id: URI.php 4072 2009-03-13 17:20:38Z jheathco $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class URI_Core extends Router {
+
+       /**
+        * Returns a singleton instance of URI.
+        *
+        * @return  object
+        */
+       public static function instance()
+       {
+               static $instance;
+
+               if ($instance == NULL)
+               {
+                       // Initialize the URI instance
+                       $instance = new URI;
+               }
+
+               return $instance;
+       }
+
+       /**
+        * Retrieve a specific URI segment.
+        *
+        * @param   integer|string  segment number or label
+        * @param   mixed           default value returned if segment does not exist
+        * @return  string
+        */
+       public function segment($index = 1, $default = FALSE)
+       {
+               if (is_string($index))
+               {
+                       if (($key = array_search($index, URI::$segments)) === FALSE)
+                               return $default;
+
+                       $index = $key + 2;
+               }
+
+               $index = (int) $index - 1;
+
+               return isset(URI::$segments[$index]) ? URI::$segments[$index] : $default;
+       }
+
+       /**
+        * Retrieve a specific routed URI segment.
+        *
+        * @param   integer|string  rsegment number or label
+        * @param   mixed           default value returned if segment does not exist
+        * @return  string
+        */
+       public function rsegment($index = 1, $default = FALSE)
+       {
+               if (is_string($index))
+               {
+                       if (($key = array_search($index, URI::$rsegments)) === FALSE)
+                               return $default;
+
+                       $index = $key + 2;
+               }
+
+               $index = (int) $index - 1;
+
+               return isset(URI::$rsegments[$index]) ? URI::$rsegments[$index] : $default;
+       }
+
+       /**
+        * Retrieve a specific URI argument.
+        * This is the part of the segments that does not indicate controller or method
+        *
+        * @param   integer|string  argument number or label
+        * @param   mixed           default value returned if segment does not exist
+        * @return  string
+        */
+       public function argument($index = 1, $default = FALSE)
+       {
+               if (is_string($index))
+               {
+                       if (($key = array_search($index, URI::$arguments)) === FALSE)
+                               return $default;
+
+                       $index = $key + 2;
+               }
+
+               $index = (int) $index - 1;
+
+               return isset(URI::$arguments[$index]) ? URI::$arguments[$index] : $default;
+       }
+
+       /**
+        * Returns an array containing all the URI segments.
+        *
+        * @param   integer  segment offset
+        * @param   boolean  return an associative array
+        * @return  array
+        */
+       public function segment_array($offset = 0, $associative = FALSE)
+       {
+               return $this->build_array(URI::$segments, $offset, $associative);
+       }
+
+       /**
+        * Returns an array containing all the re-routed URI segments.
+        *
+        * @param   integer  rsegment offset
+        * @param   boolean  return an associative array
+        * @return  array
+        */
+       public function rsegment_array($offset = 0, $associative = FALSE)
+       {
+               return $this->build_array(URI::$rsegments, $offset, $associative);
+       }
+
+       /**
+        * Returns an array containing all the URI arguments.
+        *
+        * @param   integer  segment offset
+        * @param   boolean  return an associative array
+        * @return  array
+        */
+       public function argument_array($offset = 0, $associative = FALSE)
+       {
+               return $this->build_array(URI::$arguments, $offset, $associative);
+       }
+
+       /**
+        * Creates a simple or associative array from an array and an offset.
+        * Used as a helper for (r)segment_array and argument_array.
+        *
+        * @param   array    array to rebuild
+        * @param   integer  offset to start from
+        * @param   boolean  create an associative array
+        * @return  array
+        */
+       public function build_array($array, $offset = 0, $associative = FALSE)
+       {
+               // Prevent the keys from being improperly indexed
+               array_unshift($array, 0);
+
+               // Slice the array, preserving the keys
+               $array = array_slice($array, $offset + 1, count($array) - 1, TRUE);
+
+               if ($associative === FALSE)
+                       return $array;
+
+               $associative = array();
+               $pairs       = array_chunk($array, 2);
+
+               foreach ($pairs as $pair)
+               {
+                       // Add the key/value pair to the associative array
+                       $associative[$pair[0]] = isset($pair[1]) ? $pair[1] : '';
+               }
+
+               return $associative;
+       }
+
+       /**
+        * Returns the complete URI as a string.
+        *
+        * @return  string
+        */
+       public function string()
+       {
+               return URI::$current_uri;
+       }
+
+       /**
+        * Magic method for converting an object to a string.
+        *
+        * @return  string
+        */
+       public function __toString()
+       {
+               return URI::$current_uri;
+       }
+
+       /**
+        * Returns the total number of URI segments.
+        *
+        * @return  integer
+        */
+       public function total_segments()
+       {
+               return count(URI::$segments);
+       }
+
+       /**
+        * Returns the total number of re-routed URI segments.
+        *
+        * @return  integer
+        */
+       public function total_rsegments()
+       {
+               return count(URI::$rsegments);
+       }
+
+       /**
+        * Returns the total number of URI arguments.
+        *
+        * @return  integer
+        */
+       public function total_arguments()
+       {
+               return count(URI::$arguments);
+       }
+
+       /**
+        * Returns the last URI segment.
+        *
+        * @param   mixed   default value returned if segment does not exist
+        * @return  string
+        */
+       public function last_segment($default = FALSE)
+       {
+               if (($end = $this->total_segments()) < 1)
+                       return $default;
+
+               return URI::$segments[$end - 1];
+       }
+
+       /**
+        * Returns the last re-routed URI segment.
+        *
+        * @param   mixed   default value returned if segment does not exist
+        * @return  string
+        */
+       public function last_rsegment($default = FALSE)
+       {
+               if (($end = $this->total_segments()) < 1)
+                       return $default;
+
+               return URI::$rsegments[$end - 1];
+       }
+
+       /**
+        * Returns the path to the current controller (not including the actual
+        * controller), as a web path.
+        *
+        * @param   boolean  return a full url, or only the path specifically
+        * @return  string
+        */
+       public function controller_path($full = TRUE)
+       {
+               return ($full) ? url::site(URI::$controller_path) : URI::$controller_path;
+       }
+
+       /**
+        * Returns the current controller, as a web path.
+        *
+        * @param   boolean  return a full url, or only the controller specifically
+        * @return  string
+        */
+       public function controller($full = TRUE)
+       {
+               return ($full) ? url::site(URI::$controller_path.URI::$controller) : URI::$controller;
+       }
+
+       /**
+        * Returns the current method, as a web path.
+        *
+        * @param   boolean  return a full url, or only the method specifically
+        * @return  string
+        */
+       public function method($full = TRUE)
+       {
+               return ($full) ? url::site(URI::$controller_path.URI::$controller.'/'.URI::$method) : URI::$method;
+       }
+
+} // End URI Class
diff --git a/Server/system/libraries/Validation.php b/Server/system/libraries/Validation.php
new file mode 100644 (file)
index 0000000..5a48bfc
--- /dev/null
@@ -0,0 +1,826 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Validation library.
+ *
+ * $Id: Validation.php 4120 2009-03-25 19:22:31Z jheathco $
+ *
+ * @package    Validation
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Validation_Core extends ArrayObject {
+
+       // Filters
+       protected $pre_filters = array();
+       protected $post_filters = array();
+
+       // Rules and callbacks
+       protected $rules = array();
+       protected $callbacks = array();
+
+       // Rules that are allowed to run on empty fields
+       protected $empty_rules = array('required', 'matches');
+
+       // Errors
+       protected $errors = array();
+       protected $messages = array();
+
+       // Fields that are expected to be arrays
+       protected $array_fields = array();
+
+       // Checks if there is data to validate.
+       protected $submitted;
+
+       /**
+        * Creates a new Validation instance.
+        *
+        * @param   array   array to use for validation
+        * @return  object
+        */
+       public static function factory(array $array)
+       {
+               return new Validation($array);
+       }
+
+       /**
+        * Sets the unique "any field" key and creates an ArrayObject from the
+        * passed array.
+        *
+        * @param   array   array to validate
+        * @return  void
+        */
+       public function __construct(array $array)
+       {
+               // The array is submitted if the array is not empty
+               $this->submitted = ! empty($array);
+
+               parent::__construct($array, ArrayObject::ARRAY_AS_PROPS | ArrayObject::STD_PROP_LIST);
+       }
+
+       /**
+        * Magic clone method, clears errors and messages.
+        *
+        * @return  void
+        */
+       public function __clone()
+       {
+               $this->errors = array();
+               $this->messages = array();
+       }
+
+       /**
+        * Create a copy of the current validation rules and change the array.
+        *
+        * @chainable
+        * @param   array  new array to validate
+        * @return  Validation
+        */
+       public function copy(array $array)
+       {
+               $copy = clone $this;
+
+               $copy->exchangeArray($array);
+
+               return $copy;
+       }
+
+       /**
+        * Test if the data has been submitted.
+        *
+        * @return  boolean
+        */
+       public function submitted($value = NULL)
+       {
+               if (is_bool($value))
+               {
+                       $this->submitted = $value;
+               }
+
+               return $this->submitted;
+       }
+
+       /**
+        * Returns an array of all the field names that have filters, rules, or callbacks.
+        *
+        * @return  array
+        */
+       public function field_names()
+       {
+               // All the fields that are being validated
+               $fields = array_keys(array_merge
+               (
+                       $this->pre_filters,
+                       $this->rules,
+                       $this->callbacks,
+                       $this->post_filters
+               ));
+
+               // Remove wildcard fields
+               $fields = array_diff($fields, array('*'));
+
+               return $fields;
+       }
+
+       /**
+        * Returns the array values of the current object.
+        *
+        * @return  array
+        */
+       public function as_array()
+       {
+               return $this->getArrayCopy();
+       }
+
+       /**
+        * Returns the ArrayObject values, removing all inputs without rules.
+        * To choose specific inputs, list the field name as arguments.
+        *
+        * @param   boolean  return only fields with filters, rules, and callbacks
+        * @return  array
+        */
+       public function safe_array()
+       {
+               // Load choices
+               $choices = func_get_args();
+               $choices = empty($choices) ? NULL : array_combine($choices, $choices);
+
+               // Get field names
+               $fields = $this->field_names();
+
+               $safe = array();
+               foreach ($fields as $field)
+               {
+                       if ($choices === NULL OR isset($choices[$field]))
+                       {
+                               if (isset($this[$field]))
+                               {
+                                       $value = $this[$field];
+
+                                       if (is_object($value))
+                                       {
+                                               // Convert the value back into an array
+                                               $value = $value->getArrayCopy();
+                                       }
+                               }
+                               else
+                               {
+                                       // Even if the field is not in this array, it must be set
+                                       $value = NULL;
+                               }
+
+                               // Add the field to the array
+                               $safe[$field] = $value;
+                       }
+               }
+
+               return $safe;
+       }
+
+       /**
+        * Add additional rules that will forced, even for empty fields. All arguments
+        * passed will be appended to the list.
+        *
+        * @chainable
+        * @param   string   rule name
+        * @return  object
+        */
+       public function allow_empty_rules($rules)
+       {
+               // Any number of args are supported
+               $rules = func_get_args();
+
+               // Merge the allowed rules
+               $this->empty_rules = array_merge($this->empty_rules, $rules);
+
+               return $this;
+       }
+
+       /**
+        * Converts a filter, rule, or callback into a fully-qualified callback array.
+        *
+        * @return  mixed
+        */
+       protected function callback($callback)
+       {
+               if (is_string($callback))
+               {
+                       if (strpos($callback, '::') !== FALSE)
+                       {
+                               $callback = explode('::', $callback);
+                       }
+                       elseif (function_exists($callback))
+                       {
+                               // No need to check if the callback is a method
+                               $callback = $callback;
+                       }
+                       elseif (method_exists($this, $callback))
+                       {
+                               // The callback exists in Validation
+                               $callback = array($this, $callback);
+                       }
+                       elseif (method_exists('valid', $callback))
+                       {
+                               // The callback exists in valid::
+                               $callback = array('valid', $callback);
+                       }
+               }
+
+               if ( ! is_callable($callback, FALSE))
+               {
+                       if (is_array($callback))
+                       {
+                               if (is_object($callback[0]))
+                               {
+                                       // Object instance syntax
+                                       $name = get_class($callback[0]).'->'.$callback[1];
+                               }
+                               else
+                               {
+                                       // Static class syntax
+                                       $name = $callback[0].'::'.$callback[1];
+                               }
+                       }
+                       else
+                       {
+                               // Function syntax
+                               $name = $callback;
+                       }
+
+                       throw new Kohana_Exception('validation.not_callable', $name);
+               }
+
+               return $callback;
+       }
+
+       /**
+        * Add a pre-filter to one or more inputs. Pre-filters are applied before
+        * rules or callbacks are executed.
+        *
+        * @chainable
+        * @param   callback  filter
+        * @param   string    fields to apply filter to, use TRUE for all fields
+        * @return  object
+        */
+       public function pre_filter($filter, $field = TRUE)
+       {
+               if ($field === TRUE OR $field === '*')
+               {
+                       // Use wildcard
+                       $fields = array('*');
+               }
+               else
+               {
+                       // Add the filter to specific inputs
+                       $fields = func_get_args();
+                       $fields = array_slice($fields, 1);
+               }
+
+               // Convert to a proper callback
+               $filter = $this->callback($filter);
+
+               foreach ($fields as $field)
+               {
+                       // Add the filter to specified field
+                       $this->pre_filters[$field][] = $filter;
+               }
+
+               return $this;
+       }
+
+       /**
+        * Add a post-filter to one or more inputs. Post-filters are applied after
+        * rules and callbacks have been executed.
+        *
+        * @chainable
+        * @param   callback  filter
+        * @param   string    fields to apply filter to, use TRUE for all fields
+        * @return  object
+        */
+       public function post_filter($filter, $field = TRUE)
+       {
+               if ($field === TRUE)
+               {
+                       // Use wildcard
+                       $fields = array('*');
+               }
+               else
+               {
+                       // Add the filter to specific inputs
+                       $fields = func_get_args();
+                       $fields = array_slice($fields, 1);
+               }
+
+               // Convert to a proper callback
+               $filter = $this->callback($filter);
+
+               foreach ($fields as $field)
+               {
+                       // Add the filter to specified field
+                       $this->post_filters[$field][] = $filter;
+               }
+
+               return $this;
+       }
+
+       /**
+        * Add rules to a field. Validation rules may only return TRUE or FALSE and
+        * can not manipulate the value of a field.
+        *
+        * @chainable
+        * @param   string    field name
+        * @param   callback  rules (one or more arguments)
+        * @return  object
+        */
+       public function add_rules($field, $rules)
+       {
+               // Get the rules
+               $rules = func_get_args();
+               $rules = array_slice($rules, 1);
+
+               if ($field === TRUE)
+               {
+                       // Use wildcard
+                       $field = '*';
+               }
+
+               foreach ($rules as $rule)
+               {
+                       // Arguments for rule
+                       $args = NULL;
+
+                       if (is_string($rule))
+                       {
+                               if (preg_match('/^([^\[]++)\[(.+)\]$/', $rule, $matches))
+                               {
+                                       // Split the rule into the function and args
+                                       $rule = $matches[1];
+                                       $args = preg_split('/(?<!\\\\),\s*/', $matches[2]);
+
+                                       // Replace escaped comma with comma
+                                       $args = str_replace('\,', ',', $args);
+                               }
+                       }
+
+                       if ($rule === 'is_array')
+                       {
+                               // This field is expected to be an array
+                               $this->array_fields[$field] = $field;
+                       }
+
+                       // Convert to a proper callback
+                       $rule = $this->callback($rule);
+
+                       // Add the rule, with args, to the field
+                       $this->rules[$field][] = array($rule, $args);
+               }
+
+               return $this;
+       }
+
+       /**
+        * Add callbacks to a field. Callbacks must accept the Validation object
+        * and the input name. Callback returns are not processed.
+        *
+        * @chainable
+        * @param   string     field name
+        * @param   callbacks  callbacks (unlimited number)
+        * @return  object
+        */
+       public function add_callbacks($field, $callbacks)
+       {
+               // Get all callbacks as an array
+               $callbacks = func_get_args();
+               $callbacks = array_slice($callbacks, 1);
+
+               if ($field === TRUE)
+               {
+                       // Use wildcard
+                       $field = '*';
+               }
+
+               foreach ($callbacks as $callback)
+               {
+                       // Convert to a proper callback
+                       $callback = $this->callback($callback);
+
+                       // Add the callback to specified field
+                       $this->callbacks[$field][] = $callback;
+               }
+
+               return $this;
+       }
+
+       /**
+        * Validate by processing pre-filters, rules, callbacks, and post-filters.
+        * All fields that have filters, rules, or callbacks will be initialized if
+        * they are undefined. Validation will only be run if there is data already
+        * in the array.
+        *
+        * @param   object  Validation object, used only for recursion
+        * @param   object  name of field for errors
+        * @return  bool
+        */
+       public function validate($object = NULL, $field_name = NULL)
+       {
+               if ($object === NULL)
+               {
+                       // Use the current object
+                       $object = $this;
+               }
+
+               // Get all field names
+               $fields = $this->field_names();
+
+               // Copy the array from the object, to optimize multiple sets
+               $array = $this->getArrayCopy();
+
+               foreach ($fields as $field)
+               {
+                       if ($field === '*')
+                       {
+                               // Ignore wildcard
+                               continue;
+                       }
+
+                       if ( ! isset($array[$field]))
+                       {
+                               if (isset($this->array_fields[$field]))
+                               {
+                                       // This field must be an array
+                                       $array[$field] = array();
+                               }
+                               else
+                               {
+                                       $array[$field] = NULL;
+                               }
+                       }
+               }
+
+               // Swap the array back into the object
+               $this->exchangeArray($array);
+
+               // Get all defined field names
+               $fields = array_keys($array);
+
+               foreach ($this->pre_filters as $field => $callbacks)
+               {
+                       foreach ($callbacks as $callback)
+                       {
+                               if ($field === '*')
+                               {
+                                       foreach ($fields as $f)
+                                       {
+                                               $this[$f] = is_array($this[$f]) ? array_map($callback, $this[$f]) : call_user_func($callback, $this[$f]);
+                                       }
+                               }
+                               else
+                               {
+                                       $this[$field] = is_array($this[$field]) ? array_map($callback, $this[$field]) : call_user_func($callback, $this[$field]);
+                               }
+                       }
+               }
+
+               if ($this->submitted === FALSE)
+                       return FALSE;
+
+               foreach ($this->rules as $field => $callbacks)
+               {
+                       foreach ($callbacks as $callback)
+                       {
+                               // Separate the callback and arguments
+                               list ($callback, $args) = $callback;
+
+                               // Function or method name of the rule
+                               $rule = is_array($callback) ? $callback[1] : $callback;
+
+                               if ($field === '*')
+                               {
+                                       foreach ($fields as $f)
+                                       {
+                                               // Note that continue, instead of break, is used when
+                                               // applying rules using a wildcard, so that all fields
+                                               // will be validated.
+
+                                               if (isset($this->errors[$f]))
+                                               {
+                                                       // Prevent other rules from being evaluated if an error has occurred
+                                                       continue;
+                                               }
+
+                                               if (empty($this[$f]) AND ! in_array($rule, $this->empty_rules))
+                                               {
+                                                       // This rule does not need to be processed on empty fields
+                                                       continue;
+                                               }
+
+                                               if ($args === NULL)
+                                               {
+                                                       if ( ! call_user_func($callback, $this[$f]))
+                                                       {
+                                                               $this->errors[$f] = $rule;
+
+                                                               // Stop validating this field when an error is found
+                                                               continue;
+                                                       }
+                                               }
+                                               else
+                                               {
+                                                       if ( ! call_user_func($callback, $this[$f], $args))
+                                                       {
+                                                               $this->errors[$f] = $rule;
+
+                                                               // Stop validating this field when an error is found
+                                                               continue;
+                                                       }
+                                               }
+                                       }
+                               }
+                               else
+                               {
+                                       if (isset($this->errors[$field]))
+                                       {
+                                               // Prevent other rules from being evaluated if an error has occurred
+                                               break;
+                                       }
+
+                                       if ( ! in_array($rule, $this->empty_rules) AND ! $this->required($this[$field]))
+                                       {
+                                               // This rule does not need to be processed on empty fields
+                                               continue;
+                                       }
+
+                                       if ($args === NULL)
+                                       {
+                                               if ( ! call_user_func($callback, $this[$field]))
+                                               {
+                                                       $this->errors[$field] = $rule;
+
+                                                       // Stop validating this field when an error is found
+                                                       break;
+                                               }
+                                       }
+                                       else
+                                       {
+                                               if ( ! call_user_func($callback, $this[$field], $args))
+                                               {
+                                                       $this->errors[$field] = $rule;
+
+                                                       // Stop validating this field when an error is found
+                                                       break;
+                                               }
+                                       }
+                               }
+                       }
+               }
+
+               foreach ($this->callbacks as $field => $callbacks)
+               {
+                       foreach ($callbacks as $callback)
+                       {
+                               if ($field === '*')
+                               {
+                                       foreach ($fields as $f)
+                                       {
+                                               // Note that continue, instead of break, is used when
+                                               // applying rules using a wildcard, so that all fields
+                                               // will be validated.
+
+                                               if (isset($this->errors[$f]))
+                                               {
+                                                       // Stop validating this field when an error is found
+                                                       continue;
+                                               }
+
+                                               call_user_func($callback, $this, $f);
+                                       }
+                               }
+                               else
+                               {
+                                       if (isset($this->errors[$field]))
+                                       {
+                                               // Stop validating this field when an error is found
+                                               break;
+                                       }
+
+                                       call_user_func($callback, $this, $field);
+                               }
+                       }
+               }
+
+               foreach ($this->post_filters as $field => $callbacks)
+               {
+                       foreach ($callbacks as $callback)
+                       {
+                               if ($field === '*')
+                               {
+                                       foreach ($fields as $f)
+                                       {
+                                               $this[$f] = is_array($this[$f]) ? array_map($callback, $this[$f]) : call_user_func($callback, $this[$f]);
+                                       }
+                               }
+                               else
+                               {
+                                       $this[$field] = is_array($this[$field]) ? array_map($callback, $this[$field]) : call_user_func($callback, $this[$field]);
+                               }
+                       }
+               }
+
+               // Return TRUE if there are no errors
+               return $this->errors === array();
+       }
+
+       /**
+        * Add an error to an input.
+        *
+        * @chainable
+        * @param   string  input name
+        * @param   string  unique error name
+        * @return  object
+        */
+       public function add_error($field, $name)
+       {
+               $this->errors[$field] = $name;
+
+               return $this;
+       }
+
+       /**
+        * Sets or returns the message for an input.
+        *
+        * @chainable
+        * @param   string   input key
+        * @param   string   message to set
+        * @return  string|object
+        */
+       public function message($input = NULL, $message = NULL)
+       {
+               if ($message === NULL)
+               {
+                       if ($input === NULL)
+                       {
+                               $messages = array();
+                               $keys     = array_keys($this->messages);
+
+                               foreach ($keys as $input)
+                               {
+                                       $messages[] = $this->message($input);
+                               }
+
+                               return implode("\n", $messages);
+                       }
+
+                       // Return nothing if no message exists
+                       if (empty($this->messages[$input]))
+                               return '';
+
+                       // Return the HTML message string
+                       return $this->messages[$input];
+               }
+               else
+               {
+                       $this->messages[$input] = $message;
+               }
+
+               return $this;
+       }
+
+       /**
+        * Return the errors array.
+        *
+        * @param   boolean  load errors from a lang file
+        * @return  array
+        */
+       public function errors($file = NULL)
+       {
+               if ($file === NULL)
+               {
+                       return $this->errors;
+               }
+               else
+               {
+
+                       $errors = array();
+                       foreach ($this->errors as $input => $error)
+                       {
+                               // Key for this input error
+                               $key = "$file.$input.$error";
+
+                               if (($errors[$input] = Kohana::lang($key)) === $key)
+                               {
+                                       // Get the default error message
+                                       $errors[$input] = Kohana::lang("$file.$input.default");
+                               }
+                       }
+
+                       return $errors;
+               }
+       }
+
+       /**
+        * Rule: required. Generates an error if the field has an empty value.
+        *
+        * @param   mixed   input value
+        * @return  bool
+        */
+       public function required($str)
+       {
+               if (is_object($str) AND $str instanceof ArrayObject)
+               {
+                       // Get the array from the ArrayObject
+                       $str = $str->getArrayCopy();
+               }
+
+               if (is_array($str))
+               {
+                       return ! empty($str);
+               }
+               else
+               {
+                       return ! ($str === '' OR $str === NULL OR $str === FALSE);
+               }
+       }
+
+       /**
+        * Rule: matches. Generates an error if the field does not match one or more
+        * other fields.
+        *
+        * @param   mixed   input value
+        * @param   array   input names to match against
+        * @return  bool
+        */
+       public function matches($str, array $inputs)
+       {
+               foreach ($inputs as $key)
+               {
+                       if ($str !== (isset($this[$key]) ? $this[$key] : NULL))
+                               return FALSE;
+               }
+
+               return TRUE;
+       }
+
+       /**
+        * Rule: length. Generates an error if the field is too long or too short.
+        *
+        * @param   mixed   input value
+        * @param   array   minimum, maximum, or exact length to match
+        * @return  bool
+        */
+       public function length($str, array $length)
+       {
+               if ( ! is_string($str))
+                       return FALSE;
+
+               $size = utf8::strlen($str);
+               $status = FALSE;
+
+               if (count($length) > 1)
+               {
+                       list ($min, $max) = $length;
+
+                       if ($size >= $min AND $size <= $max)
+                       {
+                               $status = TRUE;
+                       }
+               }
+               else
+               {
+                       $status = ($size === (int) $length[0]);
+               }
+
+               return $status;
+       }
+
+       /**
+        * Rule: depends_on. Generates an error if the field does not depend on one
+        * or more other fields.
+        *
+        * @param   mixed   field name
+        * @param   array   field names to check dependency
+        * @return  bool
+        */
+       public function depends_on($field, array $fields)
+       {
+               foreach ($fields as $depends_on)
+               {
+                       if ( ! isset($this[$depends_on]) OR $this[$depends_on] == NULL)
+                               return FALSE;
+               }
+
+               return TRUE;
+       }
+
+       /**
+        * Rule: chars. Generates an error if the field contains characters outside of the list.
+        *
+        * @param   string  field value
+        * @param   array   allowed characters
+        * @return  bool
+        */
+       public function chars($value, array $chars)
+       {
+               return ! preg_match('![^'.implode('', $chars).']!u', $value);
+       }
+
+} // End Validation
diff --git a/Server/system/libraries/View.php b/Server/system/libraries/View.php
new file mode 100644 (file)
index 0000000..2b8471c
--- /dev/null
@@ -0,0 +1,309 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Loads and displays Kohana view files. Can also handle output of some binary
+ * files, such as image, Javascript, and CSS files.
+ *
+ * $Id: View.php 4072 2009-03-13 17:20:38Z jheathco $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class View_Core {
+
+       // The view file name and type
+       protected $kohana_filename = FALSE;
+       protected $kohana_filetype = FALSE;
+
+       // View variable storage
+       protected $kohana_local_data = array();
+       protected static $kohana_global_data = array();
+
+       /**
+        * Creates a new View using the given parameters.
+        *
+        * @param   string  view name
+        * @param   array   pre-load data
+        * @param   string  type of file: html, css, js, etc.
+        * @return  object
+        */
+       public static function factory($name = NULL, $data = NULL, $type = NULL)
+       {
+               return new View($name, $data, $type);
+       }
+
+       /**
+        * Attempts to load a view and pre-load view data.
+        *
+        * @throws  Kohana_Exception  if the requested view cannot be found
+        * @param   string  view name
+        * @param   array   pre-load data
+        * @param   string  type of file: html, css, js, etc.
+        * @return  void
+        */
+       public function __construct($name = NULL, $data = NULL, $type = NULL)
+       {
+               if (is_string($name) AND $name !== '')
+               {
+                       // Set the filename
+                       $this->set_filename($name, $type);
+               }
+
+               if (is_array($data) AND ! empty($data))
+               {
+                       // Preload data using array_merge, to allow user extensions
+                       $this->kohana_local_data = array_merge($this->kohana_local_data, $data);
+               }
+       }
+
+       /**
+        * Magic method access to test for view property
+        *
+        * @param   string   View property to test for
+        * @return  boolean
+        */
+       public function __isset($key = NULL)
+       {
+               return $this->is_set($key);
+       }
+
+       /**
+        * Sets the view filename.
+        *
+        * @chainable
+        * @param   string  view filename
+        * @param   string  view file type
+        * @return  object
+        */
+       public function set_filename($name, $type = NULL)
+       {
+               if ($type == NULL)
+               {
+                       // Load the filename and set the content type
+                       $this->kohana_filename = Kohana::find_file('views', $name, TRUE);
+                       $this->kohana_filetype = EXT;
+               }
+               else
+               {
+                       // Check if the filetype is allowed by the configuration
+                       if ( ! in_array($type, Kohana::config('view.allowed_filetypes')))
+                               throw new Kohana_Exception('core.invalid_filetype', $type);
+
+                       // Load the filename and set the content type
+                       $this->kohana_filename = Kohana::find_file('views', $name, TRUE, $type);
+                       $this->kohana_filetype = Kohana::config('mimes.'.$type);
+
+                       if ($this->kohana_filetype == NULL)
+                       {
+                               // Use the specified type
+                               $this->kohana_filetype = $type;
+                       }
+               }
+
+               return $this;
+       }
+
+       /**
+        * Sets a view variable.
+        *
+        * @param   string|array  name of variable or an array of variables
+        * @param   mixed         value when using a named variable
+        * @return  object
+        */
+       public function set($name, $value = NULL)
+       {
+               if (is_array($name))
+               {
+                       foreach ($name as $key => $value)
+                       {
+                               $this->__set($key, $value);
+                       }
+               }
+               else
+               {
+                       $this->__set($name, $value);
+               }
+
+               return $this;
+       }
+
+       /**
+        * Checks for a property existence in the view locally or globally. Unlike the built in __isset(),
+        * this method can take an array of properties to test simultaneously.
+        *
+        * @param string $key property name to test for
+        * @param array $key array of property names to test for
+        * @return boolean property test result
+        * @return array associative array of keys and boolean test result
+        */
+       public function is_set( $key = FALSE )
+       {
+               // Setup result;
+               $result = FALSE;
+
+               // If key is an array
+               if (is_array($key))
+               {
+                       // Set the result to an array
+                       $result = array();
+
+                       // Foreach key
+                       foreach ($key as $property)
+                       {
+                               // Set the result to an associative array
+                               $result[$property] = (array_key_exists($property, $this->kohana_local_data) OR array_key_exists($property, View::$kohana_global_data)) ? TRUE : FALSE;
+                       }
+               }
+               else
+               {
+                       // Otherwise just check one property
+                       $result = (array_key_exists($key, $this->kohana_local_data) OR array_key_exists($key, View::$kohana_global_data)) ? TRUE : FALSE;
+               }
+
+               // Return the result
+               return $result;
+       }
+
+       /**
+        * Sets a bound variable by reference.
+        *
+        * @param   string   name of variable
+        * @param   mixed    variable to assign by reference
+        * @return  object
+        */
+       public function bind($name, & $var)
+       {
+               $this->kohana_local_data[$name] =& $var;
+
+               return $this;
+       }
+
+       /**
+        * Sets a view global variable.
+        *
+        * @param   string|array  name of variable or an array of variables
+        * @param   mixed         value when using a named variable
+        * @return  void
+        */
+       public static function set_global($name, $value = NULL)
+       {
+               if (is_array($name))
+               {
+                       foreach ($name as $key => $value)
+                       {
+                               View::$kohana_global_data[$key] = $value;
+                       }
+               }
+               else
+               {
+                       View::$kohana_global_data[$name] = $value;
+               }
+       }
+
+       /**
+        * Magically sets a view variable.
+        *
+        * @param   string   variable key
+        * @param   string   variable value
+        * @return  void
+        */
+       public function __set($key, $value)
+       {
+               $this->kohana_local_data[$key] = $value;
+       }
+
+       /**
+        * Magically gets a view variable.
+        *
+        * @param  string  variable key
+        * @return mixed   variable value if the key is found
+        * @return void    if the key is not found
+        */
+       public function &__get($key)
+       {
+               if (isset($this->kohana_local_data[$key]))
+                       return $this->kohana_local_data[$key];
+
+               if (isset(View::$kohana_global_data[$key]))
+                       return View::$kohana_global_data[$key];
+
+               if (isset($this->$key))
+                       return $this->$key;
+       }
+
+       /**
+        * Magically converts view object to string.
+        *
+        * @return  string
+        */
+       public function __toString()
+       {
+               try
+               {
+                       return $this->render();
+               }
+               catch (Exception $e)
+               {
+                       // Display the exception using its internal __toString method
+                       return (string) $e;
+               }
+       }
+
+       /**
+        * Renders a view.
+        *
+        * @param   boolean   set to TRUE to echo the output instead of returning it
+        * @param   callback  special renderer to pass the output through
+        * @return  string    if print is FALSE
+        * @return  void      if print is TRUE
+        */
+       public function render($print = FALSE, $renderer = FALSE)
+       {
+               if (empty($this->kohana_filename))
+                       throw new Kohana_Exception('core.view_set_filename');
+
+               if (is_string($this->kohana_filetype))
+               {
+                       // Merge global and local data, local overrides global with the same name
+                       $data = array_merge(View::$kohana_global_data, $this->kohana_local_data);
+
+                       // Load the view in the controller for access to $this
+                       $output = Kohana::$instance->_kohana_load_view($this->kohana_filename, $data);
+
+                       if ($renderer !== FALSE AND is_callable($renderer, TRUE))
+                       {
+                               // Pass the output through the user defined renderer
+                               $output = call_user_func($renderer, $output);
+                       }
+
+                       if ($print === TRUE)
+                       {
+                               // Display the output
+                               echo $output;
+                               return;
+                       }
+               }
+               else
+               {
+                       // Set the content type and size
+                       header('Content-Type: '.$this->kohana_filetype[0]);
+
+                       if ($print === TRUE)
+                       {
+                               if ($file = fopen($this->kohana_filename, 'rb'))
+                               {
+                                       // Display the output
+                                       fpassthru($file);
+                                       fclose($file);
+                               }
+                               return;
+                       }
+
+                       // Fetch the file contents
+                       $output = file_get_contents($this->kohana_filename);
+               }
+
+               return $output;
+       }
+} // End View
\ No newline at end of file
diff --git a/Server/system/libraries/drivers/Cache.php b/Server/system/libraries/drivers/Cache.php
new file mode 100644 (file)
index 0000000..7c5e3c3
--- /dev/null
@@ -0,0 +1,40 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Cache driver interface.
+ *
+ * $Id: Cache.php 4046 2009-03-05 19:23:29Z Shadowhand $
+ *
+ * @package    Cache
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+interface Cache_Driver {
+
+       /**
+        * Set a cache item.
+        */
+       public function set($id, $data, array $tags = NULL, $lifetime);
+
+       /**
+        * Find all of the cache ids for a given tag.
+        */
+       public function find($tag);
+
+       /**
+        * Get a cache item.
+        * Return NULL if the cache item is not found.
+        */
+       public function get($id);
+
+       /**
+        * Delete cache items by id or tag.
+        */
+       public function delete($id, $tag = FALSE);
+
+       /**
+        * Deletes all expired cache items.
+        */
+       public function delete_expired();
+
+} // End Cache Driver
\ No newline at end of file
diff --git a/Server/system/libraries/drivers/Cache/Apc.php b/Server/system/libraries/drivers/Cache/Apc.php
new file mode 100644 (file)
index 0000000..f7be048
--- /dev/null
@@ -0,0 +1,64 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * APC-based Cache driver.
+ *
+ * $Id: Apc.php 4046 2009-03-05 19:23:29Z Shadowhand $
+ *
+ * @package    Cache
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Cache_Apc_Driver implements Cache_Driver {
+
+       public function __construct()
+       {
+               if ( ! extension_loaded('apc'))
+                       throw new Kohana_Exception('cache.extension_not_loaded', 'apc');
+       }
+
+       public function get($id)
+       {
+               return (($return = apc_fetch($id)) === FALSE) ? NULL : $return;
+       }
+
+       public function set($id, $data, array $tags = NULL, $lifetime)
+       {
+               if ( ! empty($tags))
+               {
+                       Kohana::log('error', 'Cache: tags are unsupported by the APC driver');
+               }
+
+               return apc_store($id, $data, $lifetime);
+       }
+
+       public function find($tag)
+       {
+               Kohana::log('error', 'Cache: tags are unsupported by the APC driver');
+
+               return array();
+       }
+
+       public function delete($id, $tag = FALSE)
+       {
+               if ($tag === TRUE)
+               {
+                       Kohana::log('error', 'Cache: tags are unsupported by the APC driver');
+                       return FALSE;
+               }
+               elseif ($id === TRUE)
+               {
+                       return apc_clear_cache('user');
+               }
+               else
+               {
+                       return apc_delete($id);
+               }
+       }
+
+       public function delete_expired()
+       {
+               return TRUE;
+       }
+
+} // End Cache APC Driver
\ No newline at end of file
diff --git a/Server/system/libraries/drivers/Cache/Eaccelerator.php b/Server/system/libraries/drivers/Cache/Eaccelerator.php
new file mode 100644 (file)
index 0000000..a45616d
--- /dev/null
@@ -0,0 +1,66 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Eaccelerator-based Cache driver.
+ *
+ * $Id: Eaccelerator.php 4046 2009-03-05 19:23:29Z Shadowhand $
+ *
+ * @package    Cache
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Cache_Eaccelerator_Driver implements Cache_Driver {
+
+       public function __construct()
+       {
+               if ( ! extension_loaded('eaccelerator'))
+                       throw new Kohana_Exception('cache.extension_not_loaded', 'eaccelerator');
+       }
+
+       public function get($id)
+       {
+               return eaccelerator_get($id);
+       }
+
+       public function find($tag)
+       {
+               Kohana::log('error', 'tags are unsupported by the eAccelerator driver');
+
+               return array();
+       }
+
+       public function set($id, $data, array $tags = NULL, $lifetime)
+       {
+               if ( ! empty($tags))
+               {
+                       Kohana::log('error', 'tags are unsupported by the eAccelerator driver');
+               }
+
+               return eaccelerator_put($id, $data, $lifetime);
+       }
+
+       public function delete($id, $tag = FALSE)
+       {
+               if ($tag === TRUE)
+               {
+                       Kohana::log('error', 'tags are unsupported by the eAccelerator driver');
+                       return FALSE;
+               }
+               elseif ($id === TRUE)
+               {
+                       return eaccelerator_clean();
+               }
+               else
+               {
+                       return eaccelerator_rm($id);
+               }
+       }
+
+       public function delete_expired()
+       {
+               eaccelerator_gc();
+
+               return TRUE;
+       }
+
+} // End Cache eAccelerator Driver
\ No newline at end of file
diff --git a/Server/system/libraries/drivers/Cache/File.php b/Server/system/libraries/drivers/Cache/File.php
new file mode 100644 (file)
index 0000000..cc9d48d
--- /dev/null
@@ -0,0 +1,261 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * File-based Cache driver.
+ *
+ * $Id: File.php 4046 2009-03-05 19:23:29Z Shadowhand $
+ *
+ * @package    Cache
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Cache_File_Driver implements Cache_Driver {
+
+       protected $directory = '';
+
+       /**
+        * Tests that the storage location is a directory and is writable.
+        */
+       public function __construct($directory)
+       {
+               // Find the real path to the directory
+               $directory = str_replace('\\', '/', realpath($directory)).'/';
+
+               // Make sure the cache directory is writable
+               if ( ! is_dir($directory) OR ! is_writable($directory))
+                       throw new Kohana_Exception('cache.unwritable', $directory);
+
+               // Directory is valid
+               $this->directory = $directory;
+       }
+
+       /**
+        * Finds an array of files matching the given id or tag.
+        *
+        * @param  string  cache id or tag
+        * @param  bool    search for tags
+        * @return array   of filenames matching the id or tag
+        */
+       public function exists($id, $tag = FALSE)
+       {
+               if ($id === TRUE)
+               {
+                       // Find all the files
+                       return glob($this->directory.'*~*~*');
+               }
+               elseif ($tag === TRUE)
+               {
+                       // Find all the files that have the tag name
+                       $paths = glob($this->directory.'*~*'.$id.'*~*');
+
+                       // Find all tags matching the given tag
+                       $files = array();
+                       foreach ($paths as $path)
+                       {
+                               // Split the files
+                               $tags = explode('~', basename($path));
+
+                               // Find valid tags
+                               if (count($tags) !== 3 OR empty($tags[1]))
+                                       continue;
+
+                               // Split the tags by plus signs, used to separate tags
+                               $tags = explode('+', $tags[1]);
+
+                               if (in_array($tag, $tags))
+                               {
+                                       // Add the file to the array, it has the requested tag
+                                       $files[] = $path;
+                               }
+                       }
+
+                       return $files;
+               }
+               else
+               {
+                       // Find the file matching the given id
+                       return glob($this->directory.$id.'~*');
+               }
+       }
+
+       /**
+        * Sets a cache item to the given data, tags, and lifetime.
+        *
+        * @param   string   cache id to set
+        * @param   string   data in the cache
+        * @param   array    cache tags
+        * @param   integer  lifetime
+        * @return  bool
+        */
+       public function set($id, $data, array $tags = NULL, $lifetime)
+       {
+               // Remove old cache files
+               $this->delete($id);
+
+               // Cache File driver expects unix timestamp
+               if ($lifetime !== 0)
+               {
+                       $lifetime += time();
+               }
+
+               if ( ! empty($tags))
+               {
+                       // Convert the tags into a string list
+                       $tags = implode('+', $tags);
+               }
+
+               // Write out a serialized cache
+               return (bool) file_put_contents($this->directory.$id.'~'.$tags.'~'.$lifetime, serialize($data));
+       }
+
+       /**
+        * Finds an array of ids for a given tag.
+        *
+        * @param  string  tag name
+        * @return array   of ids that match the tag
+        */
+       public function find($tag)
+       {
+               // An array will always be returned
+               $result = array();
+
+               if ($paths = $this->exists($tag, TRUE))
+               {
+                       // Length of directory name
+                       $offset = strlen($this->directory);
+
+                       // Find all the files with the given tag
+                       foreach ($paths as $path)
+                       {
+                               // Get the id from the filename
+                               list($id, $junk) = explode('~', basename($path), 2);
+
+                               if (($data = $this->get($id)) !== FALSE)
+                               {
+                                       // Add the result to the array
+                                       $result[$id] = $data;
+                               }
+                       }
+               }
+
+               return $result;
+       }
+
+       /**
+        * Fetches a cache item. This will delete the item if it is expired or if
+        * the hash does not match the stored hash.
+        *
+        * @param   string  cache id
+        * @return  mixed|NULL
+        */
+       public function get($id)
+       {
+               if ($file = $this->exists($id))
+               {
+                       // Use the first file
+                       $file = current($file);
+
+                       // Validate that the cache has not expired
+                       if ($this->expired($file))
+                       {
+                               // Remove this cache, it has expired
+                               $this->delete($id);
+                       }
+                       else
+                       {
+                               // Turn off errors while reading the file
+                               $ER = error_reporting(0);
+
+                               if (($data = file_get_contents($file)) !== FALSE)
+                               {
+                                       // Unserialize the data
+                                       $data = unserialize($data);
+                               }
+                               else
+                               {
+                                       // Delete the data
+                                       unset($data);
+                               }
+
+                               // Turn errors back on
+                               error_reporting($ER);
+                       }
+               }
+
+               // Return NULL if there is no data
+               return isset($data) ? $data : NULL;
+       }
+
+       /**
+        * Deletes a cache item by id or tag
+        *
+        * @param   string   cache id or tag, or TRUE for "all items"
+        * @param   boolean  use tags
+        * @return  boolean
+        */
+       public function delete($id, $tag = FALSE)
+       {
+               $files = $this->exists($id, $tag);
+
+               if (empty($files))
+                       return FALSE;
+
+               // Disable all error reporting while deleting
+               $ER = error_reporting(0);
+
+               foreach ($files as $file)
+               {
+                       // Remove the cache file
+                       if ( ! unlink($file))
+                               Kohana::log('error', 'Cache: Unable to delete cache file: '.$file);
+               }
+
+               // Turn on error reporting again
+               error_reporting($ER);
+
+               return TRUE;
+       }
+
+       /**
+        * Deletes all cache files that are older than the current time.
+        *
+        * @return void
+        */
+       public function delete_expired()
+       {
+               if ($files = $this->exists(TRUE))
+               {
+                       // Disable all error reporting while deleting
+                       $ER = error_reporting(0);
+
+                       foreach ($files as $file)
+                       {
+                               if ($this->expired($file))
+                               {
+                                       // The cache file has already expired, delete it
+                                       if ( ! unlink($file))
+                                               Kohana::log('error', 'Cache: Unable to delete cache file: '.$file);
+                               }
+                       }
+
+                       // Turn on error reporting again
+                       error_reporting($ER);
+               }
+       }
+
+       /**
+        * Check if a cache file has expired by filename.
+        *
+        * @param  string  filename
+        * @return bool
+        */
+       protected function expired($file)
+       {
+               // Get the expiration time
+               $expires = (int) substr($file, strrpos($file, '~') + 1);
+
+               // Expirations of 0 are "never expire"
+               return ($expires !== 0 AND $expires <= time());
+       }
+
+} // End Cache File Driver
\ No newline at end of file
diff --git a/Server/system/libraries/drivers/Cache/Memcache.php b/Server/system/libraries/drivers/Cache/Memcache.php
new file mode 100644 (file)
index 0000000..d801de9
--- /dev/null
@@ -0,0 +1,191 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Memcache-based Cache driver.
+ *
+ * $Id: Memcache.php 4102 2009-03-19 12:55:54Z Shadowhand $
+ *
+ * @package    Cache
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Cache_Memcache_Driver implements Cache_Driver {
+
+       const TAGS_KEY = 'memcache_tags_array';
+
+       // Cache backend object and flags
+       protected $backend;
+       protected $flags;
+
+       // Tags array
+       protected static $tags;
+
+       // Have the tags been changed?
+       protected static $tags_changed = FALSE;
+
+       public function __construct()
+       {
+               if ( ! extension_loaded('memcache'))
+                       throw new Kohana_Exception('cache.extension_not_loaded', 'memcache');
+
+               $this->backend = new Memcache;
+               $this->flags = Kohana::config('cache_memcache.compression') ? MEMCACHE_COMPRESSED : FALSE;
+
+               $servers = Kohana::config('cache_memcache.servers');
+
+               foreach ($servers as $server)
+               {
+                       // Make sure all required keys are set
+                       $server += array('host' => '127.0.0.1', 'port' => 11211, 'persistent' => FALSE);
+
+                       // Add the server to the pool
+                       $this->backend->addServer($server['host'], $server['port'], (bool) $server['persistent'])
+                               or Kohana::log('error', 'Cache: Connection failed: '.$server['host']);
+               }
+
+               // Load tags
+               self::$tags = $this->backend->get(self::TAGS_KEY);
+
+               if ( ! is_array(self::$tags))
+               {
+                       // Create a new tags array
+                       self::$tags = array();
+
+                       // Tags have been created
+                       self::$tags_changed = TRUE;
+               }
+       }
+
+       public function __destruct()
+       {
+               if (self::$tags_changed === TRUE)
+               {
+                       // Save the tags
+                       $this->backend->set(self::TAGS_KEY, self::$tags, $this->flags, 0);
+
+                       // Tags are now unchanged
+                       self::$tags_changed = FALSE;
+               }
+       }
+
+       public function find($tag)
+       {
+               if (isset(self::$tags[$tag]) AND $results = $this->backend->get(self::$tags[$tag]))
+               {
+                               // Return all the found caches
+                               return $results;
+               }
+               else
+               {
+                       // No matching tags
+                       return array();
+               }
+       }
+
+       public function get($id)
+       {
+               return (($return = $this->backend->get($id)) === FALSE) ? NULL : $return;
+       }
+
+       public function set($id, $data, array $tags = NULL, $lifetime)
+       {
+               if ( ! empty($tags))
+               {
+                       // Tags will be changed
+                       self::$tags_changed = TRUE;
+
+                       foreach ($tags as $tag)
+                       {
+                               // Add the id to each tag
+                               self::$tags[$tag][$id] = $id;
+                       }
+               }
+
+               if ($lifetime !== 0)
+               {
+                       // Memcache driver expects unix timestamp
+                       $lifetime += time();
+               }
+
+               // Set a new value
+               return $this->backend->set($id, $data, $this->flags, $lifetime);
+       }
+
+       public function delete($id, $tag = FALSE)
+       {
+               // Tags will be changed
+               self::$tags_changed = TRUE;
+
+               if ($id === TRUE)
+               {
+                       if ($status = $this->backend->flush())
+                       {
+                               // Remove all tags, all items have been deleted
+                               self::$tags = array();
+
+                               // We must sleep after flushing, or overwriting will not work!
+                               // @see http://php.net/manual/en/function.memcache-flush.php#81420
+                               sleep(1);
+                       }
+
+                       return $status;
+               }
+               elseif ($tag === TRUE)
+               {
+                       if (isset(self::$tags[$id]))
+                       {
+                               foreach (self::$tags[$id] as $_id)
+                               {
+                                       // Delete each id in the tag
+                                       $this->backend->delete($_id);
+                               }
+
+                               // Delete the tag
+                               unset(self::$tags[$id]);
+                       }
+
+                       return TRUE;
+               }
+               else
+               {
+                       foreach (self::$tags as $tag => $_ids)
+                       {
+                               if (isset(self::$tags[$tag][$id]))
+                               {
+                                       // Remove the id from the tags
+                                       unset(self::$tags[$tag][$id]);
+                               }
+                       }
+
+                       return $this->backend->delete($id);
+               }
+       }
+
+       public function delete_expired()
+       {
+               // Tags will be changed
+               self::$tags_changed = TRUE;
+
+               foreach (self::$tags as $tag => $_ids)
+               {
+                       foreach ($_ids as $id)
+                       {
+                               if ( ! $this->backend->get($id))
+                               {
+                                       // This id has disappeared, delete it from the tags
+                                       unset(self::$tags[$tag][$id]);
+                               }
+                       }
+
+                       if (empty(self::$tags[$tag]))
+                       {
+                               // The tag no longer has any valid ids
+                               unset(self::$tags[$tag]);
+                       }
+               }
+
+               // Memcache handles garbage collection internally
+               return TRUE;
+       }
+
+} // End Cache Memcache Driver
diff --git a/Server/system/libraries/drivers/Cache/Sqlite.php b/Server/system/libraries/drivers/Cache/Sqlite.php
new file mode 100644 (file)
index 0000000..9458d85
--- /dev/null
@@ -0,0 +1,257 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * SQLite-based Cache driver.
+ *
+ * $Id: Sqlite.php 4046 2009-03-05 19:23:29Z Shadowhand $
+ *
+ * @package    Cache
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Cache_Sqlite_Driver implements Cache_Driver {
+
+       // SQLite database instance
+       protected $db;
+
+       // Database error messages
+       protected $error;
+
+       /**
+        * Logs an SQLite error.
+        */
+       protected static function log_error($code)
+       {
+               // Log an error
+               Kohana::log('error', 'Cache: SQLite error: '.sqlite_error_string($error));
+       }
+
+       /**
+        * Tests that the storage location is a directory and is writable.
+        */
+       public function __construct($filename)
+       {
+               // Get the directory name
+               $directory = str_replace('\\', '/', realpath(pathinfo($filename, PATHINFO_DIRNAME))).'/';
+
+               // Set the filename from the real directory path
+               $filename = $directory.basename($filename);
+
+               // Make sure the cache directory is writable
+               if ( ! is_dir($directory) OR ! is_writable($directory))
+                       throw new Kohana_Exception('cache.unwritable', $directory);
+
+               // Make sure the cache database is writable
+               if (is_file($filename) AND ! is_writable($filename))
+                       throw new Kohana_Exception('cache.unwritable', $filename);
+
+               // Open up an instance of the database
+               $this->db = new SQLiteDatabase($filename, '0666', $error);
+
+               // Throw an exception if there's an error
+               if ( ! empty($error))
+                       throw new Kohana_Exception('cache.driver_error', sqlite_error_string($error));
+
+               $query  = "SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'caches'";
+               $tables = $this->db->query($query, SQLITE_BOTH, $error);
+
+               // Throw an exception if there's an error
+               if ( ! empty($error))
+                       throw new Kohana_Exception('cache.driver_error', sqlite_error_string($error));
+
+               if ($tables->numRows() == 0)
+               {
+                       Kohana::log('error', 'Cache: Initializing new SQLite cache database');
+
+                       // Issue a CREATE TABLE command
+                       $this->db->unbufferedQuery(Kohana::config('cache_sqlite.schema'));
+               }
+       }
+
+       /**
+        * Checks if a cache id is already set.
+        *
+        * @param  string   cache id
+        * @return boolean
+        */
+       public function exists($id)
+       {
+               // Find the id that matches
+               $query = "SELECT id FROM caches WHERE id = '$id'";
+
+               return ($this->db->query($query)->numRows() > 0);
+       }
+
+       /**
+        * Sets a cache item to the given data, tags, and lifetime.
+        *
+        * @param   string   cache id to set
+        * @param   string   data in the cache
+        * @param   array    cache tags
+        * @param   integer  lifetime
+        * @return  bool
+        */
+       public function set($id, $data, array $tags = NULL, $lifetime)
+       {
+               // Serialize and escape the data
+               $data = sqlite_escape_string(serialize($data));
+
+               if ( ! empty($tags))
+               {
+                       // Escape the tags, adding brackets so the tag can be explicitly matched
+                       $tags = sqlite_escape_string('<'.implode('>,<', $tags).'>');
+               }
+
+               // Cache Sqlite driver expects unix timestamp
+               if ($lifetime !== 0)
+               {
+                       $lifetime += time();
+               }
+
+               $query = $this->exists($id)
+                       ? "UPDATE caches SET tags = '$tags', expiration = '$lifetime', cache = '$data' WHERE id = '$id'"
+                       : "INSERT INTO caches VALUES('$id', '$tags', '$lifetime', '$data')";
+
+               // Run the query
+               $this->db->unbufferedQuery($query, SQLITE_BOTH, $error);
+
+               if ( ! empty($error))
+               {
+                       self::log_error($error);
+                       return FALSE;
+               }
+               else
+               {
+                       return TRUE;
+               }
+       }
+
+       /**
+        * Finds an array of ids for a given tag.
+        *
+        * @param  string  tag name
+        * @return array   of ids that match the tag
+        */
+       public function find($tag)
+       {
+               $query = "SELECT id,cache FROM caches WHERE tags LIKE '%<{$tag}>%'";
+               $query = $this->db->query($query, SQLITE_BOTH, $error);
+
+               // An array will always be returned
+               $result = array();
+
+               if ( ! empty($error))
+               {
+                       self::log_error($error);
+               }
+               elseif ($query->numRows() > 0)
+               {
+                       // Disable notices for unserializing
+                       $ER = error_reporting(~E_NOTICE);
+
+                       while ($row = $query->fetchObject())
+                       {
+                               // Add each cache to the array
+                               $result[$row->id] = unserialize($row->cache);
+                       }
+
+                       // Turn notices back on
+                       error_reporting($ER);
+               }
+
+               return $result;
+       }
+
+       /**
+        * Fetches a cache item. This will delete the item if it is expired or if
+        * the hash does not match the stored hash.
+        *
+        * @param  string  cache id
+        * @return mixed|NULL
+        */
+       public function get($id)
+       {
+               $query = "SELECT id, expiration, cache FROM caches WHERE id = '$id' LIMIT 0, 1";
+               $query = $this->db->query($query, SQLITE_BOTH, $error);
+
+               if ( ! empty($error))
+               {
+                       self::log_error($error);
+               }
+               elseif ($cache = $query->fetchObject())
+               {
+                       // Make sure the expiration is valid and that the hash matches
+                       if ($cache->expiration != 0 AND $cache->expiration <= time())
+                       {
+                               // Cache is not valid, delete it now
+                               $this->delete($cache->id);
+                       }
+                       else
+                       {
+                               // Disable notices for unserializing
+                               $ER = error_reporting(~E_NOTICE);
+                               
+                               // Return the valid cache data
+                               $data = $cache->cache;
+
+                               // Turn notices back on
+                               error_reporting($ER);
+                       }
+               }
+
+               // No valid cache found
+               return NULL;
+       }
+
+       /**
+        * Deletes a cache item by id or tag
+        *
+        * @param  string  cache id or tag, or TRUE for "all items"
+        * @param  bool    delete a tag
+        * @return bool
+        */
+       public function delete($id, $tag = FALSE)
+       {
+               if ($id === TRUE)
+               {
+                       // Delete all caches
+                       $where = '1';
+               }
+               elseif ($tag === TRUE)
+               {
+                       // Delete by tag
+                       $where = "tags LIKE '%<{$id}>%'";
+               }
+               else
+               {
+                       // Delete by id
+                       $where = "id = '$id'";
+               }
+
+               $this->db->unbufferedQuery('DELETE FROM caches WHERE '.$where, SQLITE_BOTH, $error);
+
+               if ( ! empty($error))
+               {
+                       self::log_error($error);
+                       return FALSE;
+               }
+               else
+               {
+                       return TRUE;
+               }
+       }
+
+       /**
+        * Deletes all cache files that are older than the current time.
+        */
+       public function delete_expired()
+       {
+               // Delete all expired caches
+               $query = 'DELETE FROM caches WHERE expiration != 0 AND expiration <= '.time();
+
+               $this->db->unbufferedQuery($query);
+
+               return TRUE;
+       }
+
+} // End Cache SQLite Driver
\ No newline at end of file
diff --git a/Server/system/libraries/drivers/Cache/Xcache.php b/Server/system/libraries/drivers/Cache/Xcache.php
new file mode 100644 (file)
index 0000000..6254bbb
--- /dev/null
@@ -0,0 +1,119 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Xcache Cache driver.
+ *
+ * $Id: Xcache.php 4046 2009-03-05 19:23:29Z Shadowhand $
+ *
+ * @package    Cache
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Cache_Xcache_Driver implements Cache_Driver {
+
+       public function __construct()
+       {
+               if ( ! extension_loaded('xcache'))
+                       throw new Kohana_Exception('cache.extension_not_loaded', 'xcache');
+       }
+
+       public function get($id)
+       {
+               if (xcache_isset($id))
+                       return xcache_get($id);
+
+               return NULL;
+       }
+
+       public function set($id, $data, array $tags = NULL, $lifetime)
+       {
+               if ( ! empty($tags))
+               {
+                       Kohana::log('error', 'Cache: tags are unsupported by the Xcache driver');
+               }
+
+               return xcache_set($id, $data, $lifetime);
+       }
+
+       public function find($tag)
+       {
+               Kohana::log('error', 'Cache: tags are unsupported by the Xcache driver');
+               return FALSE;
+       }
+
+       public function delete($id, $tag = FALSE)
+       {
+               if ($tag !== FALSE)
+               {
+                       Kohana::log('error', 'Cache: tags are unsupported by the Xcache driver');
+                       return TRUE;
+               }
+               elseif ($id !== TRUE)
+               {
+                       if (xcache_isset($id))
+                               return xcache_unset($id);
+
+                       return FALSE;
+               }
+               else
+               {
+                       // Do the login
+                       $this->auth();
+                       $result = TRUE;
+                       for ($i = 0, $max = xcache_count(XC_TYPE_VAR); $i < $max; $i++)
+                       {
+                               if (xcache_clear_cache(XC_TYPE_VAR, $i) !== NULL)
+                               {
+                                       $result = FALSE;
+                                       break;
+                               }
+                       }
+
+                       // Undo the login
+                       $this->auth(TRUE);
+                       return $result;
+               }
+
+               return TRUE;
+       }
+
+       public function delete_expired()
+       {
+               return TRUE;
+       }
+
+       private function auth($reverse = FALSE)
+       {
+               static $backup = array();
+
+               $keys = array('PHP_AUTH_USER', 'PHP_AUTH_PW');
+
+               foreach ($keys as $key)
+               {
+                       if ($reverse)
+                       {
+                               if (isset($backup[$key]))
+                               {
+                                       $_SERVER[$key] = $backup[$key];
+                                       unset($backup[$key]);
+                               }
+                               else
+                               {
+                                       unset($_SERVER[$key]);
+                               }
+                       }
+                       else
+                       {
+                               $value = getenv($key);
+
+                               if ( ! empty($value))
+                               {
+                                       $backup[$key] = $value;
+                               }
+
+                               $_SERVER[$key] = Kohana::config('cache_xcache.'.$key);
+                       }
+               }
+       }
+
+} // End Cache Xcache Driver
diff --git a/Server/system/libraries/drivers/Captcha.php b/Server/system/libraries/drivers/Captcha.php
new file mode 100644 (file)
index 0000000..a4343e1
--- /dev/null
@@ -0,0 +1,227 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Captcha driver class.
+ *
+ * $Id: Captcha.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package    Captcha
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+abstract class Captcha_Driver {
+
+       // The correct Captcha challenge answer
+       protected $response;
+
+       // Image resource identifier and type ("png", "gif" or "jpeg")
+       protected $image;
+       protected $image_type = 'png';
+
+       /**
+        * Constructs a new challenge.
+        *
+        * @return  void
+        */
+       public function __construct()
+       {
+               // Generate a new challenge
+               $this->response = $this->generate_challenge();
+
+               // Store the correct Captcha response in a session
+               Event::add('system.post_controller', array($this, 'update_response_session'));
+       }
+
+       /**
+        * Generate a new Captcha challenge.
+        *
+        * @return  string  the challenge answer
+        */
+       abstract public function generate_challenge();
+
+       /**
+        * Output the Captcha challenge.
+        *
+        * @param   boolean  html output
+        * @return  mixed    the rendered Captcha (e.g. an image, riddle, etc.)
+        */
+       abstract public function render($html);
+
+       /**
+        * Stores the response for the current Captcha challenge in a session so it is available
+        * on the next page load for Captcha::valid(). This method is called after controller
+        * execution (in the system.post_controller event) in order not to overwrite itself too soon.
+        *
+        * @return  void
+        */
+       public function update_response_session()
+       {
+               Session::instance()->set('captcha_response', sha1(strtoupper($this->response)));
+       }
+
+       /**
+        * Validates a Captcha response from a user.
+        *
+        * @param   string   captcha response
+        * @return  boolean
+        */
+       public function valid($response)
+       {
+               return (sha1(strtoupper($response)) === Session::instance()->get('captcha_response'));
+       }
+
+       /**
+        * Returns the image type.
+        *
+        * @param   string        filename
+        * @return  string|FALSE  image type ("png", "gif" or "jpeg")
+        */
+       public function image_type($filename)
+       {
+               switch (strtolower(substr(strrchr($filename, '.'), 1)))
+               {
+                       case 'png':
+                               return 'png';
+
+                       case 'gif':
+                               return 'gif';
+
+                       case 'jpg':
+                       case 'jpeg':
+                               // Return "jpeg" and not "jpg" because of the GD2 function names
+                               return 'jpeg';
+
+                       default:
+                               return FALSE;
+               }
+       }
+
+       /**
+        * Creates an image resource with the dimensions specified in config.
+        * If a background image is supplied, the image dimensions are used.
+        *
+        * @throws  Kohana_Exception  if no GD2 support
+        * @param   string  path to the background image file
+        * @return  void
+        */
+       public function image_create($background = NULL)
+       {
+               // Check for GD2 support
+               if ( ! function_exists('imagegd2'))
+                       throw new Kohana_Exception('captcha.requires_GD2');
+
+               // Create a new image (black)
+               $this->image = imagecreatetruecolor(Captcha::$config['width'], Captcha::$config['height']);
+
+               // Use a background image
+               if ( ! empty($background))
+               {
+                       // Create the image using the right function for the filetype
+                       $function = 'imagecreatefrom'.$this->image_type($background);
+                       $this->background_image = $function($background);
+
+                       // Resize the image if needed
+                       if (imagesx($this->background_image) !== Captcha::$config['width']
+                           OR imagesy($this->background_image) !== Captcha::$config['height'])
+                       {
+                               imagecopyresampled
+                               (
+                                       $this->image, $this->background_image, 0, 0, 0, 0,
+                                       Captcha::$config['width'], Captcha::$config['height'],
+                                       imagesx($this->background_image), imagesy($this->background_image)
+                               );
+                       }
+
+                       // Free up resources
+                       imagedestroy($this->background_image);
+               }
+       }
+
+       /**
+        * Fills the background with a gradient.
+        *
+        * @param   resource  gd image color identifier for start color
+        * @param   resource  gd image color identifier for end color
+        * @param   string    direction: 'horizontal' or 'vertical', 'random' by default
+        * @return  void
+        */
+       public function image_gradient($color1, $color2, $direction = NULL)
+       {
+               $directions = array('horizontal', 'vertical');
+
+               // Pick a random direction if needed
+               if ( ! in_array($direction, $directions))
+               {
+                       $direction = $directions[array_rand($directions)];
+
+                       // Switch colors
+                       if (mt_rand(0, 1) === 1)
+                       {
+                               $temp = $color1;
+                               $color1 = $color2;
+                               $color2 = $temp;
+                       }
+               }
+
+               // Extract RGB values
+               $color1 = imagecolorsforindex($this->image, $color1);
+               $color2 = imagecolorsforindex($this->image, $color2);
+
+               // Preparations for the gradient loop
+               $steps = ($direction === 'horizontal') ? Captcha::$config['width'] : Captcha::$config['height'];
+
+               $r1 = ($color1['red'] - $color2['red']) / $steps;
+               $g1 = ($color1['green'] - $color2['green']) / $steps;
+               $b1 = ($color1['blue'] - $color2['blue']) / $steps;
+
+               if ($direction === 'horizontal')
+               {
+                       $x1 =& $i;
+                       $y1 = 0;
+                       $x2 =& $i;
+                       $y2 = Captcha::$config['height'];
+               }
+               else
+               {
+                       $x1 = 0;
+                       $y1 =& $i;
+                       $x2 = Captcha::$config['width'];
+                       $y2 =& $i;
+               }
+
+               // Execute the gradient loop
+               for ($i = 0; $i <= $steps; $i++)
+               {
+                       $r2 = $color1['red'] - floor($i * $r1);
+                       $g2 = $color1['green'] - floor($i * $g1);
+                       $b2 = $color1['blue'] - floor($i * $b1);
+                       $color = imagecolorallocate($this->image, $r2, $g2, $b2);
+
+                       imageline($this->image, $x1, $y1, $x2, $y2, $color);
+               }
+       }
+
+       /**
+        * Returns the img html element or outputs the image to the browser.
+        *
+        * @param   boolean  html output
+        * @return  mixed    html string or void
+        */
+       public function image_render($html)
+       {
+               // Output html element
+               if ($html)
+                       return '<img alt="Captcha" src="'.url::site('captcha/'.Captcha::$config['group']).'" width="'.Captcha::$config['width'].'" height="'.Captcha::$config['height'].'" />';
+
+               // Send the correct HTTP header
+               header('Content-Type: image/'.$this->image_type);
+
+               // Pick the correct output function
+               $function = 'image'.$this->image_type;
+               $function($this->image);
+
+               // Free up resources
+               imagedestroy($this->image);
+       }
+
+} // End Captcha Driver
\ No newline at end of file
diff --git a/Server/system/libraries/drivers/Captcha/Alpha.php b/Server/system/libraries/drivers/Captcha/Alpha.php
new file mode 100644 (file)
index 0000000..2779580
--- /dev/null
@@ -0,0 +1,92 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Captcha driver for "alpha" style.
+ *
+ * $Id: Alpha.php 4367 2009-05-27 21:23:57Z samsoir $
+ *
+ * @package    Captcha
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Captcha_Alpha_Driver extends Captcha_Driver {
+
+       /**
+        * Generates a new Captcha challenge.
+        *
+        * @return  string  the challenge answer
+        */
+       public function generate_challenge()
+       {
+               // Complexity setting is used as character count
+               return text::random('distinct', max(1, Captcha::$config['complexity']));
+       }
+
+       /**
+        * Outputs the Captcha image.
+        *
+        * @param   boolean  html output
+        * @return  mixed
+        */
+       public function render($html)
+       {
+               // Creates $this->image
+               $this->image_create(Captcha::$config['background']);
+
+               // Add a random gradient
+               if (empty(Captcha::$config['background']))
+               {
+                       $color1 = imagecolorallocate($this->image, mt_rand(0, 100), mt_rand(0, 100), mt_rand(0, 100));
+                       $color2 = imagecolorallocate($this->image, mt_rand(0, 100), mt_rand(0, 100), mt_rand(0, 100));
+                       $this->image_gradient($color1, $color2);
+               }
+
+               // Add a few random circles
+               for ($i = 0, $count = mt_rand(10, Captcha::$config['complexity'] * 3); $i < $count; $i++)
+               {
+                       $color = imagecolorallocatealpha($this->image, mt_rand(0, 255), mt_rand(0, 255), mt_rand(0, 255), mt_rand(80, 120));
+                       $size = mt_rand(5, Captcha::$config['height'] / 3);
+                       imagefilledellipse($this->image, mt_rand(0, Captcha::$config['width']), mt_rand(0, Captcha::$config['height']), $size, $size, $color);
+               }
+
+               // Calculate character font-size and spacing
+               $default_size = min(Captcha::$config['width'], Captcha::$config['height'] * 2) / strlen($this->response);
+               $spacing = (int) (Captcha::$config['width'] * 0.9 / strlen($this->response));
+
+               // Background alphabetic character attributes
+               $color_limit = mt_rand(96, 160);
+               $chars = 'ABEFGJKLPQRTVY';
+
+               // Draw each Captcha character with varying attributes
+               for ($i = 0, $strlen = strlen($this->response); $i < $strlen; $i++)
+               {
+                       // Use different fonts if available
+                       $font = Captcha::$config['fontpath'].Captcha::$config['fonts'][array_rand(Captcha::$config['fonts'])];
+
+                       $angle = mt_rand(-40, 20);
+                       // Scale the character size on image height
+                       $size = $default_size / 10 * mt_rand(8, 12);
+                       $box = imageftbbox($size, $angle, $font, $this->response[$i]);
+
+                       // Calculate character starting coordinates
+                       $x = $spacing / 4 + $i * $spacing;
+                       $y = Captcha::$config['height'] / 2 + ($box[2] - $box[5]) / 4;
+
+                       // Draw captcha text character
+                       // Allocate random color, size and rotation attributes to text
+                       $color = imagecolorallocate($this->image, mt_rand(150, 255), mt_rand(200, 255), mt_rand(0, 255));
+
+                       // Write text character to image
+                       imagefttext($this->image, $size, $angle, $x, $y, $color, $font, $this->response[$i]);
+
+                       // Draw "ghost" alphabetic character
+                       $text_color = imagecolorallocatealpha($this->image, mt_rand($color_limit + 8, 255), mt_rand($color_limit + 8, 255), mt_rand($color_limit + 8, 255), mt_rand(70, 120));
+                       $char = $chars[mt_rand(0, 14)];
+                       imagettftext($this->image, $size * 2, mt_rand(-45, 45), ($x - (mt_rand(5, 10))), ($y + (mt_rand(5, 10))), $text_color, $font, $char);
+               }
+
+               // Output
+               return $this->image_render($html);
+       }
+
+} // End Captcha Alpha Driver Class
\ No newline at end of file
diff --git a/Server/system/libraries/drivers/Captcha/Basic.php b/Server/system/libraries/drivers/Captcha/Basic.php
new file mode 100644 (file)
index 0000000..d212e72
--- /dev/null
@@ -0,0 +1,81 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Captcha driver for "basic" style.
+ *
+ * $Id: Basic.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package    Captcha
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Captcha_Basic_Driver extends Captcha_Driver {
+
+       /**
+        * Generates a new Captcha challenge.
+        *
+        * @return  string  the challenge answer
+        */
+       public function generate_challenge()
+       {
+               // Complexity setting is used as character count
+               return text::random('distinct', max(1, Captcha::$config['complexity']));
+       }
+
+       /**
+        * Outputs the Captcha image.
+        *
+        * @param   boolean  html output
+        * @return  mixed
+        */
+       public function render($html)
+       {
+               // Creates $this->image
+               $this->image_create(Captcha::$config['background']);
+
+               // Add a random gradient
+               if (empty(Captcha::$config['background']))
+               {
+                       $color1 = imagecolorallocate($this->image, mt_rand(200, 255), mt_rand(200, 255), mt_rand(150, 255));
+                       $color2 = imagecolorallocate($this->image, mt_rand(200, 255), mt_rand(200, 255), mt_rand(150, 255));
+                       $this->image_gradient($color1, $color2);
+               }
+
+               // Add a few random lines
+               for ($i = 0, $count = mt_rand(5, Captcha::$config['complexity'] * 4); $i < $count; $i++)
+               {
+                       $color = imagecolorallocatealpha($this->image, mt_rand(0, 255), mt_rand(0, 255), mt_rand(100, 255), mt_rand(50, 120));
+                       imageline($this->image, mt_rand(0, Captcha::$config['width']), 0, mt_rand(0, Captcha::$config['width']), Captcha::$config['height'], $color);
+               }
+
+               // Calculate character font-size and spacing
+               $default_size = min(Captcha::$config['width'], Captcha::$config['height'] * 2) / (strlen($this->response) + 1);
+               $spacing = (int) (Captcha::$config['width'] * 0.9 / strlen($this->response));
+
+               // Draw each Captcha character with varying attributes
+               for ($i = 0, $strlen = strlen($this->response); $i < $strlen; $i++)
+               {
+                       // Use different fonts if available
+                       $font = Captcha::$config['fontpath'].Captcha::$config['fonts'][array_rand(Captcha::$config['fonts'])];
+
+                       // Allocate random color, size and rotation attributes to text
+                       $color = imagecolorallocate($this->image, mt_rand(0, 150), mt_rand(0, 150), mt_rand(0, 150));
+                       $angle = mt_rand(-40, 20);
+
+                       // Scale the character size on image height
+                       $size = $default_size / 10 * mt_rand(8, 12);
+                       $box = imageftbbox($size, $angle, $font, $this->response[$i]);
+
+                       // Calculate character starting coordinates
+                       $x = $spacing / 4 + $i * $spacing;
+                       $y = Captcha::$config['height'] / 2 + ($box[2] - $box[5]) / 4;
+
+                       // Write text character to image
+                       imagefttext($this->image, $size, $angle, $x, $y, $color, $font, $this->response[$i]);
+               }
+
+               // Output
+               return $this->image_render($html);
+       }
+
+} // End Captcha Basic Driver Class
\ No newline at end of file
diff --git a/Server/system/libraries/drivers/Captcha/Black.php b/Server/system/libraries/drivers/Captcha/Black.php
new file mode 100644 (file)
index 0000000..6a2e222
--- /dev/null
@@ -0,0 +1,72 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Captcha driver for "black" style.
+ *
+ * $Id: Black.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package    Captcha
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Captcha_Black_Driver extends Captcha_Driver {
+
+       /**
+        * Generates a new Captcha challenge.
+        *
+        * @return  string  the challenge answer
+        */
+       public function generate_challenge()
+       {
+               // Complexity setting is used as character count
+               return text::random('distinct', max(1, ceil(Captcha::$config['complexity'] / 1.5)));
+       }
+
+       /**
+        * Outputs the Captcha image.
+        *
+        * @param   boolean  html output
+        * @return  mixed
+        */
+       public function render($html)
+       {
+               // Creates a black image to start from
+               $this->image_create(Captcha::$config['background']);
+
+               // Add random white/gray arcs, amount depends on complexity setting
+               $count = (Captcha::$config['width'] + Captcha::$config['height']) / 2;
+               $count = $count / 5 * min(10, Captcha::$config['complexity']);
+               for ($i = 0; $i < $count; $i++)
+               {
+                       imagesetthickness($this->image, mt_rand(1, 2));
+                       $color = imagecolorallocatealpha($this->image, 255, 255, 255, mt_rand(0, 120));
+                       imagearc($this->image, mt_rand(-Captcha::$config['width'], Captcha::$config['width']), mt_rand(-Captcha::$config['height'], Captcha::$config['height']), mt_rand(-Captcha::$config['width'], Captcha::$config['width']), mt_rand(-Captcha::$config['height'], Captcha::$config['height']), mt_rand(0, 360), mt_rand(0, 360), $color);
+               }
+
+               // Use different fonts if available
+               $font = Captcha::$config['fontpath'].Captcha::$config['fonts'][array_rand(Captcha::$config['fonts'])];
+
+               // Draw the character's white shadows
+               $size = (int) min(Captcha::$config['height'] / 2, Captcha::$config['width'] * 0.8 / strlen($this->response));
+               $angle = mt_rand(-15 + strlen($this->response), 15 - strlen($this->response));
+               $x = mt_rand(1, Captcha::$config['width'] * 0.9 - $size * strlen($this->response));
+               $y = ((Captcha::$config['height'] - $size) / 2) + $size;
+               $color = imagecolorallocate($this->image, 255, 255, 255);
+               imagefttext($this->image, $size, $angle, $x + 1, $y + 1, $color, $font, $this->response);
+
+               // Add more shadows for lower complexities
+               (Captcha::$config['complexity'] < 10) and imagefttext($this->image, $size, $angle, $x - 1, $y - 1, $color, $font , $this->response);
+               (Captcha::$config['complexity'] < 8)  and imagefttext($this->image, $size, $angle, $x - 2, $y + 2, $color, $font , $this->response);
+               (Captcha::$config['complexity'] < 6)  and imagefttext($this->image, $size, $angle, $x + 2, $y - 2, $color, $font , $this->response);
+               (Captcha::$config['complexity'] < 4)  and imagefttext($this->image, $size, $angle, $x + 3, $y + 3, $color, $font , $this->response);
+               (Captcha::$config['complexity'] < 2)  and imagefttext($this->image, $size, $angle, $x - 3, $y - 3, $color, $font , $this->response);
+
+               // Finally draw the foreground characters
+               $color = imagecolorallocate($this->image, 0, 0, 0);
+               imagefttext($this->image, $size, $angle, $x, $y, $color, $font, $this->response);
+
+               // Output
+               return $this->image_render($html);
+       }
+
+} // End Captcha Black Driver Class
\ No newline at end of file
diff --git a/Server/system/libraries/drivers/Captcha/Math.php b/Server/system/libraries/drivers/Captcha/Math.php
new file mode 100644 (file)
index 0000000..4ac2024
--- /dev/null
@@ -0,0 +1,61 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Captcha driver for "math" style.
+ *
+ * $Id: Math.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package    Captcha
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Captcha_Math_Driver extends Captcha_Driver {
+
+       private $math_exercice;
+
+       /**
+        * Generates a new Captcha challenge.
+        *
+        * @return  string  the challenge answer
+        */
+       public function generate_challenge()
+       {
+               // Easy
+               if (Captcha::$config['complexity'] < 4)
+               {
+                       $numbers[] = mt_rand(1, 5);
+                       $numbers[] = mt_rand(1, 4);
+               }
+               // Normal
+               elseif (Captcha::$config['complexity'] < 7)
+               {
+                       $numbers[] = mt_rand(10, 20);
+                       $numbers[] = mt_rand(1, 10);
+               }
+               // Difficult, well, not really ;)
+               else
+               {
+                       $numbers[] = mt_rand(100, 200);
+                       $numbers[] = mt_rand(10, 20);
+                       $numbers[] = mt_rand(1, 10);
+               }
+
+               // Store the question for output
+               $this->math_exercice = implode(' + ', $numbers).' = ';
+
+               // Return the answer
+               return array_sum($numbers);
+       }
+
+       /**
+        * Outputs the Captcha riddle.
+        *
+        * @param   boolean  html output
+        * @return  mixed
+        */
+       public function render($html)
+       {
+               return $this->math_exercice;
+       }
+
+} // End Captcha Math Driver Class
\ No newline at end of file
diff --git a/Server/system/libraries/drivers/Captcha/Riddle.php b/Server/system/libraries/drivers/Captcha/Riddle.php
new file mode 100644 (file)
index 0000000..765eeaa
--- /dev/null
@@ -0,0 +1,47 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Captcha driver for "riddle" style.
+ *
+ * $Id: Riddle.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package    Captcha
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Captcha_Riddle_Driver extends Captcha_Driver {
+
+       private $riddle;
+
+       /**
+        * Generates a new Captcha challenge.
+        *
+        * @return  string  the challenge answer
+        */
+       public function generate_challenge()
+       {
+               // Load riddles from the current language
+               $riddles = Kohana::lang('captcha.riddles');
+
+               // Pick a random riddle
+               $riddle = $riddles[array_rand($riddles)];
+
+               // Store the question for output
+               $this->riddle = $riddle[0];
+
+               // Return the answer
+               return $riddle[1];
+       }
+
+       /**
+        * Outputs the Captcha riddle.
+        *
+        * @param   boolean  html output
+        * @return  mixed
+        */
+       public function render($html)
+       {
+               return $this->riddle;
+       }
+
+} // End Captcha Riddle Driver Class
\ No newline at end of file
diff --git a/Server/system/libraries/drivers/Captcha/Word.php b/Server/system/libraries/drivers/Captcha/Word.php
new file mode 100644 (file)
index 0000000..856bd9b
--- /dev/null
@@ -0,0 +1,37 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Captcha driver for "word" style.
+ *
+ * $Id: Word.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package    Captcha
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Captcha_Word_Driver extends Captcha_Basic_Driver {
+
+       /**
+        * Generates a new Captcha challenge.
+        *
+        * @return  string  the challenge answer
+        */
+       public function generate_challenge()
+       {
+               // Load words from the current language and randomize them
+               $words = Kohana::lang('captcha.words');
+               shuffle($words);
+
+               // Loop over each word...
+               foreach ($words as $word)
+               {
+                       // ...until we find one of the desired length
+                       if (abs(Captcha::$config['complexity'] - strlen($word)) < 2)
+                               return strtoupper($word);
+               }
+
+               // Return any random word as final fallback
+               return strtoupper($words[array_rand($words)]);
+       }
+
+} // End Captcha Word Driver Class
\ No newline at end of file
diff --git a/Server/system/libraries/drivers/Database.php b/Server/system/libraries/drivers/Database.php
new file mode 100644 (file)
index 0000000..807469f
--- /dev/null
@@ -0,0 +1,636 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Database API driver
+ *
+ * $Id: Database.php 4343 2009-05-08 17:04:48Z jheathco $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+abstract class Database_Driver {
+
+       protected $query_cache;
+
+       /**
+        * Connect to our database.
+        * Returns FALSE on failure or a MySQL resource.
+        *
+        * @return mixed
+        */
+       abstract public function connect();
+
+       /**
+        * Perform a query based on a manually written query.
+        *
+        * @param  string  SQL query to execute
+        * @return Database_Result
+        */
+       abstract public function query($sql);
+
+       /**
+        * Builds a DELETE query.
+        *
+        * @param   string  table name
+        * @param   array   where clause
+        * @return  string
+        */
+       public function delete($table, $where)
+       {
+               return 'DELETE FROM '.$this->escape_table($table).' WHERE '.implode(' ', $where);
+       }
+
+       /**
+        * Builds an UPDATE query.
+        *
+        * @param   string  table name
+        * @param   array   key => value pairs
+        * @param   array   where clause
+        * @return  string
+        */
+       public function update($table, $values, $where)
+       {
+               foreach ($values as $key => $val)
+               {
+                       $valstr[] = $this->escape_column($key).' = '.$val;
+               }
+               return 'UPDATE '.$this->escape_table($table).' SET '.implode(', ', $valstr).' WHERE '.implode(' ',$where);
+       }
+
+       /**
+        * Set the charset using 'SET NAMES <charset>'.
+        *
+        * @param  string  character set to use
+        */
+       public function set_charset($charset)
+       {
+               throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__);
+       }
+
+       /**
+        * Wrap the tablename in backticks, has support for: table.field syntax.
+        *
+        * @param   string  table name
+        * @return  string
+        */
+       abstract public function escape_table($table);
+
+       /**
+        * Escape a column/field name, has support for special commands.
+        *
+        * @param   string  column name
+        * @return  string
+        */
+       abstract public function escape_column($column);
+
+       /**
+        * Builds a WHERE portion of a query.
+        *
+        * @param   mixed    key
+        * @param   string   value
+        * @param   string   type
+        * @param   int      number of where clauses
+        * @param   boolean  escape the value
+        * @return  string
+        */
+       public function where($key, $value, $type, $num_wheres, $quote)
+       {
+               $prefix = ($num_wheres == 0) ? '' : $type;
+
+               if ($quote === -1)
+               {
+                       $value = '';
+               }
+               else
+               {
+                       if ($value === NULL)
+                       {
+                               if ( ! $this->has_operator($key))
+                               {
+                                       $key .= ' IS';
+                               }
+
+                               $value = ' NULL';
+                       }
+                       elseif (is_bool($value))
+                       {
+                               if ( ! $this->has_operator($key))
+                               {
+                                       $key .= ' =';
+                               }
+
+                               $value = ($value == TRUE) ? ' 1' : ' 0';
+                       }
+                       else
+                       {
+                               if ( ! $this->has_operator($key) AND ! empty($key))
+                               {
+                                       $key = $this->escape_column($key).' =';
+                               }
+                               else
+                               {
+                                       preg_match('/^(.+?)([<>!=]+|\bIS(?:\s+NULL))\s*$/i', $key, $matches);
+                                       if (isset($matches[1]) AND isset($matches[2]))
+                                       {
+                                               $key = $this->escape_column(trim($matches[1])).' '.trim($matches[2]);
+                                       }
+                               }
+
+                               $value = ' '.(($quote == TRUE) ? $this->escape($value) : $value);
+                       }
+               }
+
+               return $prefix.$key.$value;
+       }
+
+       /**
+        * Builds a LIKE portion of a query.
+        *
+        * @param   mixed    field name
+        * @param   string   value to match with field
+        * @param   boolean  add wildcards before and after the match
+        * @param   string   clause type (AND or OR)
+        * @param   int      number of likes
+        * @return  string
+        */
+       public function like($field, $match, $auto, $type, $num_likes)
+       {
+               $prefix = ($num_likes == 0) ? '' : $type;
+
+               $match = $this->escape_str($match);
+
+               if ($auto === TRUE)
+               {
+                       // Add the start and end quotes
+                       $match = '%'.str_replace('%', '\\%', $match).'%';
+               }
+
+               return $prefix.' '.$this->escape_column($field).' LIKE \''.$match . '\'';
+       }
+
+       /**
+        * Builds a NOT LIKE portion of a query.
+        *
+        * @param   mixed   field name
+        * @param   string  value to match with field
+        * @param   string  clause type (AND or OR)
+        * @param   int     number of likes
+        * @return  string
+        */
+       public function notlike($field, $match, $auto, $type, $num_likes)
+       {
+               $prefix = ($num_likes == 0) ? '' : $type;
+
+               $match = $this->escape_str($match);
+
+               if ($auto === TRUE)
+               {
+                       // Add the start and end quotes
+                       $match = '%'.$match.'%';
+               }
+
+               return $prefix.' '.$this->escape_column($field).' NOT LIKE \''.$match.'\'';
+       }
+
+       /**
+        * Builds a REGEX portion of a query.
+        *
+        * @param   string   field name
+        * @param   string   value to match with field
+        * @param   string   clause type (AND or OR)
+        * @param   integer  number of regexes
+        * @return  string
+        */
+       public function regex($field, $match, $type, $num_regexs)
+       {
+               throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__);
+       }
+
+       /**
+        * Builds a NOT REGEX portion of a query.
+        *
+        * @param   string   field name
+        * @param   string   value to match with field
+        * @param   string   clause type (AND or OR)
+        * @param   integer  number of regexes
+        * @return  string
+        */
+       public function notregex($field, $match, $type, $num_regexs)
+       {
+               throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__);
+       }
+
+       /**
+        * Builds an INSERT query.
+        *
+        * @param   string  table name
+        * @param   array   keys
+        * @param   array   values
+        * @return  string
+        */
+       public function insert($table, $keys, $values)
+       {
+               // Escape the column names
+               foreach ($keys as $key => $value)
+               {
+                       $keys[$key] = $this->escape_column($value);
+               }
+               return 'INSERT INTO '.$this->escape_table($table).' ('.implode(', ', $keys).') VALUES ('.implode(', ', $values).')';
+       }
+
+       /**
+        * Builds a MERGE portion of a query.
+        *
+        * @param   string  table name
+        * @param   array   keys
+        * @param   array   values
+        * @return  string
+        */
+       public function merge($table, $keys, $values)
+       {
+               throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__);
+       }
+
+       /**
+        * Builds a LIMIT portion of a query.
+        *
+        * @param   integer  limit
+        * @param   integer  offset
+        * @return  string
+        */
+       abstract public function limit($limit, $offset = 0);
+
+       /**
+        * Creates a prepared statement.
+        *
+        * @param   string  SQL query
+        * @return  Database_Stmt
+        */
+       public function stmt_prepare($sql = '')
+       {
+               throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__);
+       }
+
+       /**
+        *  Compiles the SELECT statement.
+        *  Generates a query string based on which functions were used.
+        *  Should not be called directly, the get() function calls it.
+        *
+        * @param   array   select query values
+        * @return  string
+        */
+       abstract public function compile_select($database);
+
+       /**
+        * Determines if the string has an arithmetic operator in it.
+        *
+        * @param   string   string to check
+        * @return  boolean
+        */
+       public function has_operator($str)
+       {
+               return (bool) preg_match('/[<>!=]|\sIS(?:\s+NOT\s+)?\b|BETWEEN/i', trim($str));
+       }
+
+       /**
+        * Escapes any input value.
+        *
+        * @param   mixed   value to escape
+        * @return  string
+        */
+       public function escape($value)
+       {
+               if ( ! $this->db_config['escape'])
+                       return $value;
+
+               switch (gettype($value))
+               {
+                       case 'string':
+                               $value = '\''.$this->escape_str($value).'\'';
+                       break;
+                       case 'boolean':
+                               $value = (int) $value;
+                       break;
+                       case 'double':
+                               // Convert to non-locale aware float to prevent possible commas
+                               $value = sprintf('%F', $value);
+                       break;
+                       default:
+                               $value = ($value === NULL) ? 'NULL' : $value;
+                       break;
+               }
+
+               return (string) $value;
+       }
+
+       /**
+        * Escapes a string for a query.
+        *
+        * @param   mixed   value to escape
+        * @return  string
+        */
+       abstract public function escape_str($str);
+
+       /**
+        * Lists all tables in the database.
+        *
+        * @return  array
+        */
+       abstract public function list_tables();
+
+       /**
+        * Lists all fields in a table.
+        *
+        * @param   string  table name
+        * @return  array
+        */
+       abstract function list_fields($table);
+
+       /**
+        * Returns the last database error.
+        *
+        * @return  string
+        */
+       abstract public function show_error();
+
+       /**
+        * Returns field data about a table.
+        *
+        * @param   string  table name
+        * @return  array
+        */
+       abstract public function field_data($table);
+
+       /**
+        * Fetches SQL type information about a field, in a generic format.
+        *
+        * @param   string  field datatype
+        * @return  array
+        */
+       protected function sql_type($str)
+       {
+               static $sql_types;
+
+               if ($sql_types === NULL)
+               {
+                       // Load SQL data types
+                       $sql_types = Kohana::config('sql_types');
+               }
+
+               $str = strtolower(trim($str));
+
+               if (($open  = strpos($str, '(')) !== FALSE)
+               {
+                       // Find closing bracket
+                       $close = strpos($str, ')', $open) - 1;
+
+                       // Find the type without the size
+                       $type = substr($str, 0, $open);
+               }
+               else
+               {
+                       // No length
+                       $type = $str;
+               }
+
+               empty($sql_types[$type]) and exit
+               (
+                       'Unknown field type: '.$type.'. '.
+                       'Please report this: http://trac.kohanaphp.com/newticket'
+               );
+
+               // Fetch the field definition
+               $field = $sql_types[$type];
+
+               switch ($field['type'])
+               {
+                       case 'string':
+                       case 'float':
+                               if (isset($close))
+                               {
+                                       // Add the length to the field info
+                                       $field['length'] = substr($str, $open + 1, $close - $open);
+                               }
+                       break;
+                       case 'int':
+                               // Add unsigned value
+                               $field['unsigned'] = (strpos($str, 'unsigned') !== FALSE);
+                       break;
+               }
+
+               return $field;
+       }
+
+       /**
+        * Clears the internal query cache.
+        *
+        * @param  string  SQL query
+        */
+       public function clear_cache($sql = NULL)
+       {
+               if (empty($sql))
+               {
+                       $this->query_cache = array();
+               }
+               else
+               {
+                       unset($this->query_cache[$this->query_hash($sql)]);
+               }
+
+               Kohana::log('debug', 'Database cache cleared: '.get_class($this));
+       }
+
+       /**
+        * Creates a hash for an SQL query string. Replaces newlines with spaces,
+        * trims, and hashes.
+        *
+        * @param   string  SQL query
+        * @return  string
+        */
+       protected function query_hash($sql)
+       {
+               return sha1(str_replace("\n", ' ', trim($sql)));
+       }
+
+} // End Database Driver Interface
+
+/**
+ * Database_Result
+ *
+ */
+abstract class Database_Result implements ArrayAccess, Iterator, Countable {
+
+       // Result resource, insert id, and SQL
+       protected $result;
+       protected $insert_id;
+       protected $sql;
+
+       // Current and total rows
+       protected $current_row = 0;
+       protected $total_rows  = 0;
+
+       // Fetch function and return type
+       protected $fetch_type;
+       protected $return_type;
+
+       /**
+        * Returns the SQL used to fetch the result.
+        *
+        * @return  string
+        */
+       public function sql()
+       {
+               return $this->sql;
+       }
+
+       /**
+        * Returns the insert id from the result.
+        *
+        * @return  mixed
+        */
+       public function insert_id()
+       {
+               return $this->insert_id;
+       }
+
+       /**
+        * Prepares the query result.
+        *
+        * @param   boolean   return rows as objects
+        * @param   mixed     type
+        * @return  Database_Result
+        */
+       abstract function result($object = TRUE, $type = FALSE);
+
+       /**
+        * Builds an array of query results.
+        *
+        * @param   boolean   return rows as objects
+        * @param   mixed     type
+        * @return  array
+        */
+       abstract function result_array($object = NULL, $type = FALSE);
+
+       /**
+        * Gets the fields of an already run query.
+        *
+        * @return  array
+        */
+       abstract public function list_fields();
+
+       /**
+        * Seek to an offset in the results.
+        *
+        * @return  boolean
+        */
+       abstract public function seek($offset);
+
+       /**
+        * Countable: count
+        */
+       public function count()
+       {
+               return $this->total_rows;
+       }
+
+       /**
+        * ArrayAccess: offsetExists
+        */
+       public function offsetExists($offset)
+       {
+               if ($this->total_rows > 0)
+               {
+                       $min = 0;
+                       $max = $this->total_rows - 1;
+
+                       return ! ($offset < $min OR $offset > $max);
+               }
+
+               return FALSE;
+       }
+
+       /**
+        * ArrayAccess: offsetGet
+        */
+       public function offsetGet($offset)
+       {
+               if ( ! $this->seek($offset))
+                       return FALSE;
+
+               // Return the row by calling the defined fetching callback
+               return call_user_func($this->fetch_type, $this->result, $this->return_type);
+       }
+
+       /**
+        * ArrayAccess: offsetSet
+        *
+        * @throws  Kohana_Database_Exception
+        */
+       final public function offsetSet($offset, $value)
+       {
+               throw new Kohana_Database_Exception('database.result_read_only');
+       }
+
+       /**
+        * ArrayAccess: offsetUnset
+        *
+        * @throws  Kohana_Database_Exception
+        */
+       final public function offsetUnset($offset)
+       {
+               throw new Kohana_Database_Exception('database.result_read_only');
+       }
+
+       /**
+        * Iterator: current
+        */
+       public function current()
+       {
+               return $this->offsetGet($this->current_row);
+       }
+
+       /**
+        * Iterator: key
+        */
+       public function key()
+       {
+               return $this->current_row;
+       }
+
+       /**
+        * Iterator: next
+        */
+       public function next()
+       {
+               ++$this->current_row;
+               return $this;
+       }
+
+       /**
+        * Iterator: prev
+        */
+       public function prev()
+       {
+               --$this->current_row;
+               return $this;
+       }
+
+       /**
+        * Iterator: rewind
+        */
+       public function rewind()
+       {
+               $this->current_row = 0;
+               return $this;
+       }
+
+       /**
+        * Iterator: valid
+        */
+       public function valid()
+       {
+               return $this->offsetExists($this->current_row);
+       }
+
+} // End Database Result Interface
diff --git a/Server/system/libraries/drivers/Database/Mssql.php b/Server/system/libraries/drivers/Database/Mssql.php
new file mode 100644 (file)
index 0000000..8b5ed50
--- /dev/null
@@ -0,0 +1,462 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * MSSQL Database Driver
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Database_Mssql_Driver extends Database_Driver
+{
+       /**
+        * Database connection link
+        */
+       protected $link;
+
+       /**
+        * Database configuration
+        */
+       protected $db_config;
+
+       /**
+        * Sets the config for the class.
+        *
+        * @param  array  database configuration
+        */
+       public function __construct($config)
+       {
+               $this->db_config = $config;
+
+               Kohana::log('debug', 'MSSQL Database Driver Initialized');
+       }
+
+       /**
+        * Closes the database connection.
+        */
+       public function __destruct()
+       {
+               is_resource($this->link) and mssql_close($this->link);
+       }
+
+       /**
+        * Make the connection
+        *
+        * @return return connection
+        */
+       public function connect()
+       {
+               // Check if link already exists
+               if (is_resource($this->link))
+                       return $this->link;
+
+               // Import the connect variables
+               extract($this->db_config['connection']);
+
+               // Persistent connections enabled?
+               $connect = ($this->db_config['persistent'] == TRUE) ? 'mssql_pconnect' : 'mssql_connect';
+
+               // Build the connection info
+               $host = isset($host) ? $host : $socket;
+
+               // Windows uses a comma instead of a colon
+               $port = (isset($port) AND is_string($port)) ? (KOHANA_IS_WIN ? ',' : ':').$port : '';
+
+               // Make the connection and select the database
+               if (($this->link = $connect($host.$port, $user, $pass, TRUE)) AND mssql_select_db($database, $this->link))
+               {
+                       /* This is being removed so I can use it, will need to come up with a more elegant workaround in the future...
+                        *
+                       if ($charset = $this->db_config['character_set'])
+                       {
+                               $this->set_charset($charset);
+                       }
+                       */
+
+                       // Clear password after successful connect
+                       $this->db_config['connection']['pass'] = NULL;
+
+                       return $this->link;
+               }
+
+               return FALSE;
+       }
+
+       public function query($sql)
+       {
+               // Only cache if it's turned on, and only cache if it's not a write statement
+               if ($this->db_config['cache'] AND ! preg_match('#\b(?:INSERT|UPDATE|REPLACE|SET)\b#i', $sql))
+               {
+                       $hash = $this->query_hash($sql);
+
+                       if ( ! isset($this->query_cache[$hash]))
+                       {
+                               // Set the cached object
+                               $this->query_cache[$hash] = new Mssql_Result(mssql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql);
+                       }
+                       else
+                       {
+                               // Rewind cached result
+                               $this->query_cache[$hash]->rewind();
+                       }
+
+                       // Return the cached query
+                       return $this->query_cache[$hash];
+               }
+
+               return new Mssql_Result(mssql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql);
+       }
+
+       public function escape_table($table)
+       {
+               if (stripos($table, ' AS ') !== FALSE)
+               {
+                       // Force 'AS' to uppercase
+                       $table = str_ireplace(' AS ', ' AS ', $table);
+
+                       // Runs escape_table on both sides of an AS statement
+                       $table = array_map(array($this, __FUNCTION__), explode(' AS ', $table));
+
+                       // Re-create the AS statement
+                       return implode(' AS ', $table);
+               }
+               return '['.str_replace('.', '[.]', $table).']';
+       }
+
+       public function escape_column($column)
+       {
+               if (!$this->db_config['escape'])
+                       return $column;
+
+               if ($column == '*')
+                       return $column;
+
+               // This matches any functions we support to SELECT.
+               if ( preg_match('/(avg|count|sum|max|min)\(\s*(.*)\s*\)(\s*as\s*(.+)?)?/i', $column, $matches))
+               {
+                       if ( count($matches) == 3)
+                       {
+                               return $matches[1].'('.$this->escape_column($matches[2]).')';
+                       }
+                       else if ( count($matches) == 5)
+                       {
+                               return $matches[1].'('.$this->escape_column($matches[2]).') AS '.$this->escape_column($matches[2]);
+                       }
+               }
+
+               // This matches any modifiers we support to SELECT.
+               if ( ! preg_match('/\b(?:rand|all|distinct(?:row)?|high_priority|sql_(?:small_result|b(?:ig_result|uffer_result)|no_cache|ca(?:che|lc_found_rows)))\s/i', $column))
+               {
+                       if (stripos($column, ' AS ') !== FALSE)
+                       {
+                               // Force 'AS' to uppercase
+                               $column = str_ireplace(' AS ', ' AS ', $column);
+
+                               // Runs escape_column on both sides of an AS statement
+                               $column = array_map(array($this, __FUNCTION__), explode(' AS ', $column));
+
+                               // Re-create the AS statement
+                               return implode(' AS ', $column);
+                       }
+
+                       return preg_replace('/[^.*]+/', '[$0]', $column);
+               }
+
+               $parts = explode(' ', $column);
+               $column = '';
+
+               for ($i = 0, $c = count($parts); $i < $c; $i++)
+               {
+                       // The column is always last
+                       if ($i == ($c - 1))
+                       {
+                               $column .= preg_replace('/[^.*]+/', '[$0]', $parts[$i]);
+                       }
+                       else // otherwise, it's a modifier
+                       {
+                               $column .= $parts[$i].' ';
+                       }
+               }
+               return $column;
+       }
+
+       /**
+        * Limit in SQL Server 2000 only uses the keyword
+        * 'TOP'; 2007 may have an offset keyword, but
+        * I am unsure - for pagination style limit,offset
+        * functionality, a fancy query needs to be built.
+        *
+        * @param unknown_type $limit
+        * @return unknown
+        */
+       public function limit($limit, $offset=null)
+       {
+               return 'TOP '.$limit;
+       }
+
+       public function compile_select($database)
+       {
+               $sql = ($database['distinct'] == TRUE) ? 'SELECT DISTINCT ' : 'SELECT ';
+               $sql .= (count($database['select']) > 0) ? implode(', ', $database['select']) : '*';
+
+               if (count($database['from']) > 0)
+               {
+                       // Escape the tables
+                       $froms = array();
+                       foreach ($database['from'] as $from)
+                               $froms[] = $this->escape_column($from);
+                       $sql .= "\nFROM ";
+                       $sql .= implode(', ', $froms);
+               }
+
+               if (count($database['join']) > 0)
+               {
+                       foreach($database['join'] AS $join)
+                       {
+                               $sql .= "\n".$join['type'].'JOIN '.implode(', ', $join['tables']).' ON '.$join['conditions'];
+                       }
+               }
+
+               if (count($database['where']) > 0)
+               {
+                       $sql .= "\nWHERE ";
+               }
+
+               $sql .= implode("\n", $database['where']);
+
+               if (count($database['groupby']) > 0)
+               {
+                       $sql .= "\nGROUP BY ";
+                       $sql .= implode(', ', $database['groupby']);
+               }
+
+               if (count($database['having']) > 0)
+               {
+                       $sql .= "\nHAVING ";
+                       $sql .= implode("\n", $database['having']);
+               }
+
+               if (count($database['orderby']) > 0)
+               {
+                       $sql .= "\nORDER BY ";
+                       $sql .= implode(', ', $database['orderby']);
+               }
+
+               if (is_numeric($database['limit']))
+               {
+                       $sql .= "\n";
+                       $sql .= $this->limit($database['limit']);
+               }
+
+               return $sql;
+       }
+
+       public function escape_str($str)
+       {
+               if (!$this->db_config['escape'])
+                       return $str;
+
+               is_resource($this->link) or $this->connect();
+               //mssql_real_escape_string($str, $this->link); <-- this function doesn't exist
+
+               $characters = array('/\x00/', '/\x1a/', '/\n/', '/\r/', '/\\\/', '/\'/');
+               $replace    = array('\\\x00', '\\x1a', '\\n', '\\r', '\\\\', "''");
+               return preg_replace($characters, $replace, $str);
+       }
+
+       public function list_tables()
+       {
+               $sql    = 'SHOW TABLES FROM ['.$this->db_config['connection']['database'].']';
+               $result = $this->query($sql)->result(FALSE, MSSQL_ASSOC);
+
+               $retval = array();
+               foreach ($result as $row)
+               {
+                       $retval[] = current($row);
+               }
+
+               return $retval;
+       }
+
+       public function show_error()
+       {
+               return mssql_get_last_message($this->link);
+       }
+
+       public function list_fields($table)
+       {
+               $result = array();
+
+               foreach ($this->field_data($table) as $row)
+               {
+                       // Make an associative array
+                       $result[$row->Field] = $this->sql_type($row->Type);
+               }
+
+               return $result;
+       }
+
+       public function field_data($table)
+       {
+               $query = $this->query("SELECT COLUMN_NAME AS Field, DATA_TYPE as Type  FROM INFORMATION_SCHEMA.Columns WHERE TABLE_NAME = '".$this->escape_table($table)."'", $this->link);
+
+               return $query->result_array(TRUE);
+       }
+}
+
+/**
+ * MSSQL Result
+ */
+class Mssql_Result extends Database_Result {
+
+       // Fetch function and return type
+       protected $fetch_type  = 'mssql_fetch_object';
+       protected $return_type = MSSQL_ASSOC;
+
+       /**
+        * Sets up the result variables.
+        *
+        * @param  resource  query result
+        * @param  resource  database link
+        * @param  boolean   return objects or arrays
+        * @param  string    SQL query that was run
+        */
+       public function __construct($result, $link, $object = TRUE, $sql)
+       {
+               $this->result = $result;
+
+               // If the query is a resource, it was a SELECT, SHOW, DESCRIBE, EXPLAIN query
+               if (is_resource($result))
+               {
+                       $this->current_row = 0;
+                       $this->total_rows  = mssql_num_rows($this->result);
+                       $this->fetch_type = ($object === TRUE) ? 'mssql_fetch_object' : 'mssql_fetch_array';
+               }
+               elseif (is_bool($result))
+               {
+                       if ($result == FALSE)
+                       {
+                               // SQL error
+                               throw new Kohana_Database_Exception('database.error', mssql_get_last_message($link).' - '.$sql);
+                       }
+                       else
+                       {
+                               // Its an DELETE, INSERT, REPLACE, or UPDATE querys
+                               $last_id          = mssql_query('SELECT @@IDENTITY AS last_id', $link);
+                               $result           = mssql_fetch_assoc($last_id);
+                               $this->insert_id  = $result['last_id'];
+                               $this->total_rows = mssql_rows_affected($link);
+                       }
+               }
+
+               // Set result type
+               $this->result($object);
+
+               // Store the SQL
+               $this->sql = $sql;
+       }
+
+       /**
+        * Destruct, the cleanup crew!
+        */
+       public function __destruct()
+       {
+               if (is_resource($this->result))
+               {
+                       mssql_free_result($this->result);
+               }
+       }
+
+       public function result($object = TRUE, $type = MSSQL_ASSOC)
+       {
+               $this->fetch_type = ((bool) $object) ? 'mssql_fetch_object' : 'mssql_fetch_array';
+
+               // This check has to be outside the previous statement, because we do not
+               // know the state of fetch_type when $object = NULL
+               // NOTE - The class set by $type must be defined before fetching the result,
+               // autoloading is disabled to save a lot of stupid overhead.
+               if ($this->fetch_type == 'mssql_fetch_object')
+               {
+                       $this->return_type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
+               }
+               else
+               {
+                       $this->return_type = $type;
+               }
+
+               return $this;
+       }
+
+       public function as_array($object = NULL, $type = MSSQL_ASSOC)
+       {
+               return $this->result_array($object, $type);
+       }
+
+       public function result_array($object = NULL, $type = MSSQL_ASSOC)
+       {
+               $rows = array();
+
+               if (is_string($object))
+               {
+                       $fetch = $object;
+               }
+               elseif (is_bool($object))
+               {
+                       if ($object === TRUE)
+                       {
+                               $fetch = 'mssql_fetch_object';
+
+                               // NOTE - The class set by $type must be defined before fetching the result,
+                               // autoloading is disabled to save a lot of stupid overhead.
+                               $type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
+                       }
+                       else
+                       {
+                               $fetch = 'mssql_fetch_array';
+                       }
+               }
+               else
+               {
+                       // Use the default config values
+                       $fetch = $this->fetch_type;
+
+                       if ($fetch == 'mssql_fetch_object')
+                       {
+                               $type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
+                       }
+               }
+
+               if (mssql_num_rows($this->result))
+               {
+                       // Reset the pointer location to make sure things work properly
+                       mssql_data_seek($this->result, 0);
+
+                       while ($row = $fetch($this->result, $type))
+                       {
+                               $rows[] = $row;
+                       }
+               }
+
+               return isset($rows) ? $rows : array();
+       }
+
+       public function list_fields()
+       {
+               $field_names = array();
+               while ($field = mssql_fetch_field($this->result))
+               {
+                       $field_names[] = $field->name;
+               }
+
+               return $field_names;
+       }
+
+       public function seek($offset)
+       {
+               if ( ! $this->offsetExists($offset))
+                       return FALSE;
+
+               return mssql_data_seek($this->result, $offset);
+       }
+
+} // End mssql_Result Class
diff --git a/Server/system/libraries/drivers/Database/Mysql.php b/Server/system/libraries/drivers/Database/Mysql.php
new file mode 100644 (file)
index 0000000..d5222f5
--- /dev/null
@@ -0,0 +1,496 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * MySQL Database Driver
+ *
+ * $Id: Mysql.php 4344 2009-05-11 16:41:39Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Database_Mysql_Driver extends Database_Driver {
+
+       /**
+        * Database connection link
+        */
+       protected $link;
+
+       /**
+        * Database configuration
+        */
+       protected $db_config;
+
+       /**
+        * Sets the config for the class.
+        *
+        * @param  array  database configuration
+        */
+       public function __construct($config)
+       {
+               $this->db_config = $config;
+
+               Kohana::log('debug', 'MySQL Database Driver Initialized');
+       }
+
+       /**
+        * Closes the database connection.
+        */
+       public function __destruct()
+       {
+               is_resource($this->link) and mysql_close($this->link);
+       }
+
+       public function connect()
+       {
+               // Check if link already exists
+               if (is_resource($this->link))
+                       return $this->link;
+
+               // Import the connect variables
+               extract($this->db_config['connection']);
+
+               // Persistent connections enabled?
+               $connect = ($this->db_config['persistent'] == TRUE) ? 'mysql_pconnect' : 'mysql_connect';
+
+               // Build the connection info
+               $host = isset($host) ? $host : $socket;
+               $port = isset($port) ? ':'.$port : '';
+
+               // Make the connection and select the database
+               if (($this->link = $connect($host.$port, $user, $pass, TRUE)) AND mysql_select_db($database, $this->link))
+               {
+                       if ($charset = $this->db_config['character_set'])
+                       {
+                               $this->set_charset($charset);
+                       }
+
+                       // Clear password after successful connect
+                       $this->db_config['connection']['pass'] = NULL;
+
+                       return $this->link;
+               }
+
+               return FALSE;
+       }
+
+       public function query($sql)
+       {
+               // Only cache if it's turned on, and only cache if it's not a write statement
+               if ($this->db_config['cache'] AND ! preg_match('#\b(?:INSERT|UPDATE|REPLACE|SET|DELETE|TRUNCATE)\b#i', $sql))
+               {
+                       $hash = $this->query_hash($sql);
+
+                       if ( ! isset($this->query_cache[$hash]))
+                       {
+                               // Set the cached object
+                               $this->query_cache[$hash] = new Mysql_Result(mysql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql);
+                       }
+                       else
+                       {
+                               // Rewind cached result
+                               $this->query_cache[$hash]->rewind();
+                       }
+
+                       // Return the cached query
+                       return $this->query_cache[$hash];
+               }
+
+               return new Mysql_Result(mysql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql);
+       }
+
+       public function set_charset($charset)
+       {
+               $this->query('SET NAMES '.$this->escape_str($charset));
+       }
+
+       public function escape_table($table)
+       {
+               if (!$this->db_config['escape'])
+                       return $table;
+
+               if (stripos($table, ' AS ') !== FALSE)
+               {
+                       // Force 'AS' to uppercase
+                       $table = str_ireplace(' AS ', ' AS ', $table);
+
+                       // Runs escape_table on both sides of an AS statement
+                       $table = array_map(array($this, __FUNCTION__), explode(' AS ', $table));
+
+                       // Re-create the AS statement
+                       return implode(' AS ', $table);
+               }
+               return '`'.str_replace('.', '`.`', $table).'`';
+       }
+
+       public function escape_column($column)
+       {
+               if (!$this->db_config['escape'])
+                       return $column;
+
+               if ($column == '*')
+                       return $column;
+
+               // This matches any functions we support to SELECT.
+               if ( preg_match('/(avg|count|sum|max|min)\(\s*(.*)\s*\)(\s*as\s*(.+)?)?/i', $column, $matches))
+               {
+                       if ( count($matches) == 3)
+                       {
+                               return $matches[1].'('.$this->escape_column($matches[2]).')';
+                       }
+                       else if ( count($matches) == 5)
+                       {
+                               return $matches[1].'('.$this->escape_column($matches[2]).') AS '.$this->escape_column($matches[2]);
+                       }
+               }
+               
+               // This matches any modifiers we support to SELECT.
+               if ( ! preg_match('/\b(?:rand|all|distinct(?:row)?|high_priority|sql_(?:small_result|b(?:ig_result|uffer_result)|no_cache|ca(?:che|lc_found_rows)))\s/i', $column))
+               {
+                       if (stripos($column, ' AS ') !== FALSE)
+                       {
+                               // Force 'AS' to uppercase
+                               $column = str_ireplace(' AS ', ' AS ', $column);
+
+                               // Runs escape_column on both sides of an AS statement
+                               $column = array_map(array($this, __FUNCTION__), explode(' AS ', $column));
+
+                               // Re-create the AS statement
+                               return implode(' AS ', $column);
+                       }
+
+                       return preg_replace('/[^.*]+/', '`$0`', $column);
+               }
+
+               $parts = explode(' ', $column);
+               $column = '';
+
+               for ($i = 0, $c = count($parts); $i < $c; $i++)
+               {
+                       // The column is always last
+                       if ($i == ($c - 1))
+                       {
+                               $column .= preg_replace('/[^.*]+/', '`$0`', $parts[$i]);
+                       }
+                       else // otherwise, it's a modifier
+                       {
+                               $column .= $parts[$i].' ';
+                       }
+               }
+               return $column;
+       }
+
+       public function regex($field, $match, $type, $num_regexs)
+       {
+               $prefix = ($num_regexs == 0) ? '' : $type;
+
+               return $prefix.' '.$this->escape_column($field).' REGEXP \''.$this->escape_str($match).'\'';
+       }
+
+       public function notregex($field, $match, $type, $num_regexs)
+       {
+               $prefix = $num_regexs == 0 ? '' : $type;
+
+               return $prefix.' '.$this->escape_column($field).' NOT REGEXP \''.$this->escape_str($match) . '\'';
+       }
+
+       public function merge($table, $keys, $values)
+       {
+               // Escape the column names
+               foreach ($keys as $key => $value)
+               {
+                       $keys[$key] = $this->escape_column($value);
+               }
+               return 'REPLACE INTO '.$this->escape_table($table).' ('.implode(', ', $keys).') VALUES ('.implode(', ', $values).')';
+       }
+
+       public function limit($limit, $offset = 0)
+       {
+               return 'LIMIT '.$offset.', '.$limit;
+       }
+
+       public function compile_select($database)
+       {
+               $sql = ($database['distinct'] == TRUE) ? 'SELECT DISTINCT ' : 'SELECT ';
+               $sql .= (count($database['select']) > 0) ? implode(', ', $database['select']) : '*';
+
+               if (count($database['from']) > 0)
+               {
+                       // Escape the tables
+                       $froms = array();
+                       foreach ($database['from'] as $from)
+                       {
+                               $froms[] = $this->escape_column($from);
+                       }
+                       $sql .= "\nFROM (";
+                       $sql .= implode(', ', $froms).")";
+               }
+
+               if (count($database['join']) > 0)
+               {
+                       foreach($database['join'] AS $join)
+                       {
+                               $sql .= "\n".$join['type'].'JOIN '.implode(', ', $join['tables']).' ON '.$join['conditions'];
+                       }
+               }
+
+               if (count($database['where']) > 0)
+               {
+                       $sql .= "\nWHERE ";
+               }
+
+               $sql .= implode("\n", $database['where']);
+
+               if (count($database['groupby']) > 0)
+               {
+                       $sql .= "\nGROUP BY ";
+                       $sql .= implode(', ', $database['groupby']);
+               }
+
+               if (count($database['having']) > 0)
+               {
+                       $sql .= "\nHAVING ";
+                       $sql .= implode("\n", $database['having']);
+               }
+
+               if (count($database['orderby']) > 0)
+               {
+                       $sql .= "\nORDER BY ";
+                       $sql .= implode(', ', $database['orderby']);
+               }
+
+               if (is_numeric($database['limit']))
+               {
+                       $sql .= "\n";
+                       $sql .= $this->limit($database['limit'], $database['offset']);
+               }
+
+               return $sql;
+       }
+
+       public function escape_str($str)
+       {
+               if (!$this->db_config['escape'])
+                       return $str;
+
+               is_resource($this->link) or $this->connect();
+
+               return mysql_real_escape_string($str, $this->link);
+       }
+
+       public function list_tables()
+       {
+               $tables = array();
+
+               if ($query = $this->query('SHOW TABLES FROM '.$this->escape_table($this->db_config['connection']['database'])))
+               {
+                       foreach ($query->result(FALSE) as $row)
+                       {
+                               $tables[] = current($row);
+                       }
+               }
+
+               return $tables;
+       }
+
+       public function show_error()
+       {
+               return mysql_error($this->link);
+       }
+
+       public function list_fields($table)
+       {
+               $result = NULL;
+
+               foreach ($this->field_data($table) as $row)
+               {
+                       // Make an associative array
+                       $result[$row->Field] = $this->sql_type($row->Type);
+
+                       if ($row->Key === 'PRI' AND $row->Extra === 'auto_increment')
+                       {
+                               // For sequenced (AUTO_INCREMENT) tables
+                               $result[$row->Field]['sequenced'] = TRUE;
+                       }
+
+                       if ($row->Null === 'YES')
+                       {
+                               // Set NULL status
+                               $result[$row->Field]['null'] = TRUE;
+                       }
+               }
+
+               if (!isset($result))
+                       throw new Kohana_Database_Exception('database.table_not_found', $table);
+
+               return $result;
+       }
+
+       public function field_data($table)
+       {
+               $result = $this->query('SHOW COLUMNS FROM '.$this->escape_table($table));
+
+               return $result->result_array(TRUE);
+       }
+
+} // End Database_Mysql_Driver Class
+
+/**
+ * MySQL Result
+ */
+class Mysql_Result extends Database_Result {
+
+       // Fetch function and return type
+       protected $fetch_type  = 'mysql_fetch_object';
+       protected $return_type = MYSQL_ASSOC;
+
+       /**
+        * Sets up the result variables.
+        *
+        * @param  resource  query result
+        * @param  resource  database link
+        * @param  boolean   return objects or arrays
+        * @param  string    SQL query that was run
+        */
+       public function __construct($result, $link, $object = TRUE, $sql)
+       {
+               $this->result = $result;
+
+               // If the query is a resource, it was a SELECT, SHOW, DESCRIBE, EXPLAIN query
+               if (is_resource($result))
+               {
+                       $this->current_row = 0;
+                       $this->total_rows  = mysql_num_rows($this->result);
+                       $this->fetch_type = ($object === TRUE) ? 'mysql_fetch_object' : 'mysql_fetch_array';
+               }
+               elseif (is_bool($result))
+               {
+                       if ($result == FALSE)
+                       {
+                               // SQL error
+                               throw new Kohana_Database_Exception('database.error', mysql_error($link).' - '.$sql);
+                       }
+                       else
+                       {
+                               // Its an DELETE, INSERT, REPLACE, or UPDATE query
+                               $this->insert_id  = mysql_insert_id($link);
+                               $this->total_rows = mysql_affected_rows($link);
+                       }
+               }
+
+               // Set result type
+               $this->result($object);
+
+               // Store the SQL
+               $this->sql = $sql;
+       }
+
+       /**
+        * Destruct, the cleanup crew!
+        */
+       public function __destruct()
+       {
+               if (is_resource($this->result))
+               {
+                       mysql_free_result($this->result);
+               }
+       }
+
+       public function result($object = TRUE, $type = MYSQL_ASSOC)
+       {
+               $this->fetch_type = ((bool) $object) ? 'mysql_fetch_object' : 'mysql_fetch_array';
+
+               // This check has to be outside the previous statement, because we do not
+               // know the state of fetch_type when $object = NULL
+               // NOTE - The class set by $type must be defined before fetching the result,
+               // autoloading is disabled to save a lot of stupid overhead.
+               if ($this->fetch_type == 'mysql_fetch_object' AND $object === TRUE)
+               {
+                       $this->return_type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
+               }
+               else
+               {
+                       $this->return_type = $type;
+               }
+
+               return $this;
+       }
+
+       public function as_array($object = NULL, $type = MYSQL_ASSOC)
+       {
+               return $this->result_array($object, $type);
+       }
+
+       public function result_array($object = NULL, $type = MYSQL_ASSOC)
+       {
+               $rows = array();
+
+               if (is_string($object))
+               {
+                       $fetch = $object;
+               }
+               elseif (is_bool($object))
+               {
+                       if ($object === TRUE)
+                       {
+                               $fetch = 'mysql_fetch_object';
+
+                               $type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
+                       }
+                       else
+                       {
+                               $fetch = 'mysql_fetch_array';
+                       }
+               }
+               else
+               {
+                       // Use the default config values
+                       $fetch = $this->fetch_type;
+
+                       if ($fetch == 'mysql_fetch_object')
+                       {
+                               $type = (is_string($this->return_type) AND Kohana::auto_load($this->return_type)) ? $this->return_type : 'stdClass';
+                       }
+               }
+
+               if (mysql_num_rows($this->result))
+               {
+                       // Reset the pointer location to make sure things work properly
+                       mysql_data_seek($this->result, 0);
+
+                       while ($row = $fetch($this->result, $type))
+                       {
+                               $rows[] = $row;
+                       }
+               }
+
+               return isset($rows) ? $rows : array();
+       }
+
+       public function list_fields()
+       {
+               $field_names = array();
+               while ($field = mysql_fetch_field($this->result))
+               {
+                       $field_names[] = $field->name;
+               }
+
+               return $field_names;
+       }
+
+       public function seek($offset)
+       {
+               if ($this->offsetExists($offset) AND mysql_data_seek($this->result, $offset))
+               {
+                       // Set the current row to the offset
+                       $this->current_row = $offset;
+
+                       return TRUE;
+               }
+               else
+               {
+                       return FALSE;
+               }
+       }
+
+} // End Mysql_Result Class
diff --git a/Server/system/libraries/drivers/Database/Mysqli.php b/Server/system/libraries/drivers/Database/Mysqli.php
new file mode 100644 (file)
index 0000000..0dd9f05
--- /dev/null
@@ -0,0 +1,358 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * MySQLi Database Driver
+ *
+ * $Id: Mysqli.php 4344 2009-05-11 16:41:39Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Database_Mysqli_Driver extends Database_Mysql_Driver {
+
+       // Database connection link
+       protected $link;
+       protected $db_config;
+       protected $statements = array();
+
+       /**
+        * Sets the config for the class.
+        *
+        * @param  array  database configuration
+        */
+       public function __construct($config)
+       {
+               $this->db_config = $config;
+
+               Kohana::log('debug', 'MySQLi Database Driver Initialized');
+       }
+
+       /**
+        * Closes the database connection.
+        */
+       public function __destruct()
+       {
+               is_object($this->link) and $this->link->close();
+       }
+
+       public function connect()
+       {
+               // Check if link already exists
+               if (is_object($this->link))
+                       return $this->link;
+
+               // Import the connect variables
+               extract($this->db_config['connection']);
+
+               // Build the connection info
+               $host = isset($host) ? $host : $socket;
+
+               // Make the connection and select the database
+               if ($this->link = new mysqli($host, $user, $pass, $database, $port))
+               {
+                       if ($charset = $this->db_config['character_set'])
+                       {
+                               $this->set_charset($charset);
+                       }
+
+                       // Clear password after successful connect
+                       $this->db_config['connection']['pass'] = NULL;
+
+                       return $this->link;
+               }
+
+               return FALSE;
+       }
+
+       public function query($sql)
+       {
+               // Only cache if it's turned on, and only cache if it's not a write statement
+               if ($this->db_config['cache'] AND ! preg_match('#\b(?:INSERT|UPDATE|REPLACE|SET|DELETE|TRUNCATE)\b#i', $sql))
+               {
+                       $hash = $this->query_hash($sql);
+
+                       if ( ! isset($this->query_cache[$hash]))
+                       {
+                               // Set the cached object
+                               $this->query_cache[$hash] = new Kohana_Mysqli_Result($this->link, $this->db_config['object'], $sql);
+                       }
+                       else
+                       {
+                               // Rewind cached result
+                               $this->query_cache[$hash]->rewind();
+                       }
+
+                       // Return the cached query
+                       return $this->query_cache[$hash];
+               }
+
+               return new Kohana_Mysqli_Result($this->link, $this->db_config['object'], $sql);
+       }
+
+       public function set_charset($charset)
+       {
+               if ($this->link->set_charset($charset) === FALSE)
+                       throw new Kohana_Database_Exception('database.error', $this->show_error());
+       }
+
+       public function escape_str($str)
+       {
+               if (!$this->db_config['escape'])
+                       return $str;
+
+               is_object($this->link) or $this->connect();
+
+               return $this->link->real_escape_string($str);
+       }
+
+       public function show_error()
+       {
+               return $this->link->error;
+       }
+
+} // End Database_Mysqli_Driver Class
+
+/**
+ * MySQLi Result
+ */
+class Kohana_Mysqli_Result extends Database_Result {
+
+       // Database connection
+       protected $link;
+
+       // Data fetching types
+       protected $fetch_type  = 'mysqli_fetch_object';
+       protected $return_type = MYSQLI_ASSOC;
+
+       /**
+        * Sets up the result variables.
+        *
+        * @param  object    database link
+        * @param  boolean   return objects or arrays
+        * @param  string    SQL query that was run
+        */
+       public function __construct($link, $object = TRUE, $sql)
+       {
+               $this->link = $link;
+
+               if ( ! $this->link->multi_query($sql))
+               {
+                       // SQL error
+                       throw new Kohana_Database_Exception('database.error', $this->link->error.' - '.$sql);
+               }
+               else
+               {
+                       $this->result = $this->link->store_result();
+
+                       // If the query is an object, it was a SELECT, SHOW, DESCRIBE, EXPLAIN query
+                       if (is_object($this->result))
+                       {
+                               $this->current_row = 0;
+                               $this->total_rows  = $this->result->num_rows;
+                               $this->fetch_type = ($object === TRUE) ? 'fetch_object' : 'fetch_array';
+                       }
+                       elseif ($this->link->error)
+                       {
+                               // SQL error
+                               throw new Kohana_Database_Exception('database.error', $this->link->error.' - '.$sql);
+                       }
+                       else
+                       {
+                               // Its an DELETE, INSERT, REPLACE, or UPDATE query
+                               $this->insert_id  = $this->link->insert_id;
+                               $this->total_rows = $this->link->affected_rows;
+                       }
+               }
+
+               // Set result type
+               $this->result($object);
+
+               // Store the SQL
+               $this->sql = $sql;
+       }
+
+       /**
+        * Magic __destruct function, frees the result.
+        */
+       public function __destruct()
+       {
+               if (is_object($this->result))
+               {
+                       $this->result->free_result();
+
+                       // this is kinda useless, but needs to be done to avoid the "Commands out of sync; you
+                       // can't run this command now" error. Basically, we get all results after the first one
+                       // (the one we actually need) and free them.
+                       if (is_resource($this->link) AND $this->link->more_results())
+                       {
+                               do
+                               {
+                                       if ($result = $this->link->store_result())
+                                       {
+                                               $result->free_result();
+                                       }
+                               } while ($this->link->next_result());
+                       }
+               }
+       }
+
+       public function result($object = TRUE, $type = MYSQLI_ASSOC)
+       {
+               $this->fetch_type = ((bool) $object) ? 'fetch_object' : 'fetch_array';
+
+               // This check has to be outside the previous statement, because we do not
+               // know the state of fetch_type when $object = NULL
+               // NOTE - The class set by $type must be defined before fetching the result,
+               // autoloading is disabled to save a lot of stupid overhead.
+               if ($this->fetch_type == 'fetch_object')
+               {
+                       $this->return_type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
+               }
+               else
+               {
+                       $this->return_type = $type;
+               }
+
+               return $this;
+       }
+
+       public function as_array($object = NULL, $type = MYSQLI_ASSOC)
+       {
+               return $this->result_array($object, $type);
+       }
+
+       public function result_array($object = NULL, $type = MYSQLI_ASSOC)
+       {
+               $rows = array();
+
+               if (is_string($object))
+               {
+                       $fetch = $object;
+               }
+               elseif (is_bool($object))
+               {
+                       if ($object === TRUE)
+                       {
+                               $fetch = 'fetch_object';
+
+                               // NOTE - The class set by $type must be defined before fetching the result,
+                               // autoloading is disabled to save a lot of stupid overhead.
+                               $type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
+                       }
+                       else
+                       {
+                               $fetch = 'fetch_array';
+                       }
+               }
+               else
+               {
+                       // Use the default config values
+                       $fetch = $this->fetch_type;
+
+                       if ($fetch == 'fetch_object')
+                       {
+                               $type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
+                       }
+               }
+
+               if ($this->result->num_rows)
+               {
+                       // Reset the pointer location to make sure things work properly
+                       $this->result->data_seek(0);
+
+                       while ($row = $this->result->$fetch($type))
+                       {
+                               $rows[] = $row;
+                       }
+               }
+
+               return isset($rows) ? $rows : array();
+       }
+
+       public function list_fields()
+       {
+               $field_names = array();
+               while ($field = $this->result->fetch_field())
+               {
+                       $field_names[] = $field->name;
+               }
+
+               return $field_names;
+       }
+
+       public function seek($offset)
+       {
+               if ($this->offsetExists($offset) AND $this->result->data_seek($offset))
+               {
+                       // Set the current row to the offset
+                       $this->current_row = $offset;
+
+                       return TRUE;
+               }
+
+               return FALSE;
+       }
+
+       public function offsetGet($offset)
+       {
+               if ( ! $this->seek($offset))
+                       return FALSE;
+
+               // Return the row
+               $fetch = $this->fetch_type;
+               return $this->result->$fetch($this->return_type);
+       }
+
+} // End Mysqli_Result Class
+
+/**
+ * MySQLi Prepared Statement (experimental)
+ */
+class Kohana_Mysqli_Statement {
+
+       protected $link = NULL;
+       protected $stmt;
+       protected $var_names = array();
+       protected $var_values = array();
+
+       public function __construct($sql, $link)
+       {
+               $this->link = $link;
+
+               $this->stmt = $this->link->prepare($sql);
+
+               return $this;
+       }
+
+       public function __destruct()
+       {
+               $this->stmt->close();
+       }
+
+       // Sets the bind parameters
+       public function bind_params($param_types, $params)
+       {
+               $this->var_names = array_keys($params);
+               $this->var_values = array_values($params);
+               call_user_func_array(array($this->stmt, 'bind_param'), array_merge($param_types, $var_names));
+
+               return $this;
+       }
+
+       public function bind_result($params)
+       {
+               call_user_func_array(array($this->stmt, 'bind_result'), $params);
+       }
+
+       // Runs the statement
+       public function execute()
+       {
+               foreach ($this->var_names as $key => $name)
+               {
+                       $$name = $this->var_values[$key];
+               }
+               $this->stmt->execute();
+               return $this->stmt;
+       }
+}
diff --git a/Server/system/libraries/drivers/Database/Pdosqlite.php b/Server/system/libraries/drivers/Database/Pdosqlite.php
new file mode 100644 (file)
index 0000000..c2d1bb2
--- /dev/null
@@ -0,0 +1,486 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/*
+ * Class: Database_PdoSqlite_Driver
+ *  Provides specific database items for Sqlite.
+ *
+ * Connection string should be, eg: "pdosqlite://path/to/database.db"
+ *
+ * Version 1.0 alpha
+ *  author    - Doutu, updated by gregmac
+ *  copyright - (c) BSD
+ *  license   - <no>
+ */
+
+class Database_Pdosqlite_Driver extends Database_Driver {
+
+       // Database connection link
+       protected $link;
+       protected $db_config;
+
+       /*
+        * Constructor: __construct
+        *  Sets up the config for the class.
+        *
+        * Parameters:
+        *  config - database configuration
+        *
+        */
+       public function __construct($config)
+       {
+               $this->db_config = $config;
+
+               Kohana::log('debug', 'PDO:Sqlite Database Driver Initialized');
+       }
+
+       public function connect()
+       {
+               // Import the connect variables
+               extract($this->db_config['connection']);
+
+               try
+               {
+                       $this->link = new PDO('sqlite:'.$socket.$database, $user, $pass,
+                               array(PDO::ATTR_PERSISTENT => $this->db_config['persistent']));
+
+                       $this->link->setAttribute(PDO::ATTR_CASE, PDO::CASE_NATURAL);
+                       //$this->link->query('PRAGMA count_changes=1;');
+
+                       if ($charset = $this->db_config['character_set'])
+                       {
+                               $this->set_charset($charset);
+                       }
+               }
+               catch (PDOException $e)
+               {
+                       throw new Kohana_Database_Exception('database.error', $e->getMessage());
+               }
+
+               // Clear password after successful connect
+               $this->db_config['connection']['pass'] = NULL;
+
+               return $this->link;
+       }
+
+       public function query($sql)
+       {
+        try
+               {
+                       $sth = $this->link->prepare($sql);
+               }
+               catch (PDOException $e)
+               {
+                       throw new Kohana_Database_Exception('database.error', $e->getMessage());
+               }
+               return new Pdosqlite_Result($sth, $this->link, $this->db_config['object'], $sql);
+       }
+
+       public function set_charset($charset)
+       {
+               $this->link->query('PRAGMA encoding = '.$this->escape_str($charset));
+       }
+
+       public function escape_table($table)
+       {
+               if ( ! $this->db_config['escape'])
+                       return $table;
+
+               return '`'.str_replace('.', '`.`', $table).'`';
+       }
+
+       public function escape_column($column)
+       {
+               if ( ! $this->db_config['escape'])
+                       return $column;
+
+               if ($column == '*')
+                       return $column;
+
+               // This matches any functions we support to SELECT.
+               if ( preg_match('/(avg|count|sum|max|min)\(\s*(.*)\s*\)(\s*as\s*(.+)?)?/i', $column, $matches))
+               {
+                       if ( count($matches) == 3)
+                       {
+                               return $matches[1].'('.$this->escape_column($matches[2]).')';
+                       }
+                       else if ( count($matches) == 5)
+                       {
+                               return $matches[1].'('.$this->escape_column($matches[2]).') AS '.$this->escape_column($matches[2]);
+                       }
+               }
+
+               // This matches any modifiers we support to SELECT.
+               if ( ! preg_match('/\b(?:rand|all|distinct(?:row)?|high_priority|sql_(?:small_result|b(?:ig_result|uffer_result)|no_cache|ca(?:che|lc_found_rows)))\s/i', $column))
+               {
+                       if (stripos($column, ' AS ') !== FALSE)
+                       {
+                               // Force 'AS' to uppercase
+                               $column = str_ireplace(' AS ', ' AS ', $column);
+
+                               // Runs escape_column on both sides of an AS statement
+                               $column = array_map(array($this, __FUNCTION__), explode(' AS ', $column));
+
+                               // Re-create the AS statement
+                               return implode(' AS ', $column);
+                       }
+
+                       return preg_replace('/[^.*]+/', '`$0`', $column);
+               }
+
+               $parts = explode(' ', $column);
+               $column = '';
+
+               for ($i = 0, $c = count($parts); $i < $c; $i++)
+               {
+                       // The column is always last
+                       if ($i == ($c - 1))
+                       {
+                               $column .= preg_replace('/[^.*]+/', '`$0`', $parts[$i]);
+                       }
+                       else // otherwise, it's a modifier
+                       {
+                               $column .= $parts[$i].' ';
+                       }
+               }
+               return $column;
+       }
+
+       public function limit($limit, $offset = 0)
+       {
+               return 'LIMIT '.$offset.', '.$limit;
+       }
+
+       public function compile_select($database)
+       {
+               $sql = ($database['distinct'] == TRUE) ? 'SELECT DISTINCT ' : 'SELECT ';
+               $sql .= (count($database['select']) > 0) ? implode(', ', $database['select']) : '*';
+
+               if (count($database['from']) > 0)
+               {
+                       $sql .= "\nFROM ";
+                       $sql .= implode(', ', $database['from']);
+               }
+
+               if (count($database['join']) > 0)
+               {
+                       foreach($database['join'] AS $join)
+                       {
+                               $sql .= "\n".$join['type'].'JOIN '.implode(', ', $join['tables']).' ON '.$join['conditions'];
+                       }
+               }
+
+               if (count($database['where']) > 0)
+               {
+                       $sql .= "\nWHERE ";
+               }
+
+               $sql .= implode("\n", $database['where']);
+
+               if (count($database['groupby']) > 0)
+               {
+                       $sql .= "\nGROUP BY ";
+                       $sql .= implode(', ', $database['groupby']);
+               }
+
+               if (count($database['having']) > 0)
+               {
+                       $sql .= "\nHAVING ";
+                       $sql .= implode("\n", $database['having']);
+               }
+
+               if (count($database['orderby']) > 0)
+               {
+                       $sql .= "\nORDER BY ";
+                       $sql .= implode(', ', $database['orderby']);
+               }
+
+               if (is_numeric($database['limit']))
+               {
+                       $sql .= "\n";
+                       $sql .= $this->limit($database['limit'], $database['offset']);
+               }
+
+               return $sql;
+       }
+
+       public function escape_str($str)
+       {
+               if ( ! $this->db_config['escape'])
+                       return $str;
+
+               if (function_exists('sqlite_escape_string'))
+               {
+                       $res = sqlite_escape_string($str);
+               }
+               else
+               {
+                       $res = str_replace("'", "''", $str);
+               }
+               return $res;
+       }
+
+       public function list_tables()
+       {
+               $sql = "SELECT `name` FROM `sqlite_master` WHERE `type`='table' ORDER BY `name`;";
+               try
+               {
+                       $result = $this->query($sql)->result(FALSE, PDO::FETCH_ASSOC);
+                       $tables = array();
+                       foreach ($result as $row)
+                       {
+                               $tables[] = current($row);
+                       }
+               }
+               catch (PDOException $e)
+               {
+                       throw new Kohana_Database_Exception('database.error', $e->getMessage());
+               }
+               return $tables;
+       }
+
+       public function show_error()
+       {
+               $err = $this->link->errorInfo();
+               return isset($err[2]) ? $err[2] : 'Unknown error!';
+       }
+
+       public function list_fields($table, $query = FALSE)
+       {
+               static $tables;
+               if (is_object($query))
+               {
+                       if (empty($tables[$table]))
+                       {
+                               $tables[$table] = array();
+
+                               foreach ($query->result() as $row)
+                               {
+                                       $tables[$table][] = $row->name;
+                               }
+                       }
+
+                       return $tables[$table];
+               }
+               else
+               {
+                       $result = $this->link->query( 'PRAGMA table_info('.$this->escape_table($table).')' );
+
+                       foreach ($result as $row)
+                       {
+                               $tables[$table][$row['name']] = $this->sql_type($row['type']);
+                       }
+
+                       return $tables[$table];
+               }
+       }
+
+       public function field_data($table)
+       {
+               Kohana::log('error', 'This method is under developing');
+       }
+       /**
+        * Version number query string
+        *
+        * @access      public
+        * @return      string
+        */
+       function version()
+       {
+               return $this->link->getAttribute(constant("PDO::ATTR_SERVER_VERSION"));
+       }
+
+} // End Database_PdoSqlite_Driver Class
+
+/*
+ * PDO-sqlite Result
+ */
+class Pdosqlite_Result extends Database_Result {
+
+       // Data fetching types
+       protected $fetch_type  = PDO::FETCH_OBJ;
+       protected $return_type = PDO::FETCH_ASSOC;
+
+       /**
+        * Sets up the result variables.
+        *
+        * @param  resource  query result
+        * @param  resource  database link
+        * @param  boolean   return objects or arrays
+        * @param  string    SQL query that was run
+        */
+       public function __construct($result, $link, $object = TRUE, $sql)
+       {
+               if (is_object($result) OR $result = $link->prepare($sql))
+               {
+                       // run the query. Return true if success, false otherwise
+                       if( ! $result->execute())
+                       {
+                               // Throw Kohana Exception with error message. See PDOStatement errorInfo() method
+                               $arr_infos = $result->errorInfo();
+                               throw new Kohana_Database_Exception('database.error', $arr_infos[2]);
+                       }
+
+                       if (preg_match('/^SELECT|PRAGMA|EXPLAIN/i', $sql))
+                       {
+                               $this->result = $result;
+                               $this->current_row = 0;
+
+                               $this->total_rows = $this->sqlite_row_count();
+
+                               $this->fetch_type = ($object === TRUE) ? PDO::FETCH_OBJ : PDO::FETCH_ASSOC;
+                       }
+                       elseif (preg_match('/^DELETE|INSERT|UPDATE/i', $sql))
+                       {
+                               $this->insert_id  = $link->lastInsertId();
+
+                               $this->total_rows = $result->rowCount();
+                       }
+               }
+               else
+               {
+                       // SQL error
+                       throw new Kohana_Database_Exception('database.error', $link->errorInfo().' - '.$sql);
+               }
+
+               // Set result type
+               $this->result($object);
+
+               // Store the SQL
+               $this->sql = $sql;
+       }
+
+       private function sqlite_row_count()
+       {
+               $count = 0;
+               while ($this->result->fetch())
+               {
+                       $count++;
+               }
+
+               // The query must be re-fetched now.
+               $this->result->execute();
+
+               return $count;
+       }
+
+       /*
+        * Destructor: __destruct
+        *  Magic __destruct function, frees the result.
+        */
+       public function __destruct()
+       {
+               if (is_object($this->result))
+               {
+                       $this->result->closeCursor();
+                       $this->result = NULL;
+               }
+       }
+
+       public function result($object = TRUE, $type = PDO::FETCH_BOTH)
+       {
+               $this->fetch_type = (bool) $object ? PDO::FETCH_OBJ : PDO::FETCH_BOTH;
+
+               if ($this->fetch_type == PDO::FETCH_OBJ)
+               {
+                       $this->return_type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
+               }
+               else
+               {
+                       $this->return_type = $type;
+               }
+
+               return $this;
+       }
+
+       public function as_array($object = NULL, $type = PDO::FETCH_ASSOC)
+       {
+               return $this->result_array($object, $type);
+       }
+
+       public function result_array($object = NULL, $type = PDO::FETCH_ASSOC)
+       {
+               $rows = array();
+
+               if (is_string($object))
+               {
+                       $fetch = $object;
+               }
+               elseif (is_bool($object))
+               {
+                       if ($object === TRUE)
+                       {
+                               $fetch = PDO::FETCH_OBJ;
+
+                               // NOTE - The class set by $type must be defined before fetching the result,
+                               // autoloading is disabled to save a lot of stupid overhead.
+                               $type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
+                       }
+                       else
+                       {
+                               $fetch = PDO::FETCH_OBJ;
+                       }
+               }
+               else
+               {
+                       // Use the default config values
+                       $fetch = $this->fetch_type;
+
+                       if ($fetch == PDO::FETCH_OBJ)
+                       {
+                               $type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
+                       }
+               }
+               try
+               {
+                       while ($row = $this->result->fetch($fetch))
+                       {
+                               $rows[] = $row;
+                       }
+               }
+               catch(PDOException $e)
+               {
+                       throw new Kohana_Database_Exception('database.error', $e->getMessage());
+                       return FALSE;
+               }
+               return $rows;
+       }
+
+       public function list_fields()
+       {
+               $field_names = array();
+               for ($i = 0, $max = $this->result->columnCount(); $i < $max; $i++)
+               {
+                       $info = $this->result->getColumnMeta($i);
+                       $field_names[] = $info['name'];
+               }
+               return $field_names;
+       }
+
+       public function seek($offset)
+       {
+               // To request a scrollable cursor for your PDOStatement object, you must
+               // set the PDO::ATTR_CURSOR attribute to PDO::CURSOR_SCROLL when you
+               // prepare the statement.
+               Kohana::log('error', get_class($this).' does not support scrollable cursors, '.__FUNCTION__.' call ignored');
+
+               return FALSE;
+       }
+
+       public function offsetGet($offset)
+       {
+               try
+               {
+                       return $this->result->fetch($this->fetch_type, PDO::FETCH_ORI_ABS, $offset);
+               }
+               catch(PDOException $e)
+               {
+                       throw new Kohana_Database_Exception('database.error', $e->getMessage());
+               }
+       }
+
+       public function rewind()
+       {
+               // Same problem that seek() has, see above.
+               return $this->seek(0);
+       }
+
+} // End PdoSqlite_Result Class
\ No newline at end of file
diff --git a/Server/system/libraries/drivers/Database/Pgsql.php b/Server/system/libraries/drivers/Database/Pgsql.php
new file mode 100644 (file)
index 0000000..c53c843
--- /dev/null
@@ -0,0 +1,538 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * PostgreSQL 8.1+ Database Driver
+ *
+ * $Id: Pgsql.php 4344 2009-05-11 16:41:39Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Database_Pgsql_Driver extends Database_Driver {
+
+       // Database connection link
+       protected $link;
+       protected $db_config;
+
+       /**
+        * Sets the config for the class.
+        *
+        * @param  array  database configuration
+        */
+       public function __construct($config)
+       {
+               $this->db_config = $config;
+
+               Kohana::log('debug', 'PgSQL Database Driver Initialized');
+       }
+
+       public function connect()
+       {
+               // Check if link already exists
+               if (is_resource($this->link))
+                       return $this->link;
+
+               // Import the connect variables
+               extract($this->db_config['connection']);
+
+               // Persistent connections enabled?
+               $connect = ($this->db_config['persistent'] == TRUE) ? 'pg_pconnect' : 'pg_connect';
+
+               // Build the connection info
+               $port = isset($port) ? 'port=\''.$port.'\'' : '';
+               $host = isset($host) ? 'host=\''.$host.'\' '.$port : ''; // if no host, connect with the socket
+
+               $connection_string = $host.' dbname=\''.$database.'\' user=\''.$user.'\' password=\''.$pass.'\'';
+               // Make the connection and select the database
+               if ($this->link = $connect($connection_string))
+               {
+                       if ($charset = $this->db_config['character_set'])
+                       {
+                               echo $this->set_charset($charset);
+                       }
+
+                       // Clear password after successful connect
+                       $this->db_config['connection']['pass'] = NULL;
+
+                       return $this->link;
+               }
+
+               return FALSE;
+       }
+
+       public function query($sql)
+       {
+               // Only cache if it's turned on, and only cache if it's not a write statement
+               if ($this->db_config['cache'] AND ! preg_match('#\b(?:INSERT|UPDATE|SET)\b#i', $sql))
+               {
+                       $hash = $this->query_hash($sql);
+
+                       if ( ! isset($this->query_cache[$hash]))
+                       {
+                               // Set the cached object
+                               $this->query_cache[$hash] = new Pgsql_Result(pg_query($this->link, $sql), $this->link, $this->db_config['object'], $sql);
+                       }
+                       else
+                       {
+                               // Rewind cached result
+                               $this->query_cache[$hash]->rewind();
+                       }
+
+                       return $this->query_cache[$hash];
+               }
+
+               // Suppress warning triggered when a database error occurs (e.g., a constraint violation)
+               return new Pgsql_Result(@pg_query($this->link, $sql), $this->link, $this->db_config['object'], $sql);
+       }
+
+       public function set_charset($charset)
+       {
+               $this->query('SET client_encoding TO '.pg_escape_string($this->link, $charset));
+       }
+
+       public function escape_table($table)
+       {
+               if (!$this->db_config['escape'])
+                       return $table;
+
+               return '"'.str_replace('.', '"."', $table).'"';
+       }
+
+       public function escape_column($column)
+       {
+               if (!$this->db_config['escape'])
+                       return $column;
+
+               if ($column == '*')
+                       return $column;
+
+               // This matches any functions we support to SELECT.
+               if ( preg_match('/(avg|count|sum|max|min)\(\s*(.*)\s*\)(\s*as\s*(.+)?)?/i', $column, $matches))
+               {
+                       if ( count($matches) == 3)
+                       {
+                               return $matches[1].'('.$this->escape_column($matches[2]).')';
+                       }
+                       else if ( count($matches) == 5)
+                       {
+                               return $matches[1].'('.$this->escape_column($matches[2]).') AS '.$this->escape_column($matches[2]);
+                       }
+               }
+
+               // This matches any modifiers we support to SELECT.
+               if ( ! preg_match('/\b(?:all|distinct)\s/i', $column))
+               {
+                       if (stripos($column, ' AS ') !== FALSE)
+                       {
+                               // Force 'AS' to uppercase
+                               $column = str_ireplace(' AS ', ' AS ', $column);
+
+                               // Runs escape_column on both sides of an AS statement
+                               $column = array_map(array($this, __FUNCTION__), explode(' AS ', $column));
+
+                               // Re-create the AS statement
+                               return implode(' AS ', $column);
+                       }
+
+                       return preg_replace('/[^.*]+/', '"$0"', $column);
+               }
+
+               $parts = explode(' ', $column);
+               $column = '';
+
+               for ($i = 0, $c = count($parts); $i < $c; $i++)
+               {
+                       // The column is always last
+                       if ($i == ($c - 1))
+                       {
+                               $column .= preg_replace('/[^.*]+/', '"$0"', $parts[$i]);
+                       }
+                       else // otherwise, it's a modifier
+                       {
+                               $column .= $parts[$i].' ';
+                       }
+               }
+               return $column;
+       }
+
+       public function regex($field, $match, $type, $num_regexs)
+       {
+               $prefix = ($num_regexs == 0) ? '' : $type;
+
+               return $prefix.' '.$this->escape_column($field).' ~* \''.$this->escape_str($match).'\'';
+       }
+
+       public function notregex($field, $match, $type, $num_regexs)
+       {
+               $prefix = $num_regexs == 0 ? '' : $type;
+
+               return $prefix.' '.$this->escape_column($field).' !~* \''.$this->escape_str($match) . '\'';
+       }
+
+       public function limit($limit, $offset = 0)
+       {
+               return 'LIMIT '.$limit.' OFFSET '.$offset;
+       }
+
+       public function compile_select($database)
+       {
+               $sql = ($database['distinct'] == TRUE) ? 'SELECT DISTINCT ' : 'SELECT ';
+               $sql .= (count($database['select']) > 0) ? implode(', ', $database['select']) : '*';
+
+               if (count($database['from']) > 0)
+               {
+                       $sql .= "\nFROM ";
+                       $sql .= implode(', ', $database['from']);
+               }
+
+               if (count($database['join']) > 0)
+               {
+                       foreach($database['join'] AS $join)
+                       {
+                               $sql .= "\n".$join['type'].'JOIN '.implode(', ', $join['tables']).' ON '.$join['conditions'];
+                       }
+               }
+
+               if (count($database['where']) > 0)
+               {
+                       $sql .= "\nWHERE ";
+               }
+
+               $sql .= implode("\n", $database['where']);
+
+               if (count($database['groupby']) > 0)
+               {
+                       $sql .= "\nGROUP BY ";
+                       $sql .= implode(', ', $database['groupby']);
+               }
+
+               if (count($database['having']) > 0)
+               {
+                       $sql .= "\nHAVING ";
+                       $sql .= implode("\n", $database['having']);
+               }
+
+               if (count($database['orderby']) > 0)
+               {
+                       $sql .= "\nORDER BY ";
+                       $sql .= implode(', ', $database['orderby']);
+               }
+
+               if (is_numeric($database['limit']))
+               {
+                       $sql .= "\n";
+                       $sql .= $this->limit($database['limit'], $database['offset']);
+               }
+
+               return $sql;
+       }
+
+       public function escape_str($str)
+       {
+               if (!$this->db_config['escape'])
+                       return $str;
+
+               is_resource($this->link) or $this->connect();
+
+               return pg_escape_string($this->link, $str);
+       }
+
+       public function list_tables()
+       {
+               $sql    = 'SELECT table_schema || \'.\' || table_name FROM information_schema.tables WHERE table_schema NOT IN (\'pg_catalog\', \'information_schema\')';
+               $result = $this->query($sql)->result(FALSE, PGSQL_ASSOC);
+
+               $retval = array();
+               foreach ($result as $row)
+               {
+                       $retval[] = current($row);
+               }
+
+               return $retval;
+       }
+
+       public function show_error()
+       {
+               return pg_last_error($this->link);
+       }
+
+       public function list_fields($table)
+       {
+               $result = NULL;
+
+               foreach ($this->field_data($table) as $row)
+               {
+                       // Make an associative array
+                       $result[$row->column_name] = $this->sql_type($row->data_type);
+
+                       if (!strncmp($row->column_default, 'nextval(', 8))
+                       {
+                               $result[$row->column_name]['sequenced'] = TRUE;
+                       }
+
+                       if ($row->is_nullable === 'YES')
+                       {
+                               $result[$row->column_name]['null'] = TRUE;
+                       }
+               }
+
+               if (!isset($result))
+                       throw new Kohana_Database_Exception('database.table_not_found', $table);
+
+               return $result;
+       }
+
+       public function field_data($table)
+       {
+               // http://www.postgresql.org/docs/8.3/static/infoschema-columns.html
+               $result = $this->query('
+                       SELECT column_name, column_default, is_nullable, data_type, udt_name,
+                               character_maximum_length, numeric_precision, numeric_precision_radix, numeric_scale
+                       FROM information_schema.columns
+                       WHERE table_name = \''. $this->escape_str($table) .'\'
+                       ORDER BY ordinal_position
+               ');
+
+               return $result->result_array(TRUE);
+       }
+
+} // End Database_Pgsql_Driver Class
+
+/**
+ * PostgreSQL Result
+ */
+class Pgsql_Result extends Database_Result {
+
+       // Data fetching types
+       protected $fetch_type  = 'pgsql_fetch_object';
+       protected $return_type = PGSQL_ASSOC;
+
+       /**
+        * Sets up the result variables.
+        *
+        * @param  resource  query result
+        * @param  resource  database link
+        * @param  boolean   return objects or arrays
+        * @param  string    SQL query that was run
+        */
+       public function __construct($result, $link, $object = TRUE, $sql)
+       {
+               $this->link = $link;
+               $this->result = $result;
+
+               // If the query is a resource, it was a SELECT, SHOW, DESCRIBE, EXPLAIN query
+               if (is_resource($result))
+               {
+                       // Its an DELETE, INSERT, REPLACE, or UPDATE query
+                       if (preg_match('/^(?:delete|insert|replace|update)\b/iD', trim($sql), $matches))
+                       {
+                               $this->insert_id  = (strtolower($matches[0]) == 'insert') ? $this->insert_id() : FALSE;
+                               $this->total_rows = pg_affected_rows($this->result);
+                       }
+                       else
+                       {
+                               $this->current_row = 0;
+                               $this->total_rows  = pg_num_rows($this->result);
+                               $this->fetch_type = ($object === TRUE) ? 'pg_fetch_object' : 'pg_fetch_array';
+                       }
+               }
+               else
+               {
+                       throw new Kohana_Database_Exception('database.error', pg_last_error().' - '.$sql);
+               }
+
+               // Set result type
+               $this->result($object);
+
+               // Store the SQL
+               $this->sql = $sql;
+       }
+
+       /**
+        * Magic __destruct function, frees the result.
+        */
+       public function __destruct()
+       {
+               if (is_resource($this->result))
+               {
+                       pg_free_result($this->result);
+               }
+       }
+
+       public function result($object = TRUE, $type = PGSQL_ASSOC)
+       {
+               $this->fetch_type = ((bool) $object) ? 'pg_fetch_object' : 'pg_fetch_array';
+
+               // This check has to be outside the previous statement, because we do not
+               // know the state of fetch_type when $object = NULL
+               // NOTE - The class set by $type must be defined before fetching the result,
+               // autoloading is disabled to save a lot of stupid overhead.
+               if ($this->fetch_type == 'pg_fetch_object')
+               {
+                       $this->return_type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
+               }
+               else
+               {
+                       $this->return_type = $type;
+               }
+
+               return $this;
+       }
+
+       public function as_array($object = NULL, $type = PGSQL_ASSOC)
+       {
+               return $this->result_array($object, $type);
+       }
+
+       public function result_array($object = NULL, $type = PGSQL_ASSOC)
+       {
+               $rows = array();
+
+               if (is_string($object))
+               {
+                       $fetch = $object;
+               }
+               elseif (is_bool($object))
+               {
+                       if ($object === TRUE)
+                       {
+                               $fetch = 'pg_fetch_object';
+
+                               // NOTE - The class set by $type must be defined before fetching the result,
+                               // autoloading is disabled to save a lot of stupid overhead.
+                               $type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
+                       }
+                       else
+                       {
+                               $fetch = 'pg_fetch_array';
+                       }
+               }
+               else
+               {
+                       // Use the default config values
+                       $fetch = $this->fetch_type;
+
+                       if ($fetch == 'pg_fetch_object')
+                       {
+                               $type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
+                       }
+               }
+
+               if ($this->total_rows)
+               {
+                       pg_result_seek($this->result, 0);
+
+                       while ($row = $fetch($this->result, NULL, $type))
+                       {
+                               $rows[] = $row;
+                       }
+               }
+
+               return $rows;
+       }
+
+       public function insert_id()
+       {
+               if ($this->insert_id === NULL)
+               {
+                       $query = 'SELECT LASTVAL() AS insert_id';
+
+                       // Disable error reporting for this, just to silence errors on
+                       // tables that have no serial column.
+                       $ER = error_reporting(0);
+
+                       $result = pg_query($this->link, $query);
+                       $insert_id = pg_fetch_array($result, NULL, PGSQL_ASSOC);
+
+                       $this->insert_id = $insert_id['insert_id'];
+
+                       // Reset error reporting
+                       error_reporting($ER);
+               }
+
+               return $this->insert_id;
+       }
+
+       public function seek($offset)
+       {
+               if ($this->offsetExists($offset) AND pg_result_seek($this->result, $offset))
+               {
+                       // Set the current row to the offset
+                       $this->current_row = $offset;
+
+                       return TRUE;
+               }
+
+               return FALSE;
+       }
+
+       public function list_fields()
+       {
+               $field_names = array();
+
+               $fields = pg_num_fields($this->result);
+               for ($i = 0; $i < $fields; ++$i)
+               {
+                       $field_names[] = pg_field_name($this->result, $i);
+               }
+
+               return $field_names;
+       }
+
+       /**
+        * ArrayAccess: offsetGet
+        */
+       public function offsetGet($offset)
+       {
+               if ( ! $this->seek($offset))
+                       return FALSE;
+
+               // Return the row by calling the defined fetching callback
+               $fetch = $this->fetch_type;
+               return $fetch($this->result, NULL, $this->return_type);
+       }
+
+} // End Pgsql_Result Class
+
+/**
+ * PostgreSQL Prepared Statement (experimental)
+ */
+class Kohana_Pgsql_Statement {
+
+       protected $link = NULL;
+       protected $stmt;
+
+       public function __construct($sql, $link)
+       {
+               $this->link = $link;
+
+               $this->stmt = $this->link->prepare($sql);
+
+               return $this;
+       }
+
+       public function __destruct()
+       {
+               $this->stmt->close();
+       }
+
+       // Sets the bind parameters
+       public function bind_params()
+       {
+               $argv = func_get_args();
+               return $this;
+       }
+
+       // sets the statement values to the bound parameters
+       public function set_vals()
+       {
+               return $this;
+       }
+
+       // Runs the statement
+       public function execute()
+       {
+               return $this;
+       }
+}
diff --git a/Server/system/libraries/drivers/Image.php b/Server/system/libraries/drivers/Image.php
new file mode 100644 (file)
index 0000000..5c4ab1b
--- /dev/null
@@ -0,0 +1,149 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Image API driver.
+ *
+ * $Id: Image.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package    Image
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+abstract class Image_Driver {
+
+       // Reference to the current image
+       protected $image;
+
+       // Reference to the temporary processing image
+       protected $tmp_image;
+
+       // Processing errors
+       protected $errors = array();
+
+       /**
+        * Executes a set of actions, defined in pairs.
+        *
+        * @param   array    actions
+        * @return  boolean
+        */
+       public function execute($actions)
+       {
+               foreach ($actions as $func => $args)
+               {
+                       if ( ! $this->$func($args))
+                               return FALSE;
+               }
+
+               return TRUE;
+       }
+
+       /**
+        * Sanitize and normalize a geometry array based on the temporary image
+        * width and height. Valid properties are: width, height, top, left.
+        *
+        * @param   array  geometry properties
+        * @return  void
+        */
+       protected function sanitize_geometry( & $geometry)
+       {
+               list($width, $height) = $this->properties();
+
+               // Turn off error reporting
+               $reporting = error_reporting(0);
+
+               // Width and height cannot exceed current image size
+               $geometry['width']  = min($geometry['width'], $width);
+               $geometry['height'] = min($geometry['height'], $height);
+
+               // Set standard coordinates if given, otherwise use pixel values
+               if ($geometry['top'] === 'center')
+               {
+                       $geometry['top'] = floor(($height / 2) - ($geometry['height'] / 2));
+               }
+               elseif ($geometry['top'] === 'top')
+               {
+                       $geometry['top'] = 0;
+               }
+               elseif ($geometry['top'] === 'bottom')
+               {
+                       $geometry['top'] = $height - $geometry['height'];
+               }
+
+               // Set standard coordinates if given, otherwise use pixel values
+               if ($geometry['left'] === 'center')
+               {
+                       $geometry['left'] = floor(($width / 2) - ($geometry['width'] / 2));
+               }
+               elseif ($geometry['left'] === 'left')
+               {
+                       $geometry['left'] = 0;
+               }
+               elseif ($geometry['left'] === 'right')
+               {
+                       $geometry['left'] = $width - $geometry['height'];
+               }
+
+               // Restore error reporting
+               error_reporting($reporting);
+       }
+
+       /**
+        * Return the current width and height of the temporary image. This is mainly
+        * needed for sanitizing the geometry.
+        *
+        * @return  array  width, height
+        */
+       abstract protected function properties();
+
+       /**
+        * Process an image with a set of actions.
+        *
+        * @param   string   image filename
+        * @param   array    actions to execute
+        * @param   string   destination directory path
+        * @param   string   destination filename
+        * @return  boolean
+        */
+       abstract public function process($image, $actions, $dir, $file);
+
+       /**
+        * Flip an image. Valid directions are horizontal and vertical.
+        *
+        * @param   integer   direction to flip
+        * @return  boolean
+        */
+       abstract function flip($direction);
+
+       /**
+        * Crop an image. Valid properties are: width, height, top, left.
+        *
+        * @param   array     new properties
+        * @return  boolean
+        */
+       abstract function crop($properties);
+
+       /**
+        * Resize an image. Valid properties are: width, height, and master.
+        *
+        * @param   array     new properties
+        * @return  boolean
+        */
+       abstract public function resize($properties);
+
+       /**
+        * Rotate an image. Valid amounts are -180 to 180.
+        *
+        * @param   integer   amount to rotate
+        * @return  boolean
+        */
+       abstract public function rotate($amount);
+
+       /**
+        * Sharpen and image. Valid amounts are 1 to 100.
+        *
+        * @param   integer  amount to sharpen
+        * @return  boolean
+        */
+       abstract public function sharpen($amount);
+
+} // End Image Driver
\ No newline at end of file
diff --git a/Server/system/libraries/drivers/Image/GD.php b/Server/system/libraries/drivers/Image/GD.php
new file mode 100644 (file)
index 0000000..b3610b3
--- /dev/null
@@ -0,0 +1,379 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * GD Image Driver.
+ *
+ * $Id: GD.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package    Image
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Image_GD_Driver extends Image_Driver {
+
+       // A transparent PNG as a string
+       protected static $blank_png;
+       protected static $blank_png_width;
+       protected static $blank_png_height;
+
+       public function __construct()
+       {
+               // Make sure that GD2 is available
+               if ( ! function_exists('gd_info'))
+                       throw new Kohana_Exception('image.gd.requires_v2');
+
+               // Get the GD information
+               $info = gd_info();
+
+               // Make sure that the GD2 is installed
+               if (strpos($info['GD Version'], '2.') === FALSE)
+                       throw new Kohana_Exception('image.gd.requires_v2');
+       }
+
+       public function process($image, $actions, $dir, $file, $render = FALSE)
+       {
+               // Set the "create" function
+               switch ($image['type'])
+               {
+                       case IMAGETYPE_JPEG:
+                               $create = 'imagecreatefromjpeg';
+                       break;
+                       case IMAGETYPE_GIF:
+                               $create = 'imagecreatefromgif';
+                       break;
+                       case IMAGETYPE_PNG:
+                               $create = 'imagecreatefrompng';
+                       break;
+               }
+
+               // Set the "save" function
+               switch (strtolower(substr(strrchr($file, '.'), 1)))
+               {
+                       case 'jpg':
+                       case 'jpeg':
+                               $save = 'imagejpeg';
+                       break;
+                       case 'gif':
+                               $save = 'imagegif';
+                       break;
+                       case 'png':
+                               $save = 'imagepng';
+                       break;
+               }
+
+               // Make sure the image type is supported for import
+               if (empty($create) OR ! function_exists($create))
+                       throw new Kohana_Exception('image.type_not_allowed', $image['file']);
+
+               // Make sure the image type is supported for saving
+               if (empty($save) OR ! function_exists($save))
+                       throw new Kohana_Exception('image.type_not_allowed', $dir.$file);
+
+               // Load the image
+               $this->image = $image;
+
+               // Create the GD image resource
+               $this->tmp_image = $create($image['file']);
+
+               // Get the quality setting from the actions
+               $quality = arr::remove('quality', $actions);
+
+               if ($status = $this->execute($actions))
+               {
+                       // Prevent the alpha from being lost
+                       imagealphablending($this->tmp_image, TRUE);
+                       imagesavealpha($this->tmp_image, TRUE);
+
+                       switch ($save)
+                       {
+                               case 'imagejpeg':
+                                       // Default the quality to 95
+                                       ($quality === NULL) and $quality = 95;
+                               break;
+                               case 'imagegif':
+                                       // Remove the quality setting, GIF doesn't use it
+                                       unset($quality);
+                               break;
+                               case 'imagepng':
+                                       // Always use a compression level of 9 for PNGs. This does not
+                                       // affect quality, it only increases the level of compression!
+                                       $quality = 9;
+                               break;
+                       }
+
+                       if ($render === FALSE)
+                       {
+                               // Set the status to the save return value, saving with the quality requested
+                               $status = isset($quality) ? $save($this->tmp_image, $dir.$file, $quality) : $save($this->tmp_image, $dir.$file);
+                       }
+                       else
+                       {
+                               // Output the image directly to the browser
+                               switch ($save)
+                               {
+                                       case 'imagejpeg':
+                                               header('Content-Type: image/jpeg');
+                                       break;
+                                       case 'imagegif':
+                                               header('Content-Type: image/gif');
+                                       break;
+                                       case 'imagepng':
+                                               header('Content-Type: image/png');
+                                       break;
+                               }
+
+                               $status = isset($quality) ? $save($this->tmp_image, NULL, $quality) : $save($this->tmp_image);
+                       }
+
+                       // Destroy the temporary image
+                       imagedestroy($this->tmp_image);
+               }
+
+               return $status;
+       }
+
+       public function flip($direction)
+       {
+               // Get the current width and height
+               $width = imagesx($this->tmp_image);
+               $height = imagesy($this->tmp_image);
+
+               // Create the flipped image
+               $flipped = $this->imagecreatetransparent($width, $height);
+
+               if ($direction === Image::HORIZONTAL)
+               {
+                       for ($x = 0; $x < $width; $x++)
+                       {
+                               $status = imagecopy($flipped, $this->tmp_image, $x, 0, $width - $x - 1, 0, 1, $height);
+                       }
+               }
+               elseif ($direction === Image::VERTICAL)
+               {
+                       for ($y = 0; $y < $height; $y++)
+                       {
+                               $status = imagecopy($flipped, $this->tmp_image, 0, $y, 0, $height - $y - 1, $width, 1);
+                       }
+               }
+               else
+               {
+                       // Do nothing
+                       return TRUE;
+               }
+
+               if ($status === TRUE)
+               {
+                       // Swap the new image for the old one
+                       imagedestroy($this->tmp_image);
+                       $this->tmp_image = $flipped;
+               }
+
+               return $status;
+       }
+
+       public function crop($properties)
+       {
+               // Sanitize the cropping settings
+               $this->sanitize_geometry($properties);
+
+               // Get the current width and height
+               $width = imagesx($this->tmp_image);
+               $height = imagesy($this->tmp_image);
+
+               // Create the temporary image to copy to
+               $img = $this->imagecreatetransparent($properties['width'], $properties['height']);
+
+               // Execute the crop
+               if ($status = imagecopyresampled($img, $this->tmp_image, 0, 0, $properties['left'], $properties['top'], $width, $height, $width, $height))
+               {
+                       // Swap the new image for the old one
+                       imagedestroy($this->tmp_image);
+                       $this->tmp_image = $img;
+               }
+
+               return $status;
+       }
+
+       public function resize($properties)
+       {
+               // Get the current width and height
+               $width = imagesx($this->tmp_image);
+               $height = imagesy($this->tmp_image);
+
+               if (substr($properties['width'], -1) === '%')
+               {
+                       // Recalculate the percentage to a pixel size
+                       $properties['width'] = round($width * (substr($properties['width'], 0, -1) / 100));
+               }
+
+               if (substr($properties['height'], -1) === '%')
+               {
+                       // Recalculate the percentage to a pixel size
+                       $properties['height'] = round($height * (substr($properties['height'], 0, -1) / 100));
+               }
+               
+               // Recalculate the width and height, if they are missing
+               empty($properties['width'])  and $properties['width']  = round($width * $properties['height'] / $height);
+               empty($properties['height']) and $properties['height'] = round($height * $properties['width'] / $width);
+               
+               if ($properties['master'] === Image::AUTO)
+               {
+                       // Change an automatic master dim to the correct type
+                       $properties['master'] = (($width / $properties['width']) > ($height / $properties['height'])) ? Image::WIDTH : Image::HEIGHT;
+               }
+
+               if (empty($properties['height']) OR $properties['master'] === Image::WIDTH)
+               {
+                       // Recalculate the height based on the width
+                       $properties['height'] = round($height * $properties['width'] / $width);
+               }
+
+               if (empty($properties['width']) OR $properties['master'] === Image::HEIGHT)
+               {
+                       // Recalculate the width based on the height
+                       $properties['width'] = round($width * $properties['height'] / $height);
+               }
+
+               // Test if we can do a resize without resampling to speed up the final resize
+               if ($properties['width'] > $width / 2 AND $properties['height'] > $height / 2)
+               {
+                       // Presize width and height
+                       $pre_width = $width;
+                       $pre_height = $height;
+
+                       // The maximum reduction is 10% greater than the final size
+                       $max_reduction_width  = round($properties['width']  * 1.1);
+                       $max_reduction_height = round($properties['height'] * 1.1);
+
+                       // Reduce the size using an O(2n) algorithm, until it reaches the maximum reduction
+                       while ($pre_width / 2 > $max_reduction_width AND $pre_height / 2 > $max_reduction_height)
+                       {
+                               $pre_width /= 2;
+                               $pre_height /= 2;
+                       }
+
+                       // Create the temporary image to copy to
+                       $img = $this->imagecreatetransparent($pre_width, $pre_height);
+
+                       if ($status = imagecopyresized($img, $this->tmp_image, 0, 0, 0, 0, $pre_width, $pre_height, $width, $height))
+                       {
+                               // Swap the new image for the old one
+                               imagedestroy($this->tmp_image);
+                               $this->tmp_image = $img;
+                       }
+
+                       // Set the width and height to the presize
+                       $width  = $pre_width;
+                       $height = $pre_height;
+               }
+
+               // Create the temporary image to copy to
+               $img = $this->imagecreatetransparent($properties['width'], $properties['height']);
+
+               // Execute the resize
+               if ($status = imagecopyresampled($img, $this->tmp_image, 0, 0, 0, 0, $properties['width'], $properties['height'], $width, $height))
+               {
+                       // Swap the new image for the old one
+                       imagedestroy($this->tmp_image);
+                       $this->tmp_image = $img;
+               }
+
+               return $status;
+       }
+
+       public function rotate($amount)
+       {
+               // Use current image to rotate
+               $img = $this->tmp_image;
+
+               // White, with an alpha of 0
+               $transparent = imagecolorallocatealpha($img, 255, 255, 255, 127);
+
+               // Rotate, setting the transparent color
+               $img = imagerotate($img, 360 - $amount, $transparent, -1);
+
+               // Fill the background with the transparent "color"
+               imagecolortransparent($img, $transparent);
+
+               // Merge the images
+               if ($status = imagecopymerge($this->tmp_image, $img, 0, 0, 0, 0, imagesx($this->tmp_image), imagesy($this->tmp_image), 100))
+               {
+                       // Prevent the alpha from being lost
+                       imagealphablending($img, TRUE);
+                       imagesavealpha($img, TRUE);
+
+                       // Swap the new image for the old one
+                       imagedestroy($this->tmp_image);
+                       $this->tmp_image = $img;
+               }
+
+               return $status;
+       }
+
+       public function sharpen($amount)
+       {
+               // Make sure that the sharpening function is available
+               if ( ! function_exists('imageconvolution'))
+                       throw new Kohana_Exception('image.unsupported_method', __FUNCTION__);
+
+               // Amount should be in the range of 18-10
+               $amount = round(abs(-18 + ($amount * 0.08)), 2);
+
+               // Gaussian blur matrix
+               $matrix = array
+               (
+                       array(-1,   -1,    -1),
+                       array(-1, $amount, -1),
+                       array(-1,   -1,    -1),
+               );
+
+               // Perform the sharpen
+               return imageconvolution($this->tmp_image, $matrix, $amount - 8, 0);
+       }
+
+       protected function properties()
+       {
+               return array(imagesx($this->tmp_image), imagesy($this->tmp_image));
+       }
+
+       /**
+        * Returns an image with a transparent background. Used for rotating to
+        * prevent unfilled backgrounds.
+        *
+        * @param   integer  image width
+        * @param   integer  image height
+        * @return  resource
+        */
+       protected function imagecreatetransparent($width, $height)
+       {
+               if (self::$blank_png === NULL)
+               {
+                       // Decode the blank PNG if it has not been done already
+                       self::$blank_png = imagecreatefromstring(base64_decode
+                       (
+                               'iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29'.
+                               'mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAADqSURBVHjaYvz//z/DYAYAAcTEMMgBQAANegcCBN'.
+                               'CgdyBAAA16BwIE0KB3IEAADXoHAgTQoHcgQAANegcCBNCgdyBAAA16BwIE0KB3IEAADXoHAgTQoHcgQ'.
+                               'AANegcCBNCgdyBAAA16BwIE0KB3IEAADXoHAgTQoHcgQAANegcCBNCgdyBAAA16BwIE0KB3IEAADXoH'.
+                               'AgTQoHcgQAANegcCBNCgdyBAAA16BwIE0KB3IEAADXoHAgTQoHcgQAANegcCBNCgdyBAAA16BwIE0KB'.
+                               '3IEAADXoHAgTQoHcgQAANegcCBNCgdyBAgAEAMpcDTTQWJVEAAAAASUVORK5CYII='
+                       ));
+
+                       // Set the blank PNG width and height
+                       self::$blank_png_width = imagesx(self::$blank_png);
+                       self::$blank_png_height = imagesy(self::$blank_png);
+               }
+
+               $img = imagecreatetruecolor($width, $height);
+
+               // Resize the blank image
+               imagecopyresized($img, self::$blank_png, 0, 0, 0, 0, $width, $height, self::$blank_png_width, self::$blank_png_height);
+
+               // Prevent the alpha from being lost
+               imagealphablending($img, FALSE);
+               imagesavealpha($img, TRUE);
+
+               return $img;
+       }
+
+} // End Image GD Driver
\ No newline at end of file
diff --git a/Server/system/libraries/drivers/Image/GraphicsMagick.php b/Server/system/libraries/drivers/Image/GraphicsMagick.php
new file mode 100644 (file)
index 0000000..8840eb8
--- /dev/null
@@ -0,0 +1,211 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * GraphicsMagick Image Driver.
+ *
+ * @package    Image
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Image_GraphicsMagick_Driver extends Image_Driver {
+
+       // Directory that GM is installed in
+       protected $dir = '';
+
+       // Command extension (exe for windows)
+       protected $ext = '';
+
+       // Temporary image filename
+       protected $tmp_image;
+
+       /**
+        * Attempts to detect the GraphicsMagick installation directory.
+        *
+        * @throws  Kohana_Exception
+        * @param   array   configuration
+        * @return  void
+        */
+       public function __construct($config)
+       {
+               if (empty($config['directory']))
+               {
+                       // Attempt to locate GM by using "which" (only works for *nix!)
+                       if ( ! is_file($path = exec('which gm')))
+                               throw new Kohana_Exception('image.graphicsmagick.not_found');
+
+                       $config['directory'] = dirname($path);
+               }
+
+               // Set the command extension
+               $this->ext = (PHP_SHLIB_SUFFIX === 'dll') ? '.exe' : '';
+
+               // Check to make sure the provided path is correct
+               if ( ! is_file(realpath($config['directory']).'/gm'.$this->ext))
+                       throw new Kohana_Exception('image.graphicsmagick.not_found', 'gm'.$this->ext);
+
+
+               // Set the installation directory
+               $this->dir = str_replace('\\', '/', realpath($config['directory'])).'/';
+       }
+
+       /**
+        * Creates a temporary image and executes the given actions. By creating a
+        * temporary copy of the image before manipulating it, this process is atomic.
+        */
+       public function process($image, $actions, $dir, $file, $render = FALSE)
+       {
+               // We only need the filename
+               $image = $image['file'];
+
+               // Unique temporary filename
+               $this->tmp_image = $dir.'k2img--'.sha1(time().$dir.$file).substr($file, strrpos($file, '.'));
+
+               // Copy the image to the temporary file
+               copy($image, $this->tmp_image);
+
+               // Quality change is done last
+               $quality = (int) arr::remove('quality', $actions);
+
+               // Use 95 for the default quality
+               empty($quality) and $quality = 95;
+
+               // All calls to these will need to be escaped, so do it now
+               $this->cmd_image = escapeshellarg($this->tmp_image);
+               $this->new_image = ($render)? $this->cmd_image : escapeshellarg($dir.$file);
+
+               if ($status = $this->execute($actions))
+               {
+                       // Use convert to change the image into its final version. This is
+                       // done to allow the file type to change correctly, and to handle
+                       // the quality conversion in the most effective way possible.
+                       if ($error = exec(escapeshellcmd($this->dir.'gm'.$this->ext.' convert').' -quality '.$quality.'% '.$this->cmd_image.' '.$this->new_image))
+                       {
+                               $this->errors[] = $error;
+                       }
+                       else
+                       {
+                               // Output the image directly to the browser
+                               if ($render !== FALSE)
+                               {
+                                       $contents = file_get_contents($this->tmp_image);
+                                       switch (substr($file, strrpos($file, '.') + 1))
+                                       {
+                                               case 'jpg':
+                                               case 'jpeg':
+                                                       header('Content-Type: image/jpeg');
+                                               break;
+                                               case 'gif':
+                                                       header('Content-Type: image/gif');
+                                               break;
+                                               case 'png':
+                                                       header('Content-Type: image/png');
+                                               break;
+                                       }
+                                       echo $contents;
+                               }
+                       }
+               }
+
+               // Remove the temporary image
+               unlink($this->tmp_image);
+               $this->tmp_image = '';
+
+               return $status;
+       }
+
+       public function crop($prop)
+       {
+               // Sanitize and normalize the properties into geometry
+               $this->sanitize_geometry($prop);
+
+               // Set the IM geometry based on the properties
+               $geometry = escapeshellarg($prop['width'].'x'.$prop['height'].'+'.$prop['left'].'+'.$prop['top']);
+
+               if ($error = exec(escapeshellcmd($this->dir.'gm'.$this->ext.' convert').' -crop '.$geometry.' '.$this->cmd_image.' '.$this->cmd_image))
+               {
+                       $this->errors[] = $error;
+                       return FALSE;
+               }
+
+               return TRUE;
+       }
+
+       public function flip($dir)
+       {
+               // Convert the direction into a GM command
+               $dir = ($dir === Image::HORIZONTAL) ? '-flop' : '-flip';
+
+               if ($error = exec(escapeshellcmd($this->dir.'gm'.$this->ext.' convert').' '.$dir.' '.$this->cmd_image.' '.$this->cmd_image))
+               {
+                       $this->errors[] = $error;
+                       return FALSE;
+               }
+
+               return TRUE;
+       }
+
+       public function resize($prop)
+       {
+               switch ($prop['master'])
+               {
+                       case Image::WIDTH:  // Wx
+                               $dim = escapeshellarg($prop['width'].'x');
+                       break;
+                       case Image::HEIGHT: // xH
+                               $dim = escapeshellarg('x'.$prop['height']);
+                       break;
+                       case Image::AUTO:   // WxH
+                               $dim = escapeshellarg($prop['width'].'x'.$prop['height']);
+                       break;
+                       case Image::NONE:   // WxH!
+                               $dim = escapeshellarg($prop['width'].'x'.$prop['height'].'!');
+                       break;
+               }
+
+               // Use "convert" to change the width and height
+               if ($error = exec(escapeshellcmd($this->dir.'gm'.$this->ext.' convert').' -resize '.$dim.' '.$this->cmd_image.' '.$this->cmd_image))
+               {
+                       $this->errors[] = $error;
+                       return FALSE;
+               }
+
+               return TRUE;
+       }
+
+       public function rotate($amt)
+       {
+               if ($error = exec(escapeshellcmd($this->dir.'gm'.$this->ext.' convert').' -rotate '.escapeshellarg($amt).' -background transparent '.$this->cmd_image.' '.$this->cmd_image))
+               {
+                       $this->errors[] = $error;
+                       return FALSE;
+               }
+
+               return TRUE;
+       }
+
+       public function sharpen($amount)
+       {
+               // Set the sigma, radius, and amount. The amount formula allows a nice
+               // spread between 1 and 100 without pixelizing the image badly.
+               $sigma  = 0.5;
+               $radius = $sigma * 2;
+               $amount = round(($amount / 80) * 3.14, 2);
+
+               // Convert the amount to an GM command
+               $sharpen = escapeshellarg($radius.'x'.$sigma.'+'.$amount.'+0');
+
+               if ($error = exec(escapeshellcmd($this->dir.'gm'.$this->ext.' convert').' -unsharp '.$sharpen.' '.$this->cmd_image.' '.$this->cmd_image))
+               {
+                       $this->errors[] = $error;
+                       return FALSE;
+               }
+
+               return TRUE;
+       }
+
+       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/Server/system/libraries/drivers/Image/ImageMagick.php b/Server/system/libraries/drivers/Image/ImageMagick.php
new file mode 100644 (file)
index 0000000..5a86663
--- /dev/null
@@ -0,0 +1,212 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * ImageMagick Image Driver.
+ *
+ * $Id: ImageMagick.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package    Image
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Image_ImageMagick_Driver extends Image_Driver {
+
+       // Directory that IM is installed in
+       protected $dir = '';
+
+       // Command extension (exe for windows)
+       protected $ext = '';
+
+       // Temporary image filename
+       protected $tmp_image;
+
+       /**
+        * Attempts to detect the ImageMagick installation directory.
+        *
+        * @throws  Kohana_Exception
+        * @param   array   configuration
+        * @return  void
+        */
+       public function __construct($config)
+       {
+               if (empty($config['directory']))
+               {
+                       // Attempt to locate IM by using "which" (only works for *nix!)
+                       if ( ! is_file($path = exec('which convert')))
+                               throw new Kohana_Exception('image.imagemagick.not_found');
+
+                       $config['directory'] = dirname($path);
+               }
+
+               // Set the command extension
+               $this->ext = (PHP_SHLIB_SUFFIX === 'dll') ? '.exe' : '';
+
+               // Check to make sure the provided path is correct
+               if ( ! is_file(realpath($config['directory']).'/convert'.$this->ext))
+                       throw new Kohana_Exception('image.imagemagick.not_found', 'convert'.$this->ext);
+
+               // Set the installation directory
+               $this->dir = str_replace('\\', '/', realpath($config['directory'])).'/';
+       }
+
+       /**
+        * Creates a temporary image and executes the given actions. By creating a
+        * temporary copy of the image before manipulating it, this process is atomic.
+        */
+       public function process($image, $actions, $dir, $file, $render = FALSE)
+       {
+               // We only need the filename
+               $image = $image['file'];
+
+               // Unique temporary filename
+               $this->tmp_image = $dir.'k2img--'.sha1(time().$dir.$file).substr($file, strrpos($file, '.'));
+
+               // Copy the image to the temporary file
+               copy($image, $this->tmp_image);
+
+               // Quality change is done last
+               $quality = (int) arr::remove('quality', $actions);
+
+               // Use 95 for the default quality
+               empty($quality) and $quality = 95;
+
+               // All calls to these will need to be escaped, so do it now
+               $this->cmd_image = escapeshellarg($this->tmp_image);
+               $this->new_image = ($render)? $this->cmd_image : escapeshellarg($dir.$file);
+
+               if ($status = $this->execute($actions))
+               {
+                       // Use convert to change the image into its final version. This is
+                       // done to allow the file type to change correctly, and to handle
+                       // the quality conversion in the most effective way possible.
+                       if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -quality '.$quality.'% '.$this->cmd_image.' '.$this->new_image))
+                       {
+                               $this->errors[] = $error;
+                       }
+                       else
+                       {
+                               // Output the image directly to the browser
+                               if ($render !== FALSE)
+                               {
+                                       $contents = file_get_contents($this->tmp_image);
+                                       switch (substr($file, strrpos($file, '.') + 1))
+                                       {
+                                               case 'jpg':
+                                               case 'jpeg':
+                                                       header('Content-Type: image/jpeg');
+                                               break;
+                                               case 'gif':
+                                                       header('Content-Type: image/gif');
+                                               break;
+                                               case 'png':
+                                                       header('Content-Type: image/png');
+                                               break;
+                                       }
+                                       echo $contents;
+                               }
+                       }
+               }
+
+               // Remove the temporary image
+               unlink($this->tmp_image);
+               $this->tmp_image = '';
+
+               return $status;
+       }
+
+       public function crop($prop)
+       {
+               // Sanitize and normalize the properties into geometry
+               $this->sanitize_geometry($prop);
+
+               // Set the IM geometry based on the properties
+               $geometry = escapeshellarg($prop['width'].'x'.$prop['height'].'+'.$prop['left'].'+'.$prop['top']);
+
+               if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -crop '.$geometry.' '.$this->cmd_image.' '.$this->cmd_image))
+               {
+                       $this->errors[] = $error;
+                       return FALSE;
+               }
+
+               return TRUE;
+       }
+
+       public function flip($dir)
+       {
+               // Convert the direction into a IM command
+               $dir = ($dir === Image::HORIZONTAL) ? '-flop' : '-flip';
+
+               if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' '.$dir.' '.$this->cmd_image.' '.$this->cmd_image))
+               {
+                       $this->errors[] = $error;
+                       return FALSE;
+               }
+
+               return TRUE;
+       }
+
+       public function resize($prop)
+       {
+               switch ($prop['master'])
+               {
+                       case Image::WIDTH:  // Wx
+                               $dim = escapeshellarg($prop['width'].'x');
+                       break;
+                       case Image::HEIGHT: // xH
+                               $dim = escapeshellarg('x'.$prop['height']);
+                       break;
+                       case Image::AUTO:   // WxH
+                               $dim = escapeshellarg($prop['width'].'x'.$prop['height']);
+                       break;
+                       case Image::NONE:   // WxH!
+                               $dim = escapeshellarg($prop['width'].'x'.$prop['height'].'!');
+                       break;
+               }
+
+               // Use "convert" to change the width and height
+               if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -resize '.$dim.' '.$this->cmd_image.' '.$this->cmd_image))
+               {
+                       $this->errors[] = $error;
+                       return FALSE;
+               }
+
+               return TRUE;
+       }
+
+       public function rotate($amt)
+       {
+               if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -rotate '.escapeshellarg($amt).' -background transparent '.$this->cmd_image.' '.$this->cmd_image))
+               {
+                       $this->errors[] = $error;
+                       return FALSE;
+               }
+
+               return TRUE;
+       }
+
+       public function sharpen($amount)
+       {
+               // Set the sigma, radius, and amount. The amount formula allows a nice
+               // spread between 1 and 100 without pixelizing the image badly.
+               $sigma  = 0.5;
+               $radius = $sigma * 2;
+               $amount = round(($amount / 80) * 3.14, 2);
+
+               // Convert the amount to an IM command
+               $sharpen = escapeshellarg($radius.'x'.$sigma.'+'.$amount.'+0');
+
+               if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -unsharp '.$sharpen.' '.$this->cmd_image.' '.$this->cmd_image))
+               {
+                       $this->errors[] = $error;
+                       return FALSE;
+               }
+
+               return TRUE;
+       }
+
+       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/Server/system/libraries/drivers/Session.php b/Server/system/libraries/drivers/Session.php
new file mode 100644 (file)
index 0000000..fb58c8d
--- /dev/null
@@ -0,0 +1,70 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Session driver interface
+ *
+ * $Id: Session.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+interface Session_Driver {
+
+       /**
+        * Opens a session.
+        *
+        * @param   string   save path
+        * @param   string   session name
+        * @return  boolean
+        */
+       public function open($path, $name);
+
+       /**
+        * Closes a session.
+        *
+        * @return  boolean
+        */
+       public function close();
+
+       /**
+        * Reads a session.
+        *
+        * @param   string  session id
+        * @return  string
+        */
+       public function read($id);
+
+       /**
+        * Writes a session.
+        *
+        * @param   string   session id
+        * @param   string   session data
+        * @return  boolean
+        */
+       public function write($id, $data);
+
+       /**
+        * Destroys a session.
+        *
+        * @param   string   session id
+        * @return  boolean
+        */
+       public function destroy($id);
+
+       /**
+        * Regenerates the session id.
+        *
+        * @return  string
+        */
+       public function regenerate();
+
+       /**
+        * Garbage collection.
+        *
+        * @param   integer  session expiration period
+        * @return  boolean
+        */
+       public function gc($maxlifetime);
+
+} // End Session Driver Interface
\ No newline at end of file
diff --git a/Server/system/libraries/drivers/Session/Cache.php b/Server/system/libraries/drivers/Session/Cache.php
new file mode 100644 (file)
index 0000000..7221c9f
--- /dev/null
@@ -0,0 +1,105 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Session cache driver.
+ *
+ * Cache library config goes in the session.storage config entry:
+ * $config['storage'] = array(
+ *     'driver' => 'apc',
+ *     'requests' => 10000
+ * );
+ * Lifetime does not need to be set as it is
+ * overridden by the session expiration setting.
+ *
+ * $Id: Cache.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Session_Cache_Driver implements Session_Driver {
+
+       protected $cache;
+       protected $encrypt;
+
+       public function __construct()
+       {
+               // Load Encrypt library
+               if (Kohana::config('session.encryption'))
+               {
+                       $this->encrypt = new Encrypt;
+               }
+
+               Kohana::log('debug', 'Session Cache Driver Initialized');
+       }
+
+       public function open($path, $name)
+       {
+               $config = Kohana::config('session.storage');
+
+               if (empty($config))
+               {
+                       // Load the default group
+                       $config = Kohana::config('cache.default');
+               }
+               elseif (is_string($config))
+               {
+                       $name = $config;
+
+                       // Test the config group name
+                       if (($config = Kohana::config('cache.'.$config)) === NULL)
+                               throw new Kohana_Exception('cache.undefined_group', $name);
+               }
+
+               $config['lifetime'] = (Kohana::config('session.expiration') == 0) ? 86400 : Kohana::config('session.expiration');
+               $this->cache = new Cache($config);
+
+               return is_object($this->cache);
+       }
+
+       public function close()
+       {
+               return TRUE;
+       }
+
+       public function read($id)
+       {
+               $id = 'session_'.$id;
+               if ($data = $this->cache->get($id))
+               {
+                       return Kohana::config('session.encryption') ? $this->encrypt->decode($data) : $data;
+               }
+
+               // Return value must be string, NOT a boolean
+               return '';
+       }
+
+       public function write($id, $data)
+       {
+               $id = 'session_'.$id;
+               $data = Kohana::config('session.encryption') ? $this->encrypt->encode($data) : $data;
+
+               return $this->cache->set($id, $data);
+       }
+
+       public function destroy($id)
+       {
+               $id = 'session_'.$id;
+               return $this->cache->delete($id);
+       }
+
+       public function regenerate()
+       {
+               session_regenerate_id(TRUE);
+
+               // Return new session id
+               return session_id();
+       }
+
+       public function gc($maxlifetime)
+       {
+               // Just return, caches are automatically cleaned up
+               return TRUE;
+       }
+
+} // End Session Cache Driver
diff --git a/Server/system/libraries/drivers/Session/Cookie.php b/Server/system/libraries/drivers/Session/Cookie.php
new file mode 100644 (file)
index 0000000..7b79106
--- /dev/null
@@ -0,0 +1,80 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Session cookie driver.
+ *
+ * $Id: Cookie.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Session_Cookie_Driver implements Session_Driver {
+
+       protected $cookie_name;
+       protected $encrypt; // Library
+
+       public function __construct()
+       {
+               $this->cookie_name = Kohana::config('session.name').'_data';
+
+               if (Kohana::config('session.encryption'))
+               {
+                       $this->encrypt = Encrypt::instance();
+               }
+
+               Kohana::log('debug', 'Session Cookie Driver Initialized');
+       }
+
+       public function open($path, $name)
+       {
+               return TRUE;
+       }
+
+       public function close()
+       {
+               return TRUE;
+       }
+
+       public function read($id)
+       {
+               $data = (string) cookie::get($this->cookie_name);
+
+               if ($data == '')
+                       return $data;
+
+               return empty($this->encrypt) ? base64_decode($data) : $this->encrypt->decode($data);
+       }
+
+       public function write($id, $data)
+       {
+               $data = empty($this->encrypt) ? base64_encode($data) : $this->encrypt->encode($data);
+
+               if (strlen($data) > 4048)
+               {
+                       Kohana::log('error', 'Session ('.$id.') data exceeds the 4KB limit, ignoring write.');
+                       return FALSE;
+               }
+
+               return cookie::set($this->cookie_name, $data, Kohana::config('session.expiration'));
+       }
+
+       public function destroy($id)
+       {
+               return cookie::delete($this->cookie_name);
+       }
+
+       public function regenerate()
+       {
+               session_regenerate_id(TRUE);
+
+               // Return new id
+               return session_id();
+       }
+
+       public function gc($maxlifetime)
+       {
+               return TRUE;
+       }
+
+} // End Session Cookie Driver Class
\ No newline at end of file
diff --git a/Server/system/libraries/drivers/Session/Database.php b/Server/system/libraries/drivers/Session/Database.php
new file mode 100644 (file)
index 0000000..b4144ff
--- /dev/null
@@ -0,0 +1,163 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Session database driver.
+ *
+ * $Id: Database.php 3769 2008-12-15 00:48:56Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Session_Database_Driver implements Session_Driver {
+
+       /*
+       CREATE TABLE sessions
+       (
+               session_id VARCHAR(127) NOT NULL,
+               last_activity INT(10) UNSIGNED NOT NULL,
+               data TEXT NOT NULL,
+               PRIMARY KEY (session_id)
+       );
+       */
+
+       // Database settings
+       protected $db = 'default';
+       protected $table = 'sessions';
+
+       // Encryption
+       protected $encrypt;
+
+       // Session settings
+       protected $session_id;
+       protected $written = FALSE;
+
+       public function __construct()
+       {
+               // Load configuration
+               $config = Kohana::config('session');
+
+               if ( ! empty($config['encryption']))
+               {
+                       // Load encryption
+                       $this->encrypt = Encrypt::instance();
+               }
+
+               if (is_array($config['storage']))
+               {
+                       if ( ! empty($config['storage']['group']))
+                       {
+                               // Set the group name
+                               $this->db = $config['storage']['group'];
+                       }
+
+                       if ( ! empty($config['storage']['table']))
+                       {
+                               // Set the table name
+                               $this->table = $config['storage']['table'];
+                       }
+               }
+
+               // Load database
+               $this->db = Database::instance($this->db);
+
+               Kohana::log('debug', 'Session Database Driver Initialized');
+       }
+
+       public function open($path, $name)
+       {
+               return TRUE;
+       }
+
+       public function close()
+       {
+               return TRUE;
+       }
+
+       public function read($id)
+       {
+               // Load the session
+               $query = $this->db->from($this->table)->where('session_id', $id)->limit(1)->get()->result(TRUE);
+
+               if ($query->count() === 0)
+               {
+                       // No current session
+                       $this->session_id = NULL;
+
+                       return '';
+               }
+
+               // Set the current session id
+               $this->session_id = $id;
+
+               // Load the data
+               $data = $query->current()->data;
+
+               return ($this->encrypt === NULL) ? base64_decode($data) : $this->encrypt->decode($data);
+       }
+
+       public function write($id, $data)
+       {
+               $data = array
+               (
+                       'session_id' => $id,
+                       'last_activity' => time(),
+                       'data' => ($this->encrypt === NULL) ? base64_encode($data) : $this->encrypt->encode($data)
+               );
+
+               if ($this->session_id === NULL)
+               {
+                       // Insert a new session
+                       $query = $this->db->insert($this->table, $data);
+               }
+               elseif ($id === $this->session_id)
+               {
+                       // Do not update the session_id
+                       unset($data['session_id']);
+
+                       // Update the existing session
+                       $query = $this->db->update($this->table, $data, array('session_id' => $id));
+               }
+               else
+               {
+                       // Update the session and id
+                       $query = $this->db->update($this->table, $data, array('session_id' => $this->session_id));
+
+                       // Set the new session id
+                       $this->session_id = $id;
+               }
+
+               return (bool) $query->count();
+       }
+
+       public function destroy($id)
+       {
+               // Delete the requested session
+               $this->db->delete($this->table, array('session_id' => $id));
+
+               // Session id is no longer valid
+               $this->session_id = NULL;
+
+               return TRUE;
+       }
+
+       public function regenerate()
+       {
+               // Generate a new session id
+               session_regenerate_id();
+
+               // Return new session id
+               return session_id();
+       }
+
+       public function gc($maxlifetime)
+       {
+               // Delete all expired sessions
+               $query = $this->db->delete($this->table, array('last_activity <' => time() - $maxlifetime));
+
+               Kohana::log('debug', 'Session garbage collected: '.$query->count().' row(s) deleted.');
+
+               return TRUE;
+       }
+
+} // End Session Database Driver
diff --git a/Server/system/views/kohana/template.php b/Server/system/views/kohana/template.php
new file mode 100644 (file)
index 0000000..b090fd8
--- /dev/null
@@ -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::specialchars($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::specialchars($title) ?></h1>
+       <?php echo $content ?>
+
+       <p class="copyright">
+               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/Server/system/views/kohana_calendar.php b/Server/system/views/kohana_calendar.php
new file mode 100644 (file)
index 0000000..39545e2
--- /dev/null
@@ -0,0 +1,52 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+// Get the day names
+$days = Calendar::days(2);
+
+// Previous and next month timestamps
+$next = mktime(0, 0, 0, $month + 1, 1, $year);
+$prev = mktime(0, 0, 0, $month - 1, 1, $year);
+
+// Import the GET query array locally and remove the day
+$qs = $_GET;
+unset($qs['day']);
+
+// Previous and next month query URIs
+$prev = Router::$current_uri.'?'.http_build_query(array_merge($qs, array('month' => date('n', $prev), 'year' => date('Y', $prev))));
+$next = Router::$current_uri.'?'.http_build_query(array_merge($qs, array('month' => date('n', $next), 'year' => date('Y', $next))));
+
+?>
+<table class="calendar">
+<tr class="controls">
+<td class="prev"><?php echo html::anchor($prev, '&laquo;') ?></td>
+<td class="title" colspan="5"><?php echo strftime('%B %Y', mktime(0, 0, 0, $month, 1, $year)) ?></td>
+<td class="next"><?php echo html::anchor($next, '&raquo;') ?></td>
+</tr>
+<tr>
+<?php foreach ($days as $day): ?>
+<th><?php echo $day ?></th>
+<?php endforeach ?>
+</tr>
+<?php foreach ($weeks as $week): ?>
+<tr>
+<?php foreach ($week as $day):
+
+list ($number, $current, $data) = $day;
+
+if (is_array($data))
+{
+       $classes = $data['classes'];
+       $output = empty($data['output']) ? '' : '<ul class="output"><li>'.implode('</li><li>', $data['output']).'</li></ul>';
+}
+else
+{
+       $classes = array();
+       $output = '';
+}
+
+?>
+<td class="<?php echo implode(' ', $classes) ?>"><span class="day"><?php echo $day[0] ?></span><?php echo $output ?></td>
+<?php endforeach ?>
+</tr>
+<?php endforeach ?>
+</table>
diff --git a/Server/system/views/kohana_error_disabled.php b/Server/system/views/kohana_error_disabled.php
new file mode 100644 (file)
index 0000000..cd91132
--- /dev/null
@@ -0,0 +1,17 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.'); ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<style type="text/css">
+<?php include Kohana::find_file('views', 'kohana_errors', FALSE, 'css') ?>
+</style>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<title><?php echo $error ?></title>
+</head>
+<body>
+<div id="framework_error" style="width:24em;margin:50px auto;">
+<h3><?php echo html::specialchars($error) ?></h3>
+<p style="text-align:center"><?php echo $message ?></p>
+</div>
+</body>
+</html>
\ No newline at end of file
diff --git a/Server/system/views/kohana_error_page.php b/Server/system/views/kohana_error_page.php
new file mode 100644 (file)
index 0000000..944064c
--- /dev/null
@@ -0,0 +1,27 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.'); ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<style type="text/css">
+<?php include Kohana::find_file('views', 'kohana_errors', FALSE, 'css') ?>
+</style>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<title><?php echo $error ?></title>
+<base href="http://php.net/" />
+</head>
+<body>
+<div id="framework_error" style="width:42em;margin:20px auto;">
+<h3><?php echo html::specialchars($error) ?></h3>
+<p><?php echo html::specialchars($description) ?></p>
+<?php if ( ! empty($line) AND ! empty($file)): ?>
+<p><?php echo Kohana::lang('core.error_file_line', $file, $line) ?></p>
+<?php endif ?>
+<p><code class="block"><?php echo $message ?></code></p>
+<?php if ( ! empty($trace)): ?>
+<h3><?php echo Kohana::lang('core.stack_trace') ?></h3>
+<?php echo $trace ?>
+<?php endif ?>
+<p class="stats"><?php echo Kohana::lang('core.stats_footer') ?></p>
+</div>
+</body>
+</html>
\ No newline at end of file
diff --git a/Server/system/views/kohana_errors.css b/Server/system/views/kohana_errors.css
new file mode 100644 (file)
index 0000000..1341f57
--- /dev/null
@@ -0,0 +1,21 @@
+div#framework_error { background:#fff; border:solid 1px #ccc; font-family:sans-serif; color:#111; font-size:14px; line-height:130%; }
+div#framework_error h3 { color:#fff; font-size:16px; padding:8px 6px; margin:0 0 8px; background:#f15a00; text-align:center; }
+div#framework_error a { color:#228; text-decoration:none; }
+div#framework_error a:hover { text-decoration:underline; }
+div#framework_error strong { color:#900; }
+div#framework_error p { margin:0; padding:4px 6px 10px; }
+div#framework_error tt,
+div#framework_error pre,
+div#framework_error code { font-family:monospace; padding:2px 4px; font-size:12px; color:#333;
+       white-space:pre-wrap; /* CSS 2.1 */
+       white-space:-moz-pre-wrap; /* For Mozilla */
+       word-wrap:break-word; /* For IE5.5+ */
+}
+div#framework_error tt { font-style:italic; }
+div#framework_error tt:before { content:">"; color:#aaa; }
+div#framework_error code tt:before { content:""; }
+div#framework_error pre,
+div#framework_error code { background:#eaeee5; border:solid 0 #D6D8D1; border-width:0 1px 1px 0; }
+div#framework_error .block { display:block; text-align:left; }
+div#framework_error .stats { padding:4px; background: #eee; border-top:solid 1px #ccc; text-align:center; font-size:10px; color:#888; }
+div#framework_error .backtrace { margin:0; padding:0 6px; list-style:none; line-height:12px; }
\ No newline at end of file
diff --git a/Server/system/views/kohana_profiler.php b/Server/system/views/kohana_profiler.php
new file mode 100644 (file)
index 0000000..da77a66
--- /dev/null
@@ -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">Profiler executed in <?php echo number_format($execution_time, 3) ?>s</p>
+</div>
\ No newline at end of file
diff --git a/Server/system/views/kohana_profiler_table.css b/Server/system/views/kohana_profiler_table.css
new file mode 100644 (file)
index 0000000..41a1c9a
--- /dev/null
@@ -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/Server/system/views/kohana_profiler_table.php b/Server/system/views/kohana_profiler_table.php
new file mode 100644 (file)
index 0000000..b6b4653
--- /dev/null
@@ -0,0 +1,25 @@
+<?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>'.html::specialchars(print_r($value, TRUE)).'</pre>' : html::specialchars($value);
+                       echo '<td', $style, $class, '>', $value, '</td>';
+               }
+               ?>
+       </tr>
+<?php
+
+endforeach;
+?>
+</table>
\ No newline at end of file
diff --git a/Server/system/views/pagination/classic.php b/Server/system/views/pagination/classic.php
new file mode 100644 (file)
index 0000000..5272c2c
--- /dev/null
@@ -0,0 +1,39 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Classic pagination style
+ * 
+ * @preview  ‹ First  < 1 2 3 >  Last ›
+ */
+?>
+
+<p class="pagination">
+
+       <?php if ($first_page): ?>
+               <a href="<?php echo str_replace('{page}', 1, $url) ?>">&lsaquo;&nbsp;<?php echo Kohana::lang('pagination.first') ?></a>
+       <?php endif ?>
+
+       <?php if ($previous_page): ?>
+               <a href="<?php echo str_replace('{page}', $previous_page, $url) ?>">&lt;</a>
+       <?php endif ?>
+
+
+       <?php for ($i = 1; $i <= $total_pages; $i++): ?>
+
+               <?php if ($i == $current_page): ?>
+                       <strong><?php echo $i ?></strong>
+               <?php else: ?>
+                       <a href="<?php echo str_replace('{page}', $i, $url) ?>"><?php echo $i ?></a>
+               <?php endif ?>
+
+       <?php endfor ?>
+
+
+       <?php if ($next_page): ?>
+               <a href="<?php echo str_replace('{page}', $next_page, $url) ?>">&gt;</a>
+       <?php endif ?>
+
+       <?php if ($last_page): ?>
+               <a href="<?php echo str_replace('{page}', $last_page, $url) ?>"><?php echo Kohana::lang('pagination.last') ?>&nbsp;&rsaquo;</a>
+       <?php endif ?>
+
+</p>
\ No newline at end of file
diff --git a/Server/system/views/pagination/digg.php b/Server/system/views/pagination/digg.php
new file mode 100644 (file)
index 0000000..0e065a6
--- /dev/null
@@ -0,0 +1,83 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Digg pagination style
+ * 
+ * @preview  « Previous  1 2 … 5 6 7 8 9 10 11 12 13 14 … 25 26  Next »
+ */
+?>
+
+<p class="pagination">
+
+       <?php if ($previous_page): ?>
+               <a href="<?php echo str_replace('{page}', $previous_page, $url) ?>">&laquo;&nbsp;<?php echo Kohana::lang('pagination.previous') ?></a>
+       <?php else: ?>
+               &laquo;&nbsp;<?php echo Kohana::lang('pagination.previous') ?>
+       <?php endif ?>
+
+
+       <?php if ($total_pages < 13): /* « Previous  1 2 3 4 5 6 7 8 9 10 11 12  Next » */ ?>
+
+               <?php for ($i = 1; $i <= $total_pages; $i++): ?>
+                       <?php if ($i == $current_page): ?>
+                               <strong><?php echo $i ?></strong>
+                       <?php else: ?>
+                               <a href="<?php echo str_replace('{page}', $i, $url) ?>"><?php echo $i ?></a>
+                       <?php endif ?>
+               <?php endfor ?>
+
+       <?php elseif ($current_page < 9): /* « Previous  1 2 3 4 5 6 7 8 9 10 … 25 26  Next » */ ?>
+
+               <?php for ($i = 1; $i <= 10; $i++): ?>
+                       <?php if ($i == $current_page): ?>
+                               <strong><?php echo $i ?></strong>
+                       <?php else: ?>
+                               <a href="<?php echo str_replace('{page}', $i, $url) ?>"><?php echo $i ?></a>
+                       <?php endif ?>
+               <?php endfor ?>
+
+               &hellip;
+               <a href="<?php echo str_replace('{page}', $total_pages - 1, $url) ?>"><?php echo $total_pages - 1 ?></a>
+               <a href="<?php echo str_replace('{page}', $total_pages, $url) ?>"><?php echo $total_pages ?></a>
+
+       <?php elseif ($current_page > $total_pages - 8): /* « Previous  1 2 … 17 18 19 20 21 22 23 24 25 26  Next » */ ?>
+
+               <a href="<?php echo str_replace('{page}', 1, $url) ?>">1</a>
+               <a href="<?php echo str_replace('{page}', 2, $url) ?>">2</a>
+               &hellip;
+
+               <?php for ($i = $total_pages - 9; $i <= $total_pages; $i++): ?>
+                       <?php if ($i == $current_page): ?>
+                               <strong><?php echo $i ?></strong>
+                       <?php else: ?>
+                               <a href="<?php echo str_replace('{page}', $i, $url) ?>"><?php echo $i ?></a>
+                       <?php endif ?>
+               <?php endfor ?>
+
+       <?php else: /* « Previous  1 2 … 5 6 7 8 9 10 11 12 13 14 … 25 26  Next » */ ?>
+
+               <a href="<?php echo str_replace('{page}', 1, $url) ?>">1</a>
+               <a href="<?php echo str_replace('{page}', 2, $url) ?>">2</a>
+               &hellip;
+
+               <?php for ($i = $current_page - 5; $i <= $current_page + 5; $i++): ?>
+                       <?php if ($i == $current_page): ?>
+                               <strong><?php echo $i ?></strong>
+                       <?php else: ?>
+                               <a href="<?php echo str_replace('{page}', $i, $url) ?>"><?php echo $i ?></a>
+                       <?php endif ?>
+               <?php endfor ?>
+
+               &hellip;
+               <a href="<?php echo str_replace('{page}', $total_pages - 1, $url) ?>"><?php echo $total_pages - 1 ?></a>
+               <a href="<?php echo str_replace('{page}', $total_pages, $url) ?>"><?php echo $total_pages ?></a>
+
+       <?php endif ?>
+
+
+       <?php if ($next_page): ?>
+               <a href="<?php echo str_replace('{page}', $next_page, $url) ?>"><?php echo Kohana::lang('pagination.next') ?>&nbsp;&raquo;</a>
+       <?php else: ?>
+               <?php echo Kohana::lang('pagination.next') ?>&nbsp;&raquo;
+       <?php endif ?>
+
+</p>
\ No newline at end of file
diff --git a/Server/system/views/pagination/extended.php b/Server/system/views/pagination/extended.php
new file mode 100644 (file)
index 0000000..2427a4e
--- /dev/null
@@ -0,0 +1,27 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Extended pagination style
+ * 
+ * @preview  « Previous | Page 2 of 11 | Showing items 6-10 of 52 | Next »
+ */
+?>
+
+<p class="pagination">
+
+       <?php if ($previous_page): ?>
+               <a href="<?php echo str_replace('{page}', $previous_page, $url) ?>">&laquo;&nbsp;<?php echo Kohana::lang('pagination.previous') ?></a>
+       <?php else: ?>
+               &laquo;&nbsp;<?php echo Kohana::lang('pagination.previous') ?>
+       <?php endif ?>
+
+       | <?php echo Kohana::lang('pagination.page') ?> <?php echo $current_page ?> <?php echo Kohana::lang('pagination.of') ?> <?php echo $total_pages ?>
+
+       | <?php echo Kohana::lang('pagination.items') ?> <?php echo $current_first_item ?>&ndash;<?php echo $current_last_item ?> <?php echo Kohana::lang('pagination.of') ?> <?php echo $total_items ?>
+
+       | <?php if ($next_page): ?>
+               <a href="<?php echo str_replace('{page}', $next_page, $url) ?>"><?php echo Kohana::lang('pagination.next') ?>&nbsp;&raquo;</a>
+       <?php else: ?>
+               <?php echo Kohana::lang('pagination.next') ?>&nbsp;&raquo;
+       <?php endif ?>
+
+</p>
\ No newline at end of file
diff --git a/Server/system/views/pagination/punbb.php b/Server/system/views/pagination/punbb.php
new file mode 100644 (file)
index 0000000..6599831
--- /dev/null
@@ -0,0 +1,37 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * PunBB pagination style
+ * 
+ * @preview  Pages: 1 … 4 5 6 7 8 … 15
+ */
+?>
+
+<p class="pagination">
+
+       <?php echo Kohana::lang('pagination.pages') ?>:
+
+       <?php if ($current_page > 3): ?>
+               <a href="<?php echo str_replace('{page}', 1, $url) ?>">1</a>
+               <?php if ($current_page != 4) echo '&hellip;' ?>
+       <?php endif ?>
+
+
+       <?php for ($i = $current_page - 2, $stop = $current_page + 3; $i < $stop; ++$i): ?>
+
+               <?php if ($i < 1 OR $i > $total_pages) continue ?>
+
+               <?php if ($current_page == $i): ?>
+                       <strong><?php echo $i ?></strong>
+               <?php else: ?>
+                       <a href="<?php echo str_replace('{page}', $i, $url) ?>"><?php echo $i ?></a>
+               <?php endif ?>
+
+       <?php endfor ?>
+
+
+       <?php if ($current_page <= $total_pages - 3): ?>
+               <?php if ($current_page != $total_pages - 3) echo '&hellip;' ?>
+               <a href="<?php echo str_replace('{page}', $total_pages, $url) ?>"><?php echo $total_pages ?></a>
+       <?php endif ?>
+
+</p>
\ No newline at end of file