summaryrefslogtreecommitdiff
path: root/system/core
diff options
context:
space:
mode:
Diffstat (limited to 'system/core')
-rw-r--r--system/core/Benchmark.php126
-rw-r--r--system/core/Event.php231
-rw-r--r--system/core/Kohana.php1118
-rw-r--r--system/core/Kohana_Config.php329
-rw-r--r--system/core/Kohana_Exception.php622
5 files changed, 2426 insertions, 0 deletions
diff --git a/system/core/Benchmark.php b/system/core/Benchmark.php
new file mode 100644
index 0000000..79fadb6
--- /dev/null
+++ b/system/core/Benchmark.php
@@ -0,0 +1,126 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Simple benchmarking.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+final class Benchmark {
+
+ // Benchmark timestamps
+ private static $marks;
+
+ /**
+ * Set a benchmark start point.
+ *
+ * @param string benchmark name
+ * @return void
+ */
+ public static function start($name)
+ {
+ if (isset(self::$marks[$name]) AND self::$marks[$name][0]['stop'] === FALSE)
+ throw new Kohana_Exception('A benchmark named :name is already running.', array(':name' => $name));
+
+ if ( ! isset(self::$marks[$name]))
+ {
+ self::$marks[$name] = array();
+ }
+
+ $mark = array
+ (
+ 'start' => microtime(TRUE),
+ 'stop' => FALSE,
+ 'memory_start' => self::memory_usage(),
+ 'memory_stop' => FALSE
+ );
+
+ array_unshift(self::$marks[$name], $mark);
+ }
+
+ /**
+ * Set a benchmark stop point.
+ *
+ * @param string benchmark name
+ * @return void
+ */
+ public static function stop($name)
+ {
+ if (isset(self::$marks[$name]) AND self::$marks[$name][0]['stop'] === FALSE)
+ {
+ self::$marks[$name][0]['stop'] = microtime(TRUE);
+ self::$marks[$name][0]['memory_stop'] = self::memory_usage();
+ }
+ }
+
+ /**
+ * Get the elapsed time between a start and stop.
+ *
+ * @param string benchmark name, TRUE for all
+ * @param integer number of decimal places to count to
+ * @return array
+ */
+ public static function get($name, $decimals = 4)
+ {
+ if ($name === TRUE)
+ {
+ $times = array();
+ $names = array_keys(self::$marks);
+
+ foreach ($names as $name)
+ {
+ // Get each mark recursively
+ $times[$name] = self::get($name, $decimals);
+ }
+
+ // Return the array
+ return $times;
+ }
+
+ if ( ! isset(self::$marks[$name]))
+ return FALSE;
+
+ if (self::$marks[$name][0]['stop'] === FALSE)
+ {
+ // Stop the benchmark to prevent mis-matched results
+ self::stop($name);
+ }
+
+ // Return a string version of the time between the start and stop points
+ // Properly reading a float requires using number_format or sprintf
+ $time = $memory = 0;
+ for ($i = 0; $i < count(self::$marks[$name]); $i++)
+ {
+ $time += self::$marks[$name][$i]['stop'] - self::$marks[$name][$i]['start'];
+ $memory += self::$marks[$name][$i]['memory_stop'] - self::$marks[$name][$i]['memory_start'];
+ }
+
+ return array
+ (
+ 'time' => number_format($time, $decimals),
+ 'memory' => $memory,
+ 'count' => count(self::$marks[$name])
+ );
+ }
+
+ /**
+ * Returns the current memory usage. This is only possible if the
+ * memory_get_usage function is supported in PHP.
+ *
+ * @return integer
+ */
+ private static function memory_usage()
+ {
+ static $func;
+
+ if ($func === NULL)
+ {
+ // Test if memory usage can be seen
+ $func = function_exists('memory_get_usage');
+ }
+
+ return $func ? memory_get_usage() : 0;
+ }
+
+} // End Benchmark
diff --git a/system/core/Event.php b/system/core/Event.php
new file mode 100644
index 0000000..ab839f4
--- /dev/null
+++ b/system/core/Event.php
@@ -0,0 +1,231 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Process queuing/execution class. Allows an unlimited number of callbacks
+ * to be added to 'events'. Events can be run multiple times, and can also
+ * process event-specific data. By default, Kohana has several system events.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ * @link http://docs.kohanaphp.com/general/events
+ */
+abstract class Event_Core {
+
+ // Event callbacks
+ protected static $events = array();
+
+ // Cache of events that have been run
+ protected static $has_run = array();
+
+ // Data that can be processed during events
+ public static $data;
+
+ /**
+ * Add a callback to an event queue.
+ *
+ * @param string event name
+ * @param array http://php.net/callback
+ * @param boolean prevent duplicates
+ * @return boolean
+ */
+ public static function add($name, $callback, $unique = FALSE)
+ {
+ if ( ! isset(Event::$events[$name]))
+ {
+ // Create an empty event if it is not yet defined
+ Event::$events[$name] = array();
+ }
+ elseif ($unique AND in_array($callback, Event::$events[$name], TRUE))
+ {
+ // The event already exists
+ return FALSE;
+ }
+
+ // Add the event
+ Event::$events[$name][] = $callback;
+
+ return TRUE;
+ }
+
+ /**
+ * Add a callback to an event queue, before a given event.
+ *
+ * @param string event name
+ * @param array existing event callback
+ * @param array event callback
+ * @return boolean
+ */
+ public static function add_before($name, $existing, $callback)
+ {
+ if (empty(Event::$events[$name]) OR ($key = array_search($existing, Event::$events[$name])) === FALSE)
+ {
+ // Just add the event if there are no events
+ return Event::add($name, $callback);
+ }
+ else
+ {
+ // Insert the event immediately before the existing event
+ return Event::insert_event($name, $key, $callback);
+ }
+ }
+
+ /**
+ * Add a callback to an event queue, after a given event.
+ *
+ * @param string event name
+ * @param array existing event callback
+ * @param array event callback
+ * @return boolean
+ */
+ public static function add_after($name, $existing, $callback)
+ {
+ if (empty(Event::$events[$name]) OR ($key = array_search($existing, Event::$events[$name])) === FALSE)
+ {
+ // Just add the event if there are no events
+ return Event::add($name, $callback);
+ }
+ else
+ {
+ // Insert the event immediately after the existing event
+ return Event::insert_event($name, $key + 1, $callback);
+ }
+ }
+
+ /**
+ * Inserts a new event at a specfic key location.
+ *
+ * @param string event name
+ * @param integer key to insert new event at
+ * @param array event callback
+ * @return void
+ */
+ private static function insert_event($name, $key, $callback)
+ {
+ if (in_array($callback, Event::$events[$name], TRUE))
+ return FALSE;
+
+ // Add the new event at the given key location
+ Event::$events[$name] = array_merge
+ (
+ // Events before the key
+ array_slice(Event::$events[$name], 0, $key),
+ // New event callback
+ array($callback),
+ // Events after the key
+ array_slice(Event::$events[$name], $key)
+ );
+
+ return TRUE;
+ }
+
+ /**
+ * Replaces an event with another event.
+ *
+ * @param string event name
+ * @param array event to replace
+ * @param array new callback
+ * @return boolean
+ */
+ public static function replace($name, $existing, $callback)
+ {
+ if (empty(Event::$events[$name]) OR ($key = array_search($existing, Event::$events[$name], TRUE)) === FALSE)
+ return FALSE;
+
+ if ( ! in_array($callback, Event::$events[$name], TRUE))
+ {
+ // Replace the exisiting event with the new event
+ Event::$events[$name][$key] = $callback;
+ }
+ else
+ {
+ // Remove the existing event from the queue
+ unset(Event::$events[$name][$key]);
+
+ // Reset the array so the keys are ordered properly
+ Event::$events[$name] = array_values(Event::$events[$name]);
+ }
+
+ return TRUE;
+ }
+
+ /**
+ * Get all callbacks for an event.
+ *
+ * @param string event name
+ * @return array
+ */
+ public static function get($name)
+ {
+ return empty(Event::$events[$name]) ? array() : Event::$events[$name];
+ }
+
+ /**
+ * Clear some or all callbacks from an event.
+ *
+ * @param string event name
+ * @param array specific callback to remove, FALSE for all callbacks
+ * @return void
+ */
+ public static function clear($name, $callback = FALSE)
+ {
+ if ($callback === FALSE)
+ {
+ Event::$events[$name] = array();
+ }
+ elseif (isset(Event::$events[$name]))
+ {
+ // Loop through each of the event callbacks and compare it to the
+ // callback requested for removal. The callback is removed if it
+ // matches.
+ foreach (Event::$events[$name] as $i => $event_callback)
+ {
+ if ($callback === $event_callback)
+ {
+ unset(Event::$events[$name][$i]);
+ }
+ }
+ }
+ }
+
+ /**
+ * Execute all of the callbacks attached to an event.
+ *
+ * @param string event name
+ * @param array data can be processed as Event::$data by the callbacks
+ * @return void
+ */
+ public static function run($name, & $data = NULL)
+ {
+ if ( ! empty(Event::$events[$name]))
+ {
+ // So callbacks can access Event::$data
+ Event::$data =& $data;
+ $callbacks = Event::get($name);
+
+ foreach ($callbacks as $callback)
+ {
+ call_user_func_array($callback, array(&$data));
+ }
+
+ // Do this to prevent data from getting 'stuck'
+ $clear_data = '';
+ Event::$data =& $clear_data;
+ }
+
+ // The event has been run!
+ Event::$has_run[$name] = $name;
+ }
+
+ /**
+ * Check if a given event has been run.
+ *
+ * @param string event name
+ * @return boolean
+ */
+ public static function has_run($name)
+ {
+ return isset(Event::$has_run[$name]);
+ }
+
+} // End Event \ No newline at end of file
diff --git a/system/core/Kohana.php b/system/core/Kohana.php
new file mode 100644
index 0000000..96d969e
--- /dev/null
+++ b/system/core/Kohana.php
@@ -0,0 +1,1118 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Provides Kohana-specific helper functions. This is where the magic happens!
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+
+// Test of Kohana is running in Windows
+define('KOHANA_IS_WIN', DIRECTORY_SEPARATOR === '\\');
+
+abstract class Kohana_Core {
+
+ const VERSION = '2.4';
+ const CODENAME = 'no_codename';
+ const CHARSET = 'UTF-8';
+ const LOCALE = 'en_US';
+
+ // The singleton instance of the controller
+ public static $instance;
+
+ // Output buffering level
+ protected static $buffer_level;
+
+ // The final output that will displayed by Kohana
+ public static $output = '';
+
+ // The current locale
+ public static $locale;
+
+ // Include paths
+ protected static $include_paths;
+ protected static $include_paths_hash = '';
+
+ // Cache lifetime
+ protected static $cache_lifetime;
+
+ // Internal caches and write status
+ protected static $internal_cache = array();
+ protected static $write_cache;
+ protected static $internal_cache_path;
+ protected static $internal_cache_key;
+ protected static $internal_cache_encrypt;
+
+ // Server API that PHP is using. Allows testing of different APIs.
+ public static $server_api = PHP_SAPI;
+
+ /**
+ * Sets up the PHP environment. Adds error/exception handling, output
+ * buffering, and adds an auto-loading method for loading classes.
+ *
+ * This method is run immediately when this file is loaded, and is
+ * benchmarked as environment_setup.
+ *
+ * For security, this function also destroys the $_REQUEST global variable.
+ * Using the proper global (GET, POST, COOKIE, etc) is inherently more secure.
+ * The recommended way to fetch a global variable is using the Input library.
+ * @see http://www.php.net/globals
+ *
+ * @return void
+ */
+ public static function setup()
+ {
+ static $run;
+
+ // Only run this function once
+ if ($run === TRUE)
+ return;
+
+ $run = TRUE;
+
+ // Start the environment setup benchmark
+ Benchmark::start(SYSTEM_BENCHMARK.'_environment_setup');
+
+ // Define Kohana error constant
+ define('E_KOHANA', 42);
+
+ // Define 404 error constant
+ define('E_PAGE_NOT_FOUND', 43);
+
+ // Define database error constant
+ define('E_DATABASE_ERROR', 44);
+
+ // Set the default charset for mb_* functions
+ mb_internal_encoding(Kohana::CHARSET);
+
+ if (Kohana_Config::instance()->loaded() === FALSE)
+ {
+ // Re-parse the include paths
+ Kohana::include_paths(TRUE);
+ }
+
+ if (Kohana::$cache_lifetime = Kohana::config('core.internal_cache'))
+ {
+ // Are we using encryption for caches?
+ Kohana::$internal_cache_encrypt = Kohana::config('core.internal_cache_encrypt');
+
+ if(Kohana::$internal_cache_encrypt===TRUE)
+ {
+ Kohana::$internal_cache_key = Kohana::config('core.internal_cache_key');
+
+ // Be sure the key is of acceptable length for the mcrypt algorithm used
+ Kohana::$internal_cache_key = substr(Kohana::$internal_cache_key, 0, 24);
+ }
+
+ // Set the directory to be used for the internal cache
+ if ( ! Kohana::$internal_cache_path = Kohana::config('core.internal_cache_path'))
+ {
+ Kohana::$internal_cache_path = APPPATH.'cache/';
+ }
+
+ // Load cached configuration and language files
+ Kohana::$internal_cache['configuration'] = Kohana::cache('configuration', Kohana::$cache_lifetime);
+ Kohana::$internal_cache['language'] = Kohana::cache('language', Kohana::$cache_lifetime);
+
+ // Load cached file paths
+ Kohana::$internal_cache['find_file_paths'] = Kohana::cache('find_file_paths', Kohana::$cache_lifetime);
+
+ // Enable cache saving
+ Event::add('system.shutdown', array('Kohana', 'internal_cache_save'));
+ }
+
+ // Start output buffering
+ ob_start(array('Kohana', 'output_buffer'));
+
+ // Save buffering level
+ Kohana::$buffer_level = ob_get_level();
+
+ // Set autoloader
+ spl_autoload_register(array('Kohana', 'auto_load'));
+
+ // Register a shutdown function to handle system.shutdown events
+ register_shutdown_function(array('Kohana', 'shutdown'));
+
+ // Send default text/html UTF-8 header
+ header('Content-Type: text/html; charset='.Kohana::CHARSET);
+
+ // Load i18n
+ new I18n;
+
+ // Enable exception handling
+ Kohana_Exception::enable();
+
+ // Enable error handling
+ Kohana_PHP_Exception::enable();
+
+ // Load locales
+ $locales = Kohana::config('locale.language');
+
+ // Make first locale the defined Kohana charset
+ $locales[0] .= '.'.Kohana::CHARSET;
+
+ // Set locale information
+ Kohana::$locale = setlocale(LC_ALL, $locales);
+
+ // Default to the default locale when none of the user defined ones where accepted
+ Kohana::$locale = ! Kohana::$locale ? Kohana::LOCALE.'.'.Kohana::CHARSET : Kohana::$locale;
+
+ // Set locale for the I18n system
+ I18n::set_locale(Kohana::$locale);
+
+ // Set and validate the timezone
+ date_default_timezone_set(Kohana::config('locale.timezone'));
+
+ // register_globals is enabled
+ if (ini_get('register_globals'))
+ {
+ if (isset($_REQUEST['GLOBALS']))
+ {
+ // Prevent GLOBALS override attacks
+ exit('Global variable overload attack.');
+ }
+
+ // Destroy the REQUEST global
+ $_REQUEST = array();
+
+ // These globals are standard and should not be removed
+ $preserve = array('GLOBALS', '_REQUEST', '_GET', '_POST', '_FILES', '_COOKIE', '_SERVER', '_ENV', '_SESSION');
+
+ // This loop has the same effect as disabling register_globals
+ foreach (array_diff(array_keys($GLOBALS), $preserve) as $key)
+ {
+ global $$key;
+ $$key = NULL;
+
+ // Unset the global variable
+ unset($GLOBALS[$key], $$key);
+ }
+
+ // Warn the developer about register globals
+ Kohana_Log::add('debug', 'Disable register_globals! It is evil and deprecated: http://php.net/register_globals');
+ }
+
+ // Enable Kohana routing
+ Event::add('system.routing', array('Router', 'find_uri'));
+ Event::add('system.routing', array('Router', 'setup'));
+
+ // Enable Kohana controller initialization
+ Event::add('system.execute', array('Kohana', 'instance'));
+
+ // Enable Kohana 404 pages
+ Event::add('system.404', array('Kohana_404_Exception', 'trigger'));
+
+ if (Kohana::config('core.enable_hooks') === TRUE)
+ {
+ // Find all the hook files
+ $hooks = Kohana::list_files('hooks', TRUE);
+
+ foreach ($hooks as $file)
+ {
+ // Load the hook
+ include $file;
+ }
+ }
+
+ // Stop the environment setup routine
+ Benchmark::stop(SYSTEM_BENCHMARK.'_environment_setup');
+ }
+
+ /**
+ * Cleans up the PHP environment. Disables error/exception handling and the
+ * auto-loading method and closes the output buffer.
+ *
+ * This method does not need to be called during normal system execution,
+ * however in some advanced situations it can be helpful. @see #1781
+ *
+ * @return void
+ */
+ public static function cleanup()
+ {
+ static $run;
+
+ // Only run this function once
+ if ($run === TRUE)
+ return;
+
+ $run = TRUE;
+
+ Kohana_Exception::disable();
+
+ Kohana_PHP_Exception::disable();
+
+ spl_autoload_unregister(array('Kohana', 'auto_load'));
+
+ Kohana::close_buffers();
+ }
+
+ /**
+ * Loads the controller and initializes it. Runs the pre_controller,
+ * post_controller_constructor, and post_controller events. Triggers
+ * a system.404 event when the route cannot be mapped to a controller.
+ *
+ * This method is benchmarked as controller_setup and controller_execution.
+ *
+ * @return object instance of controller
+ */
+ public static function & instance()
+ {
+ if (Kohana::$instance === NULL)
+ {
+ Benchmark::start(SYSTEM_BENCHMARK.'_controller_setup');
+
+ // Include the Controller file
+ require_once Router::$controller_path;
+
+ try
+ {
+ // Start validation of the controller
+ $class = new ReflectionClass(ucfirst(Router::$controller).'_Controller');
+ }
+ catch (ReflectionException $e)
+ {
+ // Controller does not exist
+ Event::run('system.404');
+ }
+
+ if ($class->isAbstract() OR (IN_PRODUCTION AND $class->getConstant('ALLOW_PRODUCTION') == FALSE))
+ {
+ // Controller is not allowed to run in production
+ Event::run('system.404');
+ }
+
+ // Run system.pre_controller
+ Event::run('system.pre_controller');
+
+ // Create a new controller instance
+ $controller = $class->newInstance();
+
+ // Controller constructor has been executed
+ Event::run('system.post_controller_constructor');
+
+ try
+ {
+ // Load the controller method
+ $method = $class->getMethod(Router::$method);
+
+ // Method exists
+ if (Router::$method[0] === '_')
+ {
+ // Do not allow access to hidden methods
+ Event::run('system.404');
+ }
+
+ if ($method->isProtected() or $method->isPrivate())
+ {
+ // Do not attempt to invoke protected methods
+ throw new ReflectionException('protected controller method');
+ }
+
+ // Default arguments
+ $arguments = Router::$arguments;
+ }
+ catch (ReflectionException $e)
+ {
+ // Use __call instead
+ $method = $class->getMethod('__call');
+
+ // Use arguments in __call format
+ $arguments = array(Router::$method, Router::$arguments);
+ }
+
+ // Stop the controller setup benchmark
+ Benchmark::stop(SYSTEM_BENCHMARK.'_controller_setup');
+
+ // Start the controller execution benchmark
+ Benchmark::start(SYSTEM_BENCHMARK.'_controller_execution');
+
+ // Execute the controller method
+ $method->invokeArgs($controller, $arguments);
+
+ // Controller method has been executed
+ Event::run('system.post_controller');
+
+ // Stop the controller execution benchmark
+ Benchmark::stop(SYSTEM_BENCHMARK.'_controller_execution');
+ }
+
+ return Kohana::$instance;
+ }
+
+ /**
+ * Get all include paths. APPPATH is the first path, followed by module
+ * paths in the order they are configured, follow by the SYSPATH.
+ *
+ * @param boolean re-process the include paths
+ * @return array
+ */
+ public static function include_paths($process = FALSE)
+ {
+ if ($process === TRUE)
+ {
+ // Add APPPATH as the first path
+ Kohana::$include_paths = array(APPPATH);
+
+ foreach (Kohana::config('core.modules') as $path)
+ {
+ if ($path = str_replace('\\', '/', realpath($path)))
+ {
+ // Add a valid path
+ Kohana::$include_paths[] = $path.'/';
+ }
+ }
+
+ // Add SYSPATH as the last path
+ Kohana::$include_paths[] = SYSPATH;
+
+ Kohana::$include_paths_hash = md5(serialize(Kohana::$include_paths));
+ }
+
+ return Kohana::$include_paths;
+ }
+
+ /**
+ * Get a config item or group proxies Kohana_Config.
+ *
+ * @param string item name
+ * @param boolean force a forward slash (/) at the end of the item
+ * @param boolean is the item required?
+ * @return mixed
+ */
+ public static function config($key, $slash = FALSE, $required = FALSE)
+ {
+ return Kohana_Config::instance()->get($key,$slash,$required);
+ }
+
+ /**
+ * Load data from a simple cache file. This should only be used internally,
+ * and is NOT a replacement for the Cache library.
+ *
+ * @param string unique name of cache
+ * @param integer expiration in seconds
+ * @return mixed
+ */
+ public static function cache($name, $lifetime)
+ {
+ if ($lifetime > 0)
+ {
+ $path = Kohana::$internal_cache_path.'kohana_'.$name;
+
+ if (is_file($path))
+ {
+ // Check the file modification time
+ if ((time() - filemtime($path)) < $lifetime)
+ {
+ // Cache is valid! Now, do we need to decrypt it?
+ if(Kohana::$internal_cache_encrypt===TRUE)
+ {
+ $data = file_get_contents($path);
+
+ $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
+ $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
+
+ $decrypted_text = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, Kohana::$internal_cache_key, $data, MCRYPT_MODE_ECB, $iv);
+
+ $cache = unserialize($decrypted_text);
+
+ // If the key changed, delete the cache file
+ if(!$cache)
+ unlink($path);
+
+ // If cache is false (as above) return NULL, otherwise, return the cache
+ return ($cache ? $cache : NULL);
+ }
+ else
+ {
+ return unserialize(file_get_contents($path));
+ }
+ }
+ else
+ {
+ // Cache is invalid, delete it
+ unlink($path);
+ }
+ }
+ }
+
+ // No cache found
+ return NULL;
+ }
+
+ /**
+ * Save data to a simple cache file. This should only be used internally, and
+ * is NOT a replacement for the Cache library.
+ *
+ * @param string cache name
+ * @param mixed data to cache
+ * @param integer expiration in seconds
+ * @return boolean
+ */
+ public static function cache_save($name, $data, $lifetime)
+ {
+ if ($lifetime < 1)
+ return FALSE;
+
+ $path = Kohana::$internal_cache_path.'kohana_'.$name;
+
+ if ($data === NULL)
+ {
+ // Delete cache
+ return (is_file($path) and unlink($path));
+ }
+ else
+ {
+ // Using encryption? Encrypt the data when we write it
+ if(Kohana::$internal_cache_encrypt===TRUE)
+ {
+ // Encrypt and write data to cache file
+ $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
+ $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
+
+ // Serialize and encrypt!
+ $encrypted_text = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, Kohana::$internal_cache_key, serialize($data), MCRYPT_MODE_ECB, $iv);
+
+ return (bool) file_put_contents($path, $encrypted_text);
+ }
+ else
+ {
+ // Write data to cache file
+ return (bool) file_put_contents($path, serialize($data));
+ }
+ }
+ }
+
+ /**
+ * Kohana output handler. Called during ob_clean, ob_flush, and their variants.
+ *
+ * @param string current output buffer
+ * @return string
+ */
+ public static function output_buffer($output)
+ {
+ // Could be flushing, so send headers first
+ if ( ! Event::has_run('system.send_headers'))
+ {
+ // Run the send_headers event
+ Event::run('system.send_headers');
+ }
+
+ // Set final output
+ Kohana::$output = $output;
+
+ // Set and return the final output
+ return Kohana::$output;
+ }
+
+ /**
+ * Closes all open output buffers, either by flushing or cleaning, and stores
+ * output buffer for display during shutdown.
+ *
+ * @param boolean disable to clear buffers, rather than flushing
+ * @return void
+ */
+ public static function close_buffers($flush = TRUE)
+ {
+ if (ob_get_level() >= Kohana::$buffer_level)
+ {
+ // Set the close function
+ $close = ($flush === TRUE) ? 'ob_end_flush' : 'ob_end_clean';
+
+ while (ob_get_level() > Kohana::$buffer_level)
+ {
+ // Flush or clean the buffer
+ $close();
+ }
+
+ // Store the Kohana output buffer. Apparently there was a change in PHP
+ // 5.4 such that if you call this you wind up with a blank page.
+ // Disabling it for now. See ticket #1839
+ if (version_compare(PHP_VERSION, "5.4", "<")) {
+ ob_end_clean();
+ }
+ }
+ }
+
+ /**
+ * Triggers the shutdown of Kohana by closing the output buffer, runs the system.display event.
+ *
+ * @return void
+ */
+ public static function shutdown()
+ {
+ static $run;
+
+ // Only run this function once
+ if ($run === TRUE)
+ return;
+
+ $run = TRUE;
+
+ // Run system.shutdown event
+ Event::run('system.shutdown');
+
+ // Close output buffers
+ Kohana::close_buffers(TRUE);
+
+ // Run the output event
+ Event::run('system.display', Kohana::$output);
+
+ // Render the final output
+ Kohana::render(Kohana::$output);
+ }
+
+ /**
+ * Inserts global Kohana variables into the generated output and prints it.
+ *
+ * @param string final output that will displayed
+ * @return void
+ */
+ public static function render($output)
+ {
+ if (Kohana::config('core.render_stats') === TRUE)
+ {
+ // Fetch memory usage in MB
+ $memory = function_exists('memory_get_usage') ? (memory_get_usage() / 1024 / 1024) : 0;
+
+ // Fetch benchmark for page execution time
+ $benchmark = Benchmark::get(SYSTEM_BENCHMARK.'_total_execution');
+
+ // Replace the global template variables
+ $output = str_replace(
+ array
+ (
+ '{kohana_version}',
+ '{kohana_codename}',
+ '{execution_time}',
+ '{memory_usage}',
+ '{included_files}',
+ ),
+ array
+ (
+ KOHANA::VERSION,
+ KOHANA::CODENAME,
+ $benchmark['time'],
+ number_format($memory, 2).'MB',
+ count(get_included_files()),
+ ),
+ $output
+ );
+ }
+
+ if ($level = Kohana::config('core.output_compression') AND ini_get('output_handler') !== 'ob_gzhandler' AND (int) ini_get('zlib.output_compression') === 0)
+ {
+ if ($compress = request::preferred_encoding(array('gzip','deflate'), TRUE))
+ {
+ if ($level < 1 OR $level > 9)
+ {
+ // Normalize the level to be an integer between 1 and 9. This
+ // step must be done to prevent gzencode from triggering an error
+ $level = max(1, min($level, 9));
+ }
+
+ if ($compress === 'gzip')
+ {
+ // Compress output using gzip
+ $output = gzencode($output, $level);
+ }
+ elseif ($compress === 'deflate')
+ {
+ // Compress output using zlib (HTTP deflate)
+ $output = gzdeflate($output, $level);
+ }
+
+ // This header must be sent with compressed content to prevent
+ // browser caches from breaking
+ header('Vary: Accept-Encoding');
+
+ // Send the content encoding header
+ header('Content-Encoding: '.$compress);
+
+ // Sending Content-Length in CGI can result in unexpected behavior
+ if (stripos(Kohana::$server_api, 'cgi') === FALSE)
+ {
+ header('Content-Length: '.strlen($output));
+ }
+ }
+ }
+
+ echo $output;
+ }
+
+ /**
+ * Provides class auto-loading.
+ *
+ * @throws Kohana_Exception
+ * @param string name of class
+ * @return bool
+ */
+ public static function auto_load($class)
+ {
+ if (class_exists($class, FALSE) OR interface_exists($class, FALSE))
+ return TRUE;
+
+ if (($suffix = strrpos($class, '_')) > 0)
+ {
+ // Find the class suffix
+ $suffix = substr($class, $suffix + 1);
+ }
+ else
+ {
+ // No suffix
+ $suffix = FALSE;
+ }
+
+ if ($suffix === 'Core')
+ {
+ $type = 'libraries';
+ $file = substr($class, 0, -5);
+ }
+ elseif ($suffix === 'Controller')
+ {
+ $type = 'controllers';
+ // Lowercase filename
+ $file = strtolower(substr($class, 0, -11));
+ }
+ elseif ($suffix === 'Model')
+ {
+ $type = 'models';
+ // Lowercase filename
+ $file = strtolower(substr($class, 0, -6));
+ }
+ elseif ($suffix === 'Driver')
+ {
+ $type = 'libraries/drivers';
+ $file = str_replace('_', '/', substr($class, 0, -7));
+ }
+ else
+ {
+ // This could be either a library or a helper, but libraries must
+ // always be capitalized, so we check if the first character is
+ // uppercase. If it is, we are loading a library, not a helper.
+ $type = ($class[0] < 'a') ? 'libraries' : 'helpers';
+ $file = $class;
+ }
+
+ if ($filename = Kohana::find_file($type, $file))
+ {
+ // Load the class
+ require $filename;
+ }
+ else
+ {
+ // The class could not be found
+ return FALSE;
+ }
+
+ if ($filename = Kohana::find_file($type, Kohana::config('core.extension_prefix').$class))
+ {
+ // Load the class extension
+ require $filename;
+ }
+ elseif ($suffix !== 'Core' AND class_exists($class.'_Core', FALSE))
+ {
+ // Class extension to be evaluated
+ $extension = 'class '.$class.' extends '.$class.'_Core { }';
+
+ // Start class analysis
+ $core = new ReflectionClass($class.'_Core');
+
+ if ($core->isAbstract())
+ {
+ // Make the extension abstract
+ $extension = 'abstract '.$extension;
+ }
+
+ // Transparent class extensions are handled using eval. This is
+ // a disgusting hack, but it gets the job done.
+ eval($extension);
+ }
+
+ return TRUE;
+ }
+
+ /**
+ * Find a resource file in a given directory. Files will be located according
+ * to the order of the include paths. Configuration and i18n files will be
+ * returned in reverse order.
+ *
+ * @throws Kohana_Exception if file is required and not found
+ * @param string directory to search in
+ * @param string filename to look for (without extension)
+ * @param boolean file required
+ * @param string file extension
+ * @return array if the type is config, i18n or l10n
+ * @return string if the file is found
+ * @return FALSE if the file is not found
+ */
+ public static function find_file($directory, $filename, $required = FALSE, $ext = FALSE)
+ {
+ // NOTE: This test MUST be not be a strict comparison (===), or empty
+ // extensions will be allowed!
+ if ($ext == '')
+ {
+ // Use the default extension
+ $ext = EXT;
+ }
+ else
+ {
+ // Add a period before the extension
+ $ext = '.'.$ext;
+ }
+
+ // Search path
+ $search = $directory.'/'.$filename.$ext;
+
+ if (isset(Kohana::$internal_cache['find_file_paths'][Kohana::$include_paths_hash][$search]))
+ return Kohana::$internal_cache['find_file_paths'][Kohana::$include_paths_hash][$search];
+
+ // Load include paths
+ $paths = Kohana::$include_paths;
+
+ // Nothing found, yet
+ $found = NULL;
+
+ if ($directory === 'config' OR $directory === 'messages' OR $directory === 'i18n')
+ {
+ // Search in reverse, for merging
+ $paths = array_reverse($paths);
+
+ foreach ($paths as $path)
+ {
+ if (is_file($path.$search))
+ {
+ // A matching file has been found
+ $found[] = $path.$search;
+ }
+ }
+ }
+ else
+ {
+ foreach ($paths as $path)
+ {
+ if (is_file($path.$search))
+ {
+ // A matching file has been found
+ $found = $path.$search;
+
+ // Stop searching
+ break;
+ }
+ }
+ }
+
+ if ($found === NULL)
+ {
+ if ($required === TRUE)
+ {
+ // If the file is required, throw an exception
+ throw new Kohana_Exception('The requested :resource:, :file:, could not be found', array(':resource:' => __($directory), ':file:' =>$filename));
+ }
+ else
+ {
+ // Nothing was found, return FALSE
+ $found = FALSE;
+ }
+ }
+
+ if ( ! isset(Kohana::$write_cache['find_file_paths']))
+ {
+ // Write cache at shutdown
+ Kohana::$write_cache['find_file_paths'] = TRUE;
+ }
+
+ return Kohana::$internal_cache['find_file_paths'][Kohana::$include_paths_hash][$search] = $found;
+ }
+
+ /**
+ * Lists all files and directories in a resource path.
+ *
+ * @param string directory to search
+ * @param boolean list all files to the maximum depth?
+ * @param string list all files having extension $ext
+ * @param string full path to search (used for recursion, *never* set this manually)
+ * @return array filenames and directories
+ */
+ public static function list_files($directory, $recursive = FALSE, $ext = EXT, $path = FALSE)
+ {
+ $files = array();
+
+ if ($path === FALSE)
+ {
+ $paths = array_reverse(Kohana::include_paths());
+
+ foreach ($paths as $path)
+ {
+ // Recursively get and merge all files
+ $files = array_merge($files, Kohana::list_files($directory, $recursive, $ext, $path.$directory));
+ }
+ }
+ else
+ {
+ $path = rtrim($path, '/').'/';
+
+ if (is_readable($path) AND $items = glob($path.'*'))
+ {
+ $ext_pos = 0 - strlen($ext);
+
+ foreach ($items as $index => $item)
+ {
+ $item = str_replace('\\', '/', $item);
+
+ if (is_dir($item))
+ {
+ // Handle recursion
+ if ($recursive === TRUE)
+ {
+ // Filename should only be the basename
+ $item = pathinfo($item, PATHINFO_BASENAME);
+
+ // Append sub-directory search
+ $files = array_merge($files, Kohana::list_files($directory, TRUE, $ext, $path.$item));
+ }
+ }
+ else
+ {
+ // File extension must match
+ if ($ext_pos === 0 OR substr($item, $ext_pos) === $ext)
+ {
+ $files[] = $item;
+ }
+ }
+ }
+ }
+ }
+
+ return $files;
+ }
+
+
+ /**
+ * Fetch a message item.
+ *
+ * @param string language key to fetch
+ * @param array additional information to insert into the line
+ * @return string i18n language string, or the requested key if the i18n item is not found
+ */
+ public static function message($key, $args = array())
+ {
+ // Extract the main group from the key
+ $group = explode('.', $key, 2);
+ $group = $group[0];
+
+ if ( ! isset(Kohana::$internal_cache['messages'][$group]))
+ {
+ // Messages for this group
+ $messages = array();
+
+ if ($file = Kohana::find_file('messages', $group))
+ {
+ include $file[0];
+ }
+
+ if ( ! isset(Kohana::$write_cache['messages']))
+ {
+ // Write language cache
+ Kohana::$write_cache['messages'] = TRUE;
+ }
+
+ Kohana::$internal_cache['messages'][$group] = $messages;
+ }
+
+ // Get the line from cache
+ $line = Kohana::key_string(Kohana::$internal_cache['messages'], $key);
+
+ if ($line === NULL)
+ {
+ Kohana_Log::add('error', 'Missing messages entry '.$key.' for message '.$group);
+
+ // Return the key string as fallback
+ return $key;
+ }
+
+ if (is_string($line) AND func_num_args() > 1)
+ {
+ $args = array_slice(func_get_args(), 1);
+
+ // Add the arguments into the line
+ $line = vsprintf($line, is_array($args[0]) ? $args[0] : $args);
+ }
+
+ return $line;
+ }
+
+ /**
+ * Returns the value of a key, defined by a 'dot-noted' string, from an array.
+ *
+ * @param array array to search
+ * @param string dot-noted string: foo.bar.baz
+ * @return string if the key is found
+ * @return void if the key is not found
+ */
+ public static function key_string($array, $keys)
+ {
+ if (empty($array))
+ return NULL;
+
+ // Prepare for loop
+ $keys = explode('.', $keys);
+
+ do
+ {
+ // Get the next key
+ $key = array_shift($keys);
+
+ if (isset($array[$key]))
+ {
+ if (is_array($array[$key]) AND ! empty($keys))
+ {
+ // Dig down to prepare the next loop
+ $array = $array[$key];
+ }
+ else
+ {
+ // Requested key was found
+ return $array[$key];
+ }
+ }
+ else
+ {
+ // Requested key is not set
+ break;
+ }
+ }
+ while ( ! empty($keys));
+
+ return NULL;
+ }
+
+ /**
+ * Sets values in an array by using a 'dot-noted' string.
+ *
+ * @param array array to set keys in (reference)
+ * @param string dot-noted string: foo.bar.baz
+ * @return mixed fill value for the key
+ * @return void
+ */
+ public static function key_string_set( & $array, $keys, $fill = NULL)
+ {
+ if (is_object($array) AND ($array instanceof ArrayObject))
+ {
+ // Copy the array
+ $array_copy = $array->getArrayCopy();
+
+ // Is an object
+ $array_object = TRUE;
+ }
+ else
+ {
+ if ( ! is_array($array))
+ {
+ // Must always be an array
+ $array = (array) $array;
+ }
+
+ // Copy is a reference to the array
+ $array_copy =& $array;
+ }
+
+ if (empty($keys))
+ return $array;
+
+ // Create keys
+ $keys = explode('.', $keys);
+
+ // Create reference to the array
+ $row =& $array_copy;
+
+ for ($i = 0, $end = count($keys) - 1; $i <= $end; $i++)
+ {
+ // Get the current key
+ $key = $keys[$i];
+
+ if ( ! isset($row[$key]))
+ {
+ if (isset($keys[$i + 1]))
+ {
+ // Make the value an array
+ $row[$key] = array();
+ }
+ else
+ {
+ // Add the fill key
+ $row[$key] = $fill;
+ }
+ }
+ elseif (isset($keys[$i + 1]))
+ {
+ // Make the value an array
+ $row[$key] = (array) $row[$key];
+ }
+
+ // Go down a level, creating a new row reference
+ $row =& $row[$key];
+ }
+
+ if (isset($array_object))
+ {
+ // Swap the array back in
+ $array->exchangeArray($array_copy);
+ }
+ }
+
+ /**
+ * Quick debugging of any variable. Any number of parameters can be set.
+ *
+ * @return string
+ */
+ public static function debug()
+ {
+ if (func_num_args() === 0)
+ return;
+
+ // Get params
+ $params = func_get_args();
+ $output = array();
+
+ foreach ($params as $var)
+ {
+ $value = is_bool($var) ? ($var ? 'true' : 'false') : print_r($var, TRUE);
+ $output[] = '<pre>('.gettype($var).') '.htmlspecialchars($value, ENT_QUOTES, Kohana::CHARSET).'</pre>';
+ }
+
+ return implode("\n", $output);
+ }
+
+ /**
+ * Saves the internal caches: configuration, include paths, etc.
+ *
+ * @return boolean
+ */
+ public static function internal_cache_save()
+ {
+ if ( ! is_array(Kohana::$write_cache))
+ return FALSE;
+
+ // Get internal cache names
+ $caches = array_keys(Kohana::$write_cache);
+
+ // Nothing written
+ $written = FALSE;
+
+ foreach ($caches as $cache)
+ {
+ if (isset(Kohana::$internal_cache[$cache]))
+ {
+ // Write the cache file
+ Kohana::cache_save($cache, Kohana::$internal_cache[$cache], Kohana::config('core.internal_cache'));
+
+ // A cache has been written
+ $written = TRUE;
+ }
+ }
+
+ return $written;
+ }
+
+} // End Kohana
diff --git a/system/core/Kohana_Config.php b/system/core/Kohana_Config.php
new file mode 100644
index 0000000..9abc5b6
--- /dev/null
+++ b/system/core/Kohana_Config.php
@@ -0,0 +1,329 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Provides a driver-based interface for setting and getting
+ * configuration options for the Kohana environment
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class Kohana_Config_Core implements ArrayAccess {
+
+ /**
+ * The default Kohana_Config driver
+ * to use for system setup
+ *
+ * @var string
+ * @static
+ */
+ public static $default_driver = 'array';
+
+ /**
+ * Kohana_Config instance
+ *
+ * @var array
+ * @static
+ */
+ protected static $instance;
+
+ /**
+ * Returns a new instance of the Kohana_Config library
+ * based on the singleton pattern
+ *
+ * @param string driver
+ * @return Kohana_Config
+ * @access public
+ * @static
+ */
+ public static function & instance()
+ {
+ // If the driver has not been initialised, intialise it
+ if ( empty(Kohana_Config::$instance))
+ {
+ //call a 1 time non singleton of Kohana_Config to get a list of drivers
+ $config = new Kohana_Config(array(
+ 'config_drivers'=>array(
+ ), 'internal_cache'=>FALSE
+ ));
+ $core_config = $config->get('core');
+ Kohana_Config::$instance = new Kohana_Config($core_config);
+ }
+
+ // Return the Kohana_Config driver requested
+ return Kohana_Config::$instance;
+ }
+
+ /**
+ * The drivers for this object
+ *
+ * @var Kohana_Config_Driver
+ */
+ protected $drivers;
+
+ /**
+ * Kohana_Config constructor to load the supplied driver.
+ * Enforces the singleton pattern.
+ *
+ * @param string driver
+ * @access protected
+ */
+ protected function __construct(array $core_config)
+ {
+ $drivers = $core_config['config_drivers'];
+
+ //remove array if it's found in config
+ if (in_array('array', $drivers))
+ unset($drivers[array_search('array', $drivers)]);
+
+ //add array at the very end
+ $this->drivers = $drivers = array_merge($drivers, array(
+ 'array'
+ ));
+
+ foreach ($this->drivers as & $driver)
+ {
+ // Create the driver name
+ $driver = 'Config_'.ucfirst($driver).'_Driver';
+
+ // Ensure the driver loads correctly
+ if (!Kohana::auto_load($driver))
+ throw new Kohana_Exception('The :driver: driver for the :library: library could not be found.', array(
+ ':driver:' => $driver, ':library:' => get_class($this)
+ ));
+
+ // Load the new driver
+ $driver = new $driver($core_config);
+
+ // Ensure the new driver is valid
+ if (!$driver instanceof Config_Driver)
+ throw new Kohana_Exception('The :driver: driver for the :library: library must implement the :interface: interface', array(
+ ':driver:' => $driver, ':library:' => get_class($this), ':interface:' => 'Config_Driver'
+ ));
+ }
+ }
+
+ /**
+ * Gets a value from the configuration driver
+ *
+ * @param string key
+ * @param bool slash
+ * @param bool required
+ * @return mixed
+ * @access public
+ */
+ public function get($key, $slash = FALSE, $required = FALSE)
+ {
+ foreach ($this->drivers as $driver)
+ {
+ try
+ {
+ return $driver->get($key, $slash, $required);
+ }
+ catch (Kohana_Config_Exception $e)
+ {
+ //if it's the last driver in the list and it threw an exception, re throw it
+ if ($driver === $this->drivers[(count($this->drivers) - 1)])
+ throw $e;
+ }
+ }
+ }
+
+ /**
+ * Sets a value to the configuration drivers
+ *
+ * @param string key
+ * @param mixed value
+ * @return bool
+ * @access public
+ */
+ public function set($key, $value)
+ {
+ foreach ($this->drivers as $driver)
+ {
+ try
+ {
+ $driver->set($key, $value);
+ }
+ catch (Kohana_Config_Exception $e)
+ {
+ //if it's the last driver in the list and it threw an exception, re throw it
+ if ($driver === $this->drivers[(count($this->drivers) - 1)])
+ throw $e;
+ }
+ }
+ return TRUE;
+ }
+
+ /**
+ * Clears a group from configuration
+ *
+ * @param string group
+ * @return bool
+ * @access public
+ */
+ public function clear($group)
+ {
+ foreach ($this->drivers as $driver)
+ {
+ try
+ {
+ $driver->clear($group);
+ }
+ catch (Kohana_Config_Exception $e)
+ {
+ //if it's the last driver in the list and it threw an exception, re throw it
+ if ($driver === $this->drivers[(count($this->drivers) - 1)])
+ throw $e;
+ }
+ }
+ return TRUE;
+ }
+
+ /**
+ * Loads a configuration group
+ *
+ * @param string group
+ * @param bool required
+ * @return array
+ * @access public
+ */
+ public function load($group, $required = FALSE)
+ {
+ foreach ($this->drivers as $driver)
+ {
+ try
+ {
+ return $driver->load($group, $required);
+ }
+ catch (Kohana_Config_Exception $e)
+ {
+ //if it's the last driver in the list and it threw an exception, re throw it
+ if ($driver === $this->drivers[(count($this->drivers) - 1)])
+ throw $e;
+ }
+ }
+ }
+
+ /**
+ * Returns true or false if any config has been loaded(either manually or from cache)
+ *
+ * @return boolean
+ */
+ public function loaded()
+ {
+ return $this->drivers[(count($this->drivers) - 1)]->loaded;
+ }
+
+ /**
+ * The following allows access using
+ * array syntax.
+ *
+ * @example $config['core.site_domain']
+ */
+
+ /**
+ * Allows access to configuration settings
+ * using the ArrayAccess interface
+ *
+ * @param string key
+ * @return mixed
+ * @access public
+ */
+ public function offsetGet($key)
+ {
+ foreach ($this->drivers as $driver)
+ {
+ try
+ {
+ return $driver->get($key);
+ }
+ catch (Kohana_Config_Exception $e)
+ {
+ //if it's the last driver in the list and it threw an exception, re throw it
+ if ($driver === $this->drivers[(count($this->drivers) - 1)])
+ throw $e;
+ }
+ }
+ }
+
+ /**
+ * Allows access to configuration settings
+ * using the ArrayAccess interface
+ *
+ * @param string key
+ * @param mixed value
+ * @return bool
+ * @access public
+ */
+ public function offsetSet($key, $value)
+ {
+ foreach ($this->drivers as $driver)
+ {
+ try
+ {
+ $driver->set($key, $value);
+ }
+ catch (Kohana_Config_Exception $e)
+ {
+ //if it's the last driver in the list and it threw an exception, re throw it
+ if ($driver === $this->drivers[(count($this->drivers) - 1)])
+ throw $e;
+ }
+ }
+ return TRUE;
+ }
+
+ /**
+ * Allows access to configuration settings
+ * using the ArrayAccess interface
+ *
+ * @param string key
+ * @return bool
+ * @access public
+ */
+ public function offsetExists($key)
+ {
+ foreach ($this->drivers as $driver)
+ {
+ try
+ {
+ return $driver->setting_exists($key);
+ }
+ catch (Kohana_Config_Exception $e)
+ {
+ //if it's the last driver in the list and it threw an exception, re throw it
+ if ($driver === $this->drivers[(count($this->drivers) - 1)])
+ throw $e;
+ }
+ }
+ }
+
+ /**
+ * Allows access to configuration settings
+ * using the ArrayAccess interface
+ *
+ * @param string key
+ * @return bool
+ * @access public
+ */
+ public function offsetUnset($key)
+ {
+ foreach ($this->drivers as $driver)
+ {
+ try
+ {
+ return $driver->set($key, NULL);
+ }
+ catch (Kohana_Config_Exception $e)
+ {
+ //if it's the last driver in the list and it threw an exception, re throw it
+ if ($driver === $this->drivers[(count($this->drivers) - 1)])
+ throw $e;
+ }
+ }
+ return TRUE;
+ }
+} // End KohanaConfig
+
+class Kohana_Config_Exception extends Kohana_Exception {}
diff --git a/system/core/Kohana_Exception.php b/system/core/Kohana_Exception.php
new file mode 100644
index 0000000..bc0efd1
--- /dev/null
+++ b/system/core/Kohana_Exception.php
@@ -0,0 +1,622 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Kohana Exceptions
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+
+class Kohana_Exception_Core extends Exception {
+
+ public static $enabled = FALSE;
+
+ // Template file
+ public static $template = 'kohana/error';
+
+ // Show stack traces in errors
+ public static $trace_output = TRUE;
+
+ // Show source code in errors
+ public static $source_output = TRUE;
+
+ // To hold unique identifier to distinguish error output
+ protected $instance_identifier;
+
+ // Error code
+ protected $code = E_KOHANA;
+
+ /**
+ * Creates a new translated exception.
+ *
+ * @param string error message
+ * @param array translation variables
+ * @return void
+ */
+ public function __construct($message, array $variables = NULL, $code = 0)
+ {
+ $this->instance_identifier = uniqid();
+
+ // Translate the error message
+ $message = __($message, $variables);
+
+ // Sets $this->message the proper way
+ parent::__construct($message, $code);
+ }
+
+ /**
+ * Enable Kohana exception handling.
+ *
+ * @uses Kohana_Exception::$template
+ * @return void
+ */
+ public static function enable()
+ {
+ if ( ! Kohana_Exception::$enabled)
+ {
+ set_exception_handler(array('Kohana_Exception', 'handle'));
+
+ Kohana_Exception::$enabled = TRUE;
+ }
+ }
+
+ /**
+ * Disable Kohana exception handling.
+ *
+ * @return void
+ */
+ public static function disable()
+ {
+ if (Kohana_Exception::$enabled)
+ {
+ restore_exception_handler();
+
+ Kohana_Exception::$enabled = FALSE;
+ }
+ }
+
+ /**
+ * Get a single line of text representing the exception:
+ *
+ * Error [ Code ]: Message ~ File [ Line ]
+ *
+ * @param object Exception
+ * @return string
+ */
+ public static function text($e)
+ {
+ return sprintf('%s [ %s ]: %s ~ %s [ %d ]',
+ get_class($e), $e->getCode(), strip_tags($e->getMessage()), Kohana_Exception::debug_path($e->getFile()), $e->getLine());
+ }
+
+ /**
+ * exception handler, displays the error message, source of the
+ * exception, and the stack trace of the error.
+ *
+ * @uses Kohana::message()
+ * @uses Kohana_Exception::text()
+ * @param object exception object
+ * @return void
+ */
+ public static function handle(Exception $e)
+ {
+ try
+ {
+ // Get the exception information
+ $type = get_class($e);
+ $code = $e->getCode();
+ $message = $e->getMessage();
+
+ // Create a text version of the exception
+ $error = Kohana_Exception::text($e);
+
+ // Add this exception to the log
+ Kohana_Log::add('error', $error);
+
+ // Manually save logs after exceptions
+ Kohana_Log::save();
+
+ if (Kohana::config('kohana/core.display_errors') === FALSE)
+ {
+ // Do not show the details
+ $file = $line = NULL;
+ $trace = array();
+
+ $template = '_disabled';
+ }
+ else
+ {
+ $file = $e->getFile();
+ $line = $e->getLine();
+ $trace = $e->getTrace();
+
+ $template = '';
+ }
+
+ if ($e instanceof Kohana_Exception)
+ {
+ $template = $e->getTemplate().$template;
+
+ if ( ! headers_sent())
+ {
+ $e->sendHeaders();
+ }
+
+ // Use the human-readable error name
+ $code = Kohana::message('kohana/core.errors.'.$code);
+ }
+ else
+ {
+ $template = Kohana_Exception::$template.$template;
+
+ if ( ! headers_sent())
+ {
+ header('HTTP/1.1 500 Internal Server Error');
+ }
+
+ if ($e instanceof ErrorException)
+ {
+ // Use the human-readable error name
+ $code = Kohana::message('kohana/core.errors.'.$e->getSeverity());
+
+ if (version_compare(PHP_VERSION, '5.3', '<'))
+ {
+ // Workaround for a bug in ErrorException::getTrace() that exists in
+ // all PHP 5.2 versions. @see http://bugs.php.net/45895
+ for ($i = count($trace) - 1; $i > 0; --$i)
+ {
+ if (isset($trace[$i - 1]['args']))
+ {
+ // Re-position the arguments
+ $trace[$i]['args'] = $trace[$i - 1]['args'];
+
+ unset($trace[$i - 1]['args']);
+ }
+ }
+ }
+ }
+ }
+
+ // Clean the output buffer if one exists
+ ob_get_level() and ob_clean();
+
+ if ($template = Kohana::find_file('views', $template))
+ {
+ include $template;
+ }
+ }
+ catch (Exception $e)
+ {
+ // Clean the output buffer if one exists
+ ob_get_level() and ob_clean();
+
+ // Display the exception text
+ echo Kohana_Exception::text($e), "\n";
+ }
+
+ if (Kohana::$server_api === 'cli')
+ {
+ // Exit with an error status
+ exit(1);
+ }
+ }
+
+ /**
+ * Returns the template for this exception.
+ *
+ * @uses Kohana_Exception::$template
+ * @return string
+ */
+ public function getTemplate()
+ {
+ return Kohana_Exception::$template;
+ }
+
+ /**
+ * Sends an Internal Server Error header.
+ *
+ * @return void
+ */
+ public function sendHeaders()
+ {
+ // Send the 500 header
+ header('HTTP/1.1 500 Internal Server Error');
+ }
+
+ /**
+ * Returns an HTML string of information about a single variable.
+ *
+ * Borrows heavily on concepts from the Debug class of {@link http://nettephp.com/ Nette}.
+ *
+ * @param mixed variable to dump
+ * @param integer maximum length of strings
+ * @param integer maximum levels of recursion
+ * @return string
+ */
+ public static function dump($value, $length = 128, $max_level = 5)
+ {
+ return Kohana_Exception::_dump($value, $length, $max_level);
+ }
+
+ /**
+ * Helper for Kohana_Exception::dump(), handles recursion in arrays and objects.
+ *
+ * @param mixed variable to dump
+ * @param integer maximum length of strings
+ * @param integer maximum levels of recursion
+ * @param integer current recursion level (internal)
+ * @return string
+ */
+ private static function _dump( & $var, $length = 128, $max_level = 5, $level = 0)
+ {
+ if ($var === NULL)
+ {
+ return '<small>NULL</small>';
+ }
+ elseif (is_bool($var))
+ {
+ return '<small>bool</small> '.($var ? 'TRUE' : 'FALSE');
+ }
+ elseif (is_float($var))
+ {
+ return '<small>float</small> '.$var;
+ }
+ elseif (is_resource($var))
+ {
+ if (($type = get_resource_type($var)) === 'stream' AND $meta = stream_get_meta_data($var))
+ {
+ $meta = stream_get_meta_data($var);
+
+ if (isset($meta['uri']))
+ {
+ $file = $meta['uri'];
+
+ if (function_exists('stream_is_local'))
+ {
+ // Only exists on PHP >= 5.2.4
+ if (stream_is_local($file))
+ {
+ $file = Kohana_Exception::debug_path($file);
+ }
+ }
+
+ return '<small>resource</small><span>('.$type.')</span> '.htmlspecialchars($file, ENT_NOQUOTES, Kohana::CHARSET);
+ }
+ }
+ else
+ {
+ return '<small>resource</small><span>('.$type.')</span>';
+ }
+ }
+ elseif (is_string($var))
+ {
+ if (strlen($var) > $length)
+ {
+ // Encode the truncated string
+ $str = htmlspecialchars(substr($var, 0, $length), ENT_NOQUOTES, Kohana::CHARSET).'&nbsp;&hellip;';
+ }
+ else
+ {
+ // Encode the string
+ $str = htmlspecialchars($var, ENT_NOQUOTES, Kohana::CHARSET);
+ }
+
+ return '<small>string</small><span>('.strlen($var).')</span> "'.$str.'"';
+ }
+ elseif (is_array($var))
+ {
+ $output = array();
+
+ // Indentation for this variable
+ $space = str_repeat($s = ' ', $level);
+
+ static $marker;
+
+ if ($marker === NULL)
+ {
+ // Make a unique marker
+ $marker = uniqid("\x00");
+ }
+
+ if (empty($var))
+ {
+ // Do nothing
+ }
+ elseif (isset($var[$marker]))
+ {
+ $output[] = "(\n$space$s*RECURSION*\n$space)";
+ }
+ elseif ($level <= $max_level)
+ {
+ $output[] = "<span>(";
+
+ $var[$marker] = TRUE;
+ foreach ($var as $key => & $val)
+ {
+ if ($key === $marker) continue;
+ if ( ! is_int($key))
+ {
+ $key = '"'.$key.'"';
+ }
+
+ $output[] = "$space$s$key => ".Kohana_Exception::_dump($val, $length, $max_level, $level + 1);
+ }
+ unset($var[$marker]);
+
+ $output[] = "$space)</span>";
+ }
+ else
+ {
+ // Depth too great
+ $output[] = "(\n$space$s...\n$space)";
+ }
+
+ return '<small>array</small><span>('.count($var).')</span> '.implode("\n", $output);
+ }
+ elseif (is_object($var))
+ {
+ // Copy the object as an array
+ $array = (array) $var;
+
+ $output = array();
+
+ // Indentation for this variable
+ $space = str_repeat($s = ' ', $level);
+
+ $hash = spl_object_hash($var);
+
+ // Objects that are being dumped
+ static $objects = array();
+
+ if (empty($var))
+ {
+ // Do nothing
+ }
+ elseif (isset($objects[$hash]))
+ {
+ $output[] = "{\n$space$s*RECURSION*\n$space}";
+ }
+ elseif ($level <= $max_level)
+ {
+ $output[] = "<code>{";
+
+ $objects[$hash] = TRUE;
+ foreach ($array as $key => & $val)
+ {
+ if ($key[0] === "\x00")
+ {
+ // Determine if the access is private or protected
+ $access = '<small>'.($key[1] === '*' ? 'protected' : 'private').'</small>';
+
+ // Remove the access level from the variable name
+ $key = substr($key, strrpos($key, "\x00") + 1);
+ }
+ else
+ {
+ $access = '<small>public</small>';
+ }
+
+ $output[] = "$space$s$access $key => ".Kohana_Exception::_dump($val, $length, $max_level, $level + 1);
+ }
+ unset($objects[$hash]);
+
+ $output[] = "$space}</code>";
+ }
+ else
+ {
+ // Depth too great
+ $output[] = "{\n$space$s...\n$space}";
+ }
+
+ return '<small>object</small> <span>'.get_class($var).'('.count($array).')</span> '.implode("\n", $output);
+ }
+ else
+ {
+ return '<small>'.gettype($var).'</small> '.htmlspecialchars(print_r($var, TRUE), ENT_NOQUOTES, Kohana::CHARSET);
+ }
+ }
+
+ /**
+ * Removes APPPATH, SYSPATH, MODPATH, and DOCROOT from filenames, replacing
+ * them with the plain text equivalents.
+ *
+ * @param string path to sanitize
+ * @return string
+ */
+ public static function debug_path($file)
+ {
+ // Normalize directory separator
+ $file = str_replace('\\', '/', $file);
+
+ if (strpos($file, APPPATH) === 0)
+ {
+ $file = 'APPPATH/'.substr($file, strlen(APPPATH));
+ }
+ elseif (strpos($file, SYSPATH) === 0)
+ {
+ $file = 'SYSPATH/'.substr($file, strlen(SYSPATH));
+ }
+ elseif (strpos($file, MODPATH) === 0)
+ {
+ $file = 'MODPATH/'.substr($file, strlen(MODPATH));
+ }
+ elseif (strpos($file, DOCROOT) === 0)
+ {
+ $file = 'DOCROOT/'.substr($file, strlen(DOCROOT));
+ }
+
+ return $file;
+ }
+
+ /**
+ * Returns an array of lines from a file.
+ *
+ * // Returns the current line of the current file
+ * echo Kohana_Exception::debug_source(__FILE__, __LINE__);
+ *
+ * @param string file to open
+ * @param integer line number to find
+ * @param integer number of padding lines
+ * @return array
+ */
+ public static function debug_source($file, $line_number, $padding = 5)
+ {
+ // Make sure we can read the source file
+ if ( ! is_readable($file))
+ return array();
+
+ // Open the file and set the line position
+ $file = fopen($file, 'r');
+ $line = 0;
+
+ // Set the reading range
+ $range = array('start' => $line_number - $padding, 'end' => $line_number + $padding);
+
+ // Set the zero-padding amount for line numbers
+ $format = '% '.strlen($range['end']).'d';
+
+ $source = array();
+ while (($row = fgets($file)) !== FALSE)
+ {
+ // Increment the line number
+ if (++$line > $range['end'])
+ break;
+
+ if ($line >= $range['start'])
+ {
+ $source[sprintf($format, $line)] = $row;
+ }
+ }
+
+ // Close the file
+ fclose($file);
+
+ return $source;
+ }
+
+ /**
+ * Returns an array of strings that represent each step in the backtrace.
+ *
+ * @param array trace to analyze
+ * @return array
+ */
+ public static function trace($trace = NULL)
+ {
+ if ($trace === NULL)
+ {
+ // Start a new trace
+ $trace = debug_backtrace();
+ }
+
+ // Non-standard function calls
+ $statements = array('include', 'include_once', 'require', 'require_once');
+
+ $output = array();
+ foreach ($trace as $step)
+ {
+ if ( ! isset($step['function']))
+ {
+ // Invalid trace step
+ continue;
+ }
+
+ if (isset($step['file']) AND isset($step['line']))
+ {
+ // Include the source of this step
+ $source = Kohana_Exception::debug_source($step['file'], $step['line']);
+ }
+
+ if (isset($step['file']))
+ {
+ $file = $step['file'];
+
+ if (isset($step['line']))
+ {
+ $line = $step['line'];
+ }
+ }
+
+ // function()
+ $function = $step['function'];
+
+ if (in_array($step['function'], $statements))
+ {
+ if (empty($step['args']))
+ {
+ // No arguments
+ $args = array();
+ }
+ else
+ {
+ // Sanitize the file path
+ $args = array($step['args'][0]);
+ }
+ }
+ elseif (isset($step['args']))
+ {
+ if ($step['function'] === '{closure}')
+ {
+ // Introspection on closures in a stack trace is impossible
+ $params = NULL;
+ }
+ else
+ {
+ if (isset($step['class']))
+ {
+ if (method_exists($step['class'], $step['function']))
+ {
+ $reflection = new ReflectionMethod($step['class'], $step['function']);
+ }
+ else
+ {
+ $reflection = new ReflectionMethod($step['class'], '__call');
+ }
+ }
+ else
+ {
+ $reflection = new ReflectionFunction($step['function']);
+ }
+
+ // Get the function parameters
+ $params = $reflection->getParameters();
+ }
+
+ $args = array();
+
+ foreach ($step['args'] as $i => $arg)
+ {
+ if (isset($params[$i]))
+ {
+ // Assign the argument by the parameter name
+ $args[$params[$i]->name] = $arg;
+ }
+ else
+ {
+ // Assign the argument by number
+ $args[$i] = $arg;
+ }
+ }
+ }
+
+ if (isset($step['class']))
+ {
+ // Class->method() or Class::method()
+ $function = $step['class'].$step['type'].$step['function'];
+ }
+
+ $output[] = array(
+ 'function' => $function,
+ 'args' => isset($args) ? $args : NULL,
+ 'file' => isset($file) ? $file : NULL,
+ 'line' => isset($line) ? $line : NULL,
+ 'source' => isset($source) ? $source : NULL,
+ );
+
+ unset($function, $args, $file, $line, $source);
+ }
+
+ return $output;
+ }
+
+} // End Kohana Exception