diff options
Diffstat (limited to 'system/core/Kohana.php')
-rw-r--r-- | system/core/Kohana.php | 1166 |
1 files changed, 233 insertions, 933 deletions
diff --git a/system/core/Kohana.php b/system/core/Kohana.php index 8027975d..5258d635 100644 --- a/system/core/Kohana.php +++ b/system/core/Kohana.php @@ -2,60 +2,48 @@ /** * Provides Kohana-specific helper functions. This is where the magic happens! * - * $Id: Kohana.php 4372 2009-05-28 17:00:34Z ixmatus $ + * $Id: Kohana.php 4679 2009-11-10 01:45:52Z isaiah $ * * @package Core * @author Kohana Team - * @copyright (c) 2007-2008 Kohana Team - * @license http://kohanaphp.com/license.html + * @copyright (c) 2007-2009 Kohana Team + * @license http://kohanaphp.com/license */ -final class Kohana { + +// Test of Kohana is running in Windows +define('KOHANA_IS_WIN', DIRECTORY_SEPARATOR === '\\'); + +abstract class Kohana_Core { + + const VERSION = '2.4'; + const CODENAME = 'no_codename'; + const CHARSET = 'UTF-8'; + const LOCALE = 'en_US'; // The singleton instance of the controller public static $instance; // Output buffering level - private static $buffer_level; - - // Will be set to TRUE when an exception is caught - public static $has_error = FALSE; + protected static $buffer_level; // The final output that will displayed by Kohana public static $output = ''; - // The current user agent - public static $user_agent; - // The current locale public static $locale; - // Configuration - private static $configuration; - // Include paths - private static $include_paths; - - // Logged messages - private static $log; + protected static $include_paths; // Cache lifetime - private static $cache_lifetime; - - // Log levels - private static $log_levels = array - ( - 'error' => 1, - 'alert' => 2, - 'info' => 3, - 'debug' => 4, - ); + protected static $cache_lifetime; // Internal caches and write status - private static $internal_cache = array(); - private static $write_cache; - private static $internal_cache_path; - private static $internal_cache_key; - private static $internal_cache_encrypt; + protected static $internal_cache = array(); + protected static $write_cache; + protected static $internal_cache_path; + protected static $internal_cache_key; + protected static $internal_cache_encrypt; /** * Sets up the PHP environment. Adds error/exception handling, output @@ -75,10 +63,12 @@ final class Kohana { { static $run; - // This function can only be run once + // Only run this function once if ($run === TRUE) return; + $run = TRUE; + // Start the environment setup benchmark Benchmark::start(SYSTEM_BENCHMARK.'_environment_setup'); @@ -91,89 +81,86 @@ final class Kohana { // Define database error constant define('E_DATABASE_ERROR', 44); - if (self::$cache_lifetime = self::config('core.internal_cache')) + // Set the default charset for mb_* functions + mb_internal_encoding(Kohana::CHARSET); + + if (Kohana_Config::instance()->loaded() === FALSE) + { + // Re-parse the include paths + Kohana::include_paths(TRUE); + } + + if (Kohana::$cache_lifetime = Kohana::config('core.internal_cache')) { // Are we using encryption for caches? - self::$internal_cache_encrypt = self::config('core.internal_cache_encrypt'); - - if(self::$internal_cache_encrypt===TRUE) + Kohana::$internal_cache_encrypt = Kohana::config('core.internal_cache_encrypt'); + + if(Kohana::$internal_cache_encrypt===TRUE) { - self::$internal_cache_key = self::config('core.internal_cache_key'); - + Kohana::$internal_cache_key = Kohana::config('core.internal_cache_key'); + // Be sure the key is of acceptable length for the mcrypt algorithm used - self::$internal_cache_key = substr(self::$internal_cache_key, 0, 24); + Kohana::$internal_cache_key = substr(Kohana::$internal_cache_key, 0, 24); } - + // Set the directory to be used for the internal cache - if ( ! self::$internal_cache_path = self::config('core.internal_cache_path')) + if ( ! Kohana::$internal_cache_path = Kohana::config('core.internal_cache_path')) { - self::$internal_cache_path = APPPATH.'cache/'; + Kohana::$internal_cache_path = APPPATH.'cache/'; } // Load cached configuration and language files - self::$internal_cache['configuration'] = self::cache('configuration', self::$cache_lifetime); - self::$internal_cache['language'] = self::cache('language', self::$cache_lifetime); + Kohana::$internal_cache['configuration'] = Kohana::cache('configuration', Kohana::$cache_lifetime); + Kohana::$internal_cache['language'] = Kohana::cache('language', Kohana::$cache_lifetime); // Load cached file paths - self::$internal_cache['find_file_paths'] = self::cache('find_file_paths', self::$cache_lifetime); + Kohana::$internal_cache['find_file_paths'] = Kohana::cache('find_file_paths', Kohana::$cache_lifetime); // Enable cache saving - Event::add('system.shutdown', array(__CLASS__, 'internal_cache_save')); - } - - // Disable notices and "strict" errors - $ER = error_reporting(~E_NOTICE & ~E_STRICT); - - // Set the user agent - self::$user_agent = ( ! empty($_SERVER['HTTP_USER_AGENT']) ? trim($_SERVER['HTTP_USER_AGENT']) : ''); - - if (function_exists('date_default_timezone_set')) - { - $timezone = self::config('locale.timezone'); - - // Set default timezone, due to increased validation of date settings - // which cause massive amounts of E_NOTICEs to be generated in PHP 5.2+ - date_default_timezone_set(empty($timezone) ? date_default_timezone_get() : $timezone); + Event::add('system.shutdown', array('Kohana', 'internal_cache_save')); } - // Restore error reporting - error_reporting($ER); - // Start output buffering - ob_start(array(__CLASS__, 'output_buffer')); + ob_start(array('Kohana', 'output_buffer')); // Save buffering level - self::$buffer_level = ob_get_level(); + Kohana::$buffer_level = ob_get_level(); // Set autoloader spl_autoload_register(array('Kohana', 'auto_load')); - // Set error handler - set_error_handler(array('Kohana', 'exception_handler')); - - // Set exception handler - set_exception_handler(array('Kohana', 'exception_handler')); + // Register a shutdown function to handle system.shutdown events + register_shutdown_function(array('Kohana', 'shutdown')); // Send default text/html UTF-8 header - header('Content-Type: text/html; charset=UTF-8'); + header('Content-Type: text/html; charset='.Kohana::CHARSET); + + // Load i18n + new I18n; + + // Enable exception handling + Kohana_Exception::enable(); + + // Enable error handling + Kohana_PHP_Exception::enable(); // Load locales - $locales = self::config('locale.language'); + $locales = Kohana::config('locale.language'); - // Make first locale UTF-8 - $locales[0] .= '.UTF-8'; + // Make first locale the defined Kohana charset + $locales[0] .= '.'.Kohana::CHARSET; // Set locale information - self::$locale = setlocale(LC_ALL, $locales); + Kohana::$locale = setlocale(LC_ALL, $locales); - if (self::$configuration['core']['log_threshold'] > 0) - { - // Set the log directory - self::log_directory(self::$configuration['core']['log_directory']); + // Default to the default locale when none of the user defined ones where accepted + Kohana::$locale = ! Kohana::$locale ? Kohana::LOCALE.'.'.Kohana::CHARSET : Kohana::$locale; - // Enable log writing at shutdown - register_shutdown_function(array(__CLASS__, 'log_save')); - } + // Set locale for the I18n system + I18n::set_locale(Kohana::$locale); + + // Set and validate the timezone + date_default_timezone_set(Kohana::config('locale.timezone')); // Enable Kohana routing Event::add('system.routing', array('Router', 'find_uri')); @@ -183,15 +170,12 @@ final class Kohana { Event::add('system.execute', array('Kohana', 'instance')); // Enable Kohana 404 pages - Event::add('system.404', array('Kohana', 'show_404')); - - // Enable Kohana output handling - Event::add('system.shutdown', array('Kohana', 'shutdown')); + Event::add('system.404', array('Kohana_404_Exception', 'trigger')); - if (self::config('core.enable_hooks') === TRUE) + if (Kohana::config('core.enable_hooks') === TRUE) { // Find all the hook files - $hooks = self::list_files('hooks', TRUE); + $hooks = Kohana::list_files('hooks', TRUE); foreach ($hooks as $file) { @@ -200,14 +184,39 @@ final class Kohana { } } - // Setup is complete, prevent it from being run again - $run = TRUE; - // Stop the environment setup routine Benchmark::stop(SYSTEM_BENCHMARK.'_environment_setup'); } /** + * Cleans up the PHP environment. Disables error/exception handling and the + * auto-loading method and closes the output buffer. + * + * This method does not need to be called during normal system execution, + * however in some advanced situations it can be helpful. @see #1781 + * + * @return void + */ + public static function cleanup() + { + static $run; + + // Only run this function once + if ($run === TRUE) + return; + + $run = TRUE; + + Kohana_Exception::disable(); + + Kohana_PHP_Exception::disable(); + + spl_autoload_unregister(array('Kohana', 'auto_load')); + + Kohana::close_buffers(); + } + + /** * Loads the controller and initializes it. Runs the pre_controller, * post_controller_constructor, and post_controller events. Triggers * a system.404 event when the route cannot be mapped to a controller. @@ -218,12 +227,12 @@ final class Kohana { */ public static function & instance() { - if (self::$instance === NULL) + if (Kohana::$instance === NULL) { Benchmark::start(SYSTEM_BENCHMARK.'_controller_setup'); // Include the Controller file - require Router::$controller_path; + require_once Router::$controller_path; try { @@ -297,7 +306,7 @@ final class Kohana { Benchmark::stop(SYSTEM_BENCHMARK.'_controller_execution'); } - return self::$instance; + return Kohana::$instance; } /** @@ -312,279 +321,44 @@ final class Kohana { if ($process === TRUE) { // Add APPPATH as the first path - self::$include_paths = array(APPPATH); + Kohana::$include_paths = array(APPPATH); - foreach (self::$configuration['core']['modules'] as $path) + foreach (Kohana::config('core.modules') as $path) { if ($path = str_replace('\\', '/', realpath($path))) { // Add a valid path - self::$include_paths[] = $path.'/'; + Kohana::$include_paths[] = $path.'/'; } } // Add SYSPATH as the last path - self::$include_paths[] = SYSPATH; - - // Local fix for Kohana Ticket:2276 - self::$internal_cache['find_file_paths'] = array(); - if ( ! isset(self::$write_cache['find_file_paths'])) - { - // Write cache at shutdown - self::$write_cache['find_file_paths'] = TRUE; - } - } - - return self::$include_paths; - } - - /** - * Get a config item or group. - * - * @param string item name - * @param boolean force a forward slash (/) at the end of the item - * @param boolean is the item required? - * @return mixed - */ - public static function config($key, $slash = FALSE, $required = TRUE) - { - if (self::$configuration === NULL) - { - // Load core configuration - self::$configuration['core'] = self::config_load('core'); - - // Re-parse the include paths - self::include_paths(TRUE); - } - - // Get the group name from the key - $group = explode('.', $key, 2); - $group = $group[0]; + Kohana::$include_paths[] = SYSPATH; - if ( ! isset(self::$configuration[$group])) - { - // Load the configuration group - self::$configuration[$group] = self::config_load($group, $required); - } - - // Get the value of the key string - $value = self::key_string(self::$configuration, $key); - - if ($slash === TRUE AND is_string($value) AND $value !== '') - { - // Force the value to end with "/" - $value = rtrim($value, '/').'/'; - } - - return $value; - } - - /** - * Sets a configuration item, if allowed. - * - * @param string config key string - * @param string config value - * @return boolean - */ - public static function config_set($key, $value) - { - // Do this to make sure that the config array is already loaded - self::config($key); - - if (substr($key, 0, 7) === 'routes.') - { - // Routes cannot contain sub keys due to possible dots in regex - $keys = explode('.', $key, 2); - } - else - { - // Convert dot-noted key string to an array - $keys = explode('.', $key); - } - - // Used for recursion - $conf =& self::$configuration; - $last = count($keys) - 1; - - foreach ($keys as $i => $k) - { - if ($i === $last) + // Clear cached include paths + self::$internal_cache['find_file_paths'] = array(); + if ( ! isset(self::$write_cache['find_file_paths'])) { - $conf[$k] = $value; + // Write cache at shutdown + self::$write_cache['find_file_paths'] = TRUE; } - else - { - $conf =& $conf[$k]; - } - } - if ($key === 'core.modules') - { - // Reprocess the include paths - self::include_paths(TRUE); } - return TRUE; + return Kohana::$include_paths; } /** - * Load a config file. + * Get a config item or group proxies Kohana_Config. * - * @param string config filename, without extension - * @param boolean is the file required? - * @return array - */ - public static function config_load($name, $required = TRUE) - { - if ($name === 'core') - { - // Load the application configuration file - require APPPATH.'config/config'.EXT; - - if ( ! isset($config['site_domain'])) - { - // Invalid config file - die('Your Kohana application configuration file is not valid.'); - } - - return $config; - } - - if (isset(self::$internal_cache['configuration'][$name])) - return self::$internal_cache['configuration'][$name]; - - // Load matching configs - $configuration = array(); - - if ($files = self::find_file('config', $name, $required)) - { - foreach ($files as $file) - { - require $file; - - if (isset($config) AND is_array($config)) - { - // Merge in configuration - $configuration = array_merge($configuration, $config); - } - } - } - - if ( ! isset(self::$write_cache['configuration'])) - { - // Cache has changed - self::$write_cache['configuration'] = TRUE; - } - - return self::$internal_cache['configuration'][$name] = $configuration; - } - - /** - * Clears a config group from the cached configuration. - * - * @param string config group - * @return void - */ - public static function config_clear($group) - { - // Remove the group from config - unset(self::$configuration[$group], self::$internal_cache['configuration'][$group]); - - if ( ! isset(self::$write_cache['configuration'])) - { - // Cache has changed - self::$write_cache['configuration'] = TRUE; - } - } - - /** - * Add a new message to the log. - * - * @param string type of message - * @param string message text - * @return void - */ - public static function log($type, $message) - { - if (self::$log_levels[$type] <= self::$configuration['core']['log_threshold']) - { - $message = array(date('Y-m-d H:i:s P'), $type, $message); - - // Run the system.log event - Event::run('system.log', $message); - - self::$log[] = $message; - } - } - - /** - * Save all currently logged messages. - * - * @return void - */ - public static function log_save() - { - if (empty(self::$log) OR self::$configuration['core']['log_threshold'] < 1) - return; - - // Filename of the log - $filename = self::log_directory().date('Y-m-d').'.log'.EXT; - - if ( ! is_file($filename)) - { - // Write the SYSPATH checking header - file_put_contents($filename, - '<?php defined(\'SYSPATH\') or die(\'No direct script access.\'); ?>'.PHP_EOL.PHP_EOL); - - // Prevent external writes - chmod($filename, 0644); - } - - // Messages to write - $messages = array(); - - do - { - // Load the next mess - list ($date, $type, $text) = array_shift(self::$log); - - // Add a new message line - $messages[] = $date.' --- '.$type.': '.$text; - } - while ( ! empty(self::$log)); - - // Write messages to log file - file_put_contents($filename, implode(PHP_EOL, $messages).PHP_EOL, FILE_APPEND); - } - - /** - * Get or set the logging directory. - * - * @param string new log directory - * @return string + * @param string item name + * @param boolean force a forward slash (/) at the end of the item + * @param boolean is the item required? + * @return mixed */ - public static function log_directory($dir = NULL) + public static function config($key, $slash = FALSE, $required = FALSE) { - static $directory; - - if ( ! empty($dir)) - { - // Get the directory path - $dir = realpath($dir); - - if (is_dir($dir) AND is_writable($dir)) - { - // Change the log directory - $directory = str_replace('\\', '/', $dir).'/'; - } - else - { - // Log directory is invalid - throw new Kohana_Exception('core.log_dir_unwritable', $dir); - } - } - - return $directory; + return Kohana_Config::instance()->get($key,$slash,$required); } /** @@ -599,7 +373,7 @@ final class Kohana { { if ($lifetime > 0) { - $path = self::$internal_cache_path.'kohana_'.$name; + $path = Kohana::$internal_cache_path.'kohana_'.$name; if (is_file($path)) { @@ -607,17 +381,17 @@ final class Kohana { if ((time() - filemtime($path)) < $lifetime) { // Cache is valid! Now, do we need to decrypt it? - if(self::$internal_cache_encrypt===TRUE) + if(Kohana::$internal_cache_encrypt===TRUE) { $data = file_get_contents($path); - + $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB); $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND); - - $decrypted_text = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, self::$internal_cache_key, $data, MCRYPT_MODE_ECB, $iv); - + + $decrypted_text = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, Kohana::$internal_cache_key, $data, MCRYPT_MODE_ECB, $iv); + $cache = unserialize($decrypted_text); - + // If the key changed, delete the cache file if(!$cache) unlink($path); @@ -656,7 +430,7 @@ final class Kohana { if ($lifetime < 1) return FALSE; - $path = self::$internal_cache_path.'kohana_'.$name; + $path = Kohana::$internal_cache_path.'kohana_'.$name; if ($data === NULL) { @@ -666,15 +440,15 @@ final class Kohana { else { // Using encryption? Encrypt the data when we write it - if(self::$internal_cache_encrypt===TRUE) + if(Kohana::$internal_cache_encrypt===TRUE) { // Encrypt and write data to cache file $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB); $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND); - + // Serialize and encrypt! - $encrypted_text = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, self::$internal_cache_key, serialize($data), MCRYPT_MODE_ECB, $iv); - + $encrypted_text = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, Kohana::$internal_cache_key, serialize($data), MCRYPT_MODE_ECB, $iv); + return (bool) file_put_contents($path, $encrypted_text); } else @@ -699,15 +473,16 @@ final class Kohana { // Run the send_headers event Event::run('system.send_headers'); } - - self::$output = $output; - + + // Set final output + Kohana::$output = $output; + // Set and return the final output - return self::$output; + return Kohana::$output; } /** - * Closes all open output buffers, either by flushing or cleaning, and stores the Kohana + * Closes all open output buffers, either by flushing or cleaning, and stores * output buffer for display during shutdown. * * @param boolean disable to clear buffers, rather than flushing @@ -715,12 +490,12 @@ final class Kohana { */ public static function close_buffers($flush = TRUE) { - if (ob_get_level() >= self::$buffer_level) + if (ob_get_level() >= Kohana::$buffer_level) { // Set the close function $close = ($flush === TRUE) ? 'ob_end_flush' : 'ob_end_clean'; - while (ob_get_level() > self::$buffer_level) + while (ob_get_level() > Kohana::$buffer_level) { // Flush or clean the buffer $close(); @@ -738,14 +513,25 @@ final class Kohana { */ public static function shutdown() { + static $run; + + // Only run this function once + if ($run === TRUE) + return; + + $run = TRUE; + + // Run system.shutdown event + Event::run('system.shutdown'); + // Close output buffers - self::close_buffers(TRUE); + Kohana::close_buffers(TRUE); // Run the output event - Event::run('system.display', self::$output); + Event::run('system.display', Kohana::$output); // Render the final output - self::render(self::$output); + Kohana::render(Kohana::$output); } /** @@ -756,7 +542,7 @@ final class Kohana { */ public static function render($output) { - if (self::config('core.render_stats') === TRUE) + if (Kohana::config('core.render_stats') === TRUE) { // Fetch memory usage in MB $memory = function_exists('memory_get_usage') ? (memory_get_usage() / 1024 / 1024) : 0; @@ -776,8 +562,8 @@ final class Kohana { ), array ( - KOHANA_VERSION, - KOHANA_CODENAME, + KOHANA::VERSION, + KOHANA::CODENAME, $benchmark['time'], number_format($memory, 2).'MB', count(get_included_files()), @@ -786,215 +572,44 @@ final class Kohana { ); } - if ($level = self::config('core.output_compression') AND ini_get('output_handler') !== 'ob_gzhandler' AND (int) ini_get('zlib.output_compression') === 0) + if ($level = Kohana::config('core.output_compression') AND ini_get('output_handler') !== 'ob_gzhandler' AND (int) ini_get('zlib.output_compression') === 0) { - if ($level < 1 OR $level > 9) - { - // Normalize the level to be an integer between 1 and 9. This - // step must be done to prevent gzencode from triggering an error - $level = max(1, min($level, 9)); - } - - if (stripos(@$_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE) + if ($compress = request::preferred_encoding(array('gzip','deflate'), TRUE)) { - $compress = 'gzip'; - } - elseif (stripos(@$_SERVER['HTTP_ACCEPT_ENCODING'], 'deflate') !== FALSE) - { - $compress = 'deflate'; - } - } + if ($level < 1 OR $level > 9) + { + // Normalize the level to be an integer between 1 and 9. This + // step must be done to prevent gzencode from triggering an error + $level = max(1, min($level, 9)); + } - if (isset($compress) AND $level > 0) - { - switch ($compress) - { - case 'gzip': + if ($compress === 'gzip') + { // Compress output using gzip $output = gzencode($output, $level); - break; - case 'deflate': + } + elseif ($compress === 'deflate') + { // Compress output using zlib (HTTP deflate) $output = gzdeflate($output, $level); - break; - } - - // This header must be sent with compressed content to prevent - // browser caches from breaking - header('Vary: Accept-Encoding'); - - // Send the content encoding header - header('Content-Encoding: '.$compress); + } - // Sending Content-Length in CGI can result in unexpected behavior - if (stripos(PHP_SAPI, 'cgi') === FALSE) - { - header('Content-Length: '.strlen($output)); - } - } + // This header must be sent with compressed content to prevent + // browser caches from breaking + header('Vary: Accept-Encoding'); - echo $output; - } + // Send the content encoding header + header('Content-Encoding: '.$compress); - /** - * Displays a 404 page. - * - * @throws Kohana_404_Exception - * @param string URI of page - * @param string custom template - * @return void - */ - public static function show_404($page = FALSE, $template = FALSE) - { - throw new Kohana_404_Exception($page, $template); - } - - /** - * Dual-purpose PHP error and exception handler. Uses the kohana_error_page - * view to display the message. - * - * @param integer|object exception object or error code - * @param string error message - * @param string filename - * @param integer line number - * @return void - */ - public static function exception_handler($exception, $message = NULL, $file = NULL, $line = NULL) - { - try - { - // PHP errors have 5 args, always - $PHP_ERROR = (func_num_args() === 5); - - // Test to see if errors should be displayed - if ($PHP_ERROR AND (error_reporting() & $exception) === 0) - return; - - // This is useful for hooks to determine if a page has an error - self::$has_error = TRUE; - - // Error handling will use exactly 5 args, every time - if ($PHP_ERROR) - { - $code = $exception; - $type = 'PHP Error'; - $template = 'kohana_error_page'; - } - else - { - $code = $exception->getCode(); - $type = get_class($exception); - $message = $exception->getMessage(); - $file = $exception->getFile(); - $line = $exception->getLine(); - $template = ($exception instanceof Kohana_Exception) ? $exception->getTemplate() : 'kohana_error_page'; - } - - if (is_numeric($code)) - { - $codes = self::lang('errors'); - - if ( ! empty($codes[$code])) - { - list($level, $error, $description) = $codes[$code]; - } - else - { - $level = 1; - $error = $PHP_ERROR ? 'Unknown Error' : get_class($exception); - $description = ''; - } - } - else - { - // Custom error message, this will never be logged - $level = 5; - $error = $code; - $description = ''; - } - - // Remove the DOCROOT from the path, as a security precaution - $file = str_replace('\\', '/', realpath($file)); - $file = preg_replace('|^'.preg_quote(DOCROOT).'|', '', $file); - - if ($level <= self::$configuration['core']['log_threshold']) - { - // Log the error - self::log('error', self::lang('core.uncaught_exception', $type, $message, $file, $line)); - } - - if ($PHP_ERROR) - { - $description = self::lang('errors.'.E_RECOVERABLE_ERROR); - $description = is_array($description) ? $description[2] : ''; - - if ( ! headers_sent()) + // Sending Content-Length in CGI can result in unexpected behavior + if (stripos(PHP_SAPI, 'cgi') === FALSE) { - // Send the 500 header - header('HTTP/1.1 500 Internal Server Error'); + header('Content-Length: '.strlen($output)); } } - else - { - if (method_exists($exception, 'sendHeaders') AND ! headers_sent()) - { - // Send the headers if they have not already been sent - $exception->sendHeaders(); - } - } - - // Close all output buffers except for Kohana - while (ob_get_level() > self::$buffer_level) - { - ob_end_clean(); - } - - // Test if display_errors is on - if (self::$configuration['core']['display_errors'] === TRUE) - { - if ( ! IN_PRODUCTION AND $line != FALSE) - { - // Remove the first entry of debug_backtrace(), it is the exception_handler call - $trace = $PHP_ERROR ? array_slice(debug_backtrace(), 1) : $exception->getTrace(); - - // Beautify backtrace - $trace = self::backtrace($trace); - } - - // Load the error - require self::find_file('views', empty($template) ? 'kohana_error_page' : $template); - } - else - { - // Get the i18n messages - $error = self::lang('core.generic_error'); - $message = self::lang('core.errors_disabled', url::site(), url::site(Router::$current_uri)); - - // Load the errors_disabled view - require self::find_file('views', 'kohana_error_disabled'); - } - - if ( ! Event::has_run('system.shutdown')) - { - // Run the shutdown even to ensure a clean exit - Event::run('system.shutdown'); - } - - // Turn off error reporting - error_reporting(0); - exit; - } - catch (Exception $e) - { - if (IN_PRODUCTION) - { - die('Fatal Error'); - } - else - { - die('Fatal Error: '.$e->getMessage().' File: '.$e->getFile().' Line: '.$e->getLine()); - } } + + echo $output; } /** @@ -1006,7 +621,7 @@ final class Kohana { */ public static function auto_load($class) { - if (class_exists($class, FALSE)) + if (class_exists($class, FALSE) OR interface_exists($class, FALSE)) return TRUE; if (($suffix = strrpos($class, '_')) > 0) @@ -1051,7 +666,7 @@ final class Kohana { $file = $class; } - if ($filename = self::find_file($type, $file)) + if ($filename = Kohana::find_file($type, $file)) { // Load the class require $filename; @@ -1062,7 +677,7 @@ final class Kohana { return FALSE; } - if ($filename = self::find_file($type, self::$configuration['core']['extension_prefix'].$class)) + if ($filename = Kohana::find_file($type, Kohana::config('core.extension_prefix').$class)) { // Load the class extension require $filename; @@ -1121,16 +736,16 @@ final class Kohana { // Search path $search = $directory.'/'.$filename.$ext; - if (isset(self::$internal_cache['find_file_paths'][$search])) - return self::$internal_cache['find_file_paths'][$search]; + if (isset(Kohana::$internal_cache['find_file_paths'][$search])) + return Kohana::$internal_cache['find_file_paths'][$search]; // Load include paths - $paths = self::$include_paths; + $paths = Kohana::$include_paths; // Nothing found, yet $found = NULL; - if ($directory === 'config' OR $directory === 'i18n') + if ($directory === 'config' OR $directory === 'messages' OR $directory === 'i18n') { // Search in reverse, for merging $paths = array_reverse($paths); @@ -1167,7 +782,7 @@ final class Kohana { $directory = 'core.'.inflector::singular($directory); // If the file is required, throw an exception - throw new Kohana_Exception('core.resource_not_found', self::lang($directory), $filename); + throw new Kohana_Exception('The requested :resource:, :file:, could not be found', array(':resource:' => Kohana::message($directory), ':file:' =>$filename)); } else { @@ -1176,13 +791,13 @@ final class Kohana { } } - if ( ! isset(self::$write_cache['find_file_paths'])) + if ( ! isset(Kohana::$write_cache['find_file_paths'])) { // Write cache at shutdown - self::$write_cache['find_file_paths'] = TRUE; + Kohana::$write_cache['find_file_paths'] = TRUE; } - return self::$internal_cache['find_file_paths'][$search] = $found; + return Kohana::$internal_cache['find_file_paths'][$search] = $found; } /** @@ -1190,45 +805,54 @@ final class Kohana { * * @param string directory to search * @param boolean list all files to the maximum depth? + * @param string list all files having extension $ext * @param string full path to search (used for recursion, *never* set this manually) * @return array filenames and directories */ - public static function list_files($directory, $recursive = FALSE, $path = FALSE) + public static function list_files($directory, $recursive = FALSE, $ext = EXT, $path = FALSE) { $files = array(); if ($path === FALSE) { - $paths = array_reverse(self::include_paths()); + $paths = array_reverse(Kohana::include_paths()); foreach ($paths as $path) { // Recursively get and merge all files - $files = array_merge($files, self::list_files($directory, $recursive, $path.$directory)); + $files = array_merge($files, Kohana::list_files($directory, $recursive, $ext, $path.$directory)); } } else { $path = rtrim($path, '/').'/'; - if (is_readable($path)) + if (is_readable($path) AND $items = glob($path.'*')) { - $items = (array) glob($path.'*'); + $ext_pos = 0 - strlen($ext); - if ( ! empty($items)) + foreach ($items as $index => $item) { - foreach ($items as $index => $item) - { - $files[] = $item = str_replace('\\', '/', $item); + $item = str_replace('\\', '/', $item); + if (is_dir($item)) + { // Handle recursion - if (is_dir($item) AND $recursive == TRUE) + if ($recursive === TRUE) { // Filename should only be the basename $item = pathinfo($item, PATHINFO_BASENAME); // Append sub-directory search - $files = array_merge($files, self::list_files($directory, TRUE, $path.$item)); + $files = array_merge($files, Kohana::list_files($directory, TRUE, $ext, $path.$item)); + } + } + else + { + // File extension must match + if ($ext_pos === 0 OR substr($item, $ext_pos) === $ext) + { + $files[] = $item; } } } @@ -1238,59 +862,48 @@ final class Kohana { return $files; } + /** - * Fetch an i18n language item. + * Fetch a message item. * * @param string language key to fetch * @param array additional information to insert into the line * @return string i18n language string, or the requested key if the i18n item is not found */ - public static function lang($key, $args = array()) + public static function message($key, $args = array()) { // Extract the main group from the key $group = explode('.', $key, 2); $group = $group[0]; // Get locale name - $locale = self::config('locale.language.0'); + $locale = Kohana::config('locale.language.0'); - if ( ! isset(self::$internal_cache['language'][$locale][$group])) + if ( ! isset(Kohana::$internal_cache['messages'][$group])) { // Messages for this group $messages = array(); - if ($files = self::find_file('i18n', $locale.'/'.$group)) + if ($file = Kohana::find_file('messages', $group)) { - foreach ($files as $file) - { - include $file; - - // Merge in configuration - if ( ! empty($lang) AND is_array($lang)) - { - foreach ($lang as $k => $v) - { - $messages[$k] = $v; - } - } - } + include $file[0]; } - if ( ! isset(self::$write_cache['language'])) + if ( ! isset(Kohana::$write_cache['messages'])) { // Write language cache - self::$write_cache['language'] = TRUE; + Kohana::$write_cache['messages'] = TRUE; } - self::$internal_cache['language'][$locale][$group] = $messages; + Kohana::$internal_cache['messages'][$group] = $messages; } // Get the line from cache - $line = self::key_string(self::$internal_cache['language'][$locale], $key); + $line = Kohana::key_string(Kohana::$internal_cache['messages'], $key); if ($line === NULL) { - self::log('error', 'Missing i18n entry '.$key.' for language '.$locale); + Kohana_Log::add('error', 'Missing messages entry '.$key.' for message '.$group); // Return the key string as fallback return $key; @@ -1427,121 +1040,6 @@ final class Kohana { } /** - * Retrieves current user agent information: - * keys: browser, version, platform, mobile, robot, referrer, languages, charsets - * tests: is_browser, is_mobile, is_robot, accept_lang, accept_charset - * - * @param string key or test name - * @param string used with "accept" tests: user_agent(accept_lang, en) - * @return array languages and charsets - * @return string all other keys - * @return boolean all tests - */ - public static function user_agent($key = 'agent', $compare = NULL) - { - static $info; - - // Return the raw string - if ($key === 'agent') - return self::$user_agent; - - if ($info === NULL) - { - // Parse the user agent and extract basic information - $agents = self::config('user_agents'); - - foreach ($agents as $type => $data) - { - foreach ($data as $agent => $name) - { - if (stripos(self::$user_agent, $agent) !== FALSE) - { - if ($type === 'browser' AND preg_match('|'.preg_quote($agent).'[^0-9.]*+([0-9.][0-9.a-z]*)|i', self::$user_agent, $match)) - { - // Set the browser version - $info['version'] = $match[1]; - } - - // Set the agent name - $info[$type] = $name; - break; - } - } - } - } - - if (empty($info[$key])) - { - switch ($key) - { - case 'is_robot': - case 'is_browser': - case 'is_mobile': - // A boolean result - $return = ! empty($info[substr($key, 3)]); - break; - case 'languages': - $return = array(); - if ( ! empty($_SERVER['HTTP_ACCEPT_LANGUAGE'])) - { - if (preg_match_all('/[-a-z]{2,}/', strtolower(trim($_SERVER['HTTP_ACCEPT_LANGUAGE'])), $matches)) - { - // Found a result - $return = $matches[0]; - } - } - break; - case 'charsets': - $return = array(); - if ( ! empty($_SERVER['HTTP_ACCEPT_CHARSET'])) - { - if (preg_match_all('/[-a-z0-9]{2,}/', strtolower(trim($_SERVER['HTTP_ACCEPT_CHARSET'])), $matches)) - { - // Found a result - $return = $matches[0]; - } - } - break; - case 'referrer': - if ( ! empty($_SERVER['HTTP_REFERER'])) - { - // Found a result - $return = trim($_SERVER['HTTP_REFERER']); - } - break; - } - - // Cache the return value - isset($return) and $info[$key] = $return; - } - - if ( ! empty($compare)) - { - // The comparison must always be lowercase - $compare = strtolower($compare); - - switch ($key) - { - case 'accept_lang': - // Check if the lange is accepted - return in_array($compare, self::user_agent('languages')); - break; - case 'accept_charset': - // Check if the charset is accepted - return in_array($compare, self::user_agent('charsets')); - break; - default: - // Invalid comparison - return FALSE; - break; - } - } - - // Return the key, if set - return isset($info[$key]) ? $info[$key] : NULL; - } - - /** * Quick debugging of any variable. Any number of parameters can be set. * * @return string @@ -1557,98 +1055,35 @@ final class Kohana { foreach ($params as $var) { - $output[] = '<pre>('.gettype($var).') '.html::specialchars(print_r($var, TRUE)).'</pre>'; + $value = is_bool($var) ? ($var ? 'true' : 'false') : print_r($var, TRUE); + $output[] = '<pre>('.gettype($var).') '.htmlspecialchars($value, ENT_QUOTES, Kohana::CHARSET).'</pre>'; } return implode("\n", $output); } /** - * Displays nice backtrace information. - * @see http://php.net/debug_backtrace - * - * @param array backtrace generated by an exception or debug_backtrace - * @return string - */ - public static function backtrace($trace) - { - if ( ! is_array($trace)) - return; - - // Final output - $output = array(); - - foreach ($trace as $entry) - { - $temp = '<li>'; - - if (isset($entry['file'])) - { - $temp .= self::lang('core.error_file_line', preg_replace('!^'.preg_quote(DOCROOT).'!', '', $entry['file']), $entry['line']); - } - - $temp .= '<pre>'; - - if (isset($entry['class'])) - { - // Add class and call type - $temp .= $entry['class'].$entry['type']; - } - - // Add function - $temp .= $entry['function'].'( '; - - // Add function args - if (isset($entry['args']) AND is_array($entry['args'])) - { - // Separator starts as nothing - $sep = ''; - - while ($arg = array_shift($entry['args'])) - { - if (is_string($arg) AND is_file($arg)) - { - // Remove docroot from filename - $arg = preg_replace('!^'.preg_quote(DOCROOT).'!', '', $arg); - } - - $temp .= $sep.html::specialchars(print_r($arg, TRUE)); - - // Change separator to a comma - $sep = ', '; - } - } - - $temp .= ' )</pre></li>'; - - $output[] = $temp; - } - - return '<ul class="backtrace">'.implode("\n", $output).'</ul>'; - } - - /** * Saves the internal caches: configuration, include paths, etc. * * @return boolean */ public static function internal_cache_save() { - if ( ! is_array(self::$write_cache)) + if ( ! is_array(Kohana::$write_cache)) return FALSE; // Get internal cache names - $caches = array_keys(self::$write_cache); + $caches = array_keys(Kohana::$write_cache); // Nothing written $written = FALSE; foreach ($caches as $cache) { - if (isset(self::$internal_cache[$cache])) + if (isset(Kohana::$internal_cache[$cache])) { // Write the cache file - self::cache_save($cache, self::$internal_cache[$cache], self::$configuration['core']['internal_cache']); + Kohana::cache_save($cache, Kohana::$internal_cache[$cache], Kohana::config('core.internal_cache')); // A cache has been written $written = TRUE; @@ -1659,138 +1094,3 @@ final class Kohana { } } // End Kohana - -/** - * Creates a generic i18n exception. - */ -class Kohana_Exception extends Exception { - - // Template file - protected $template = 'kohana_error_page'; - - // Header - protected $header = FALSE; - - // Error code - protected $code = E_KOHANA; - - /** - * Set exception message. - * - * @param string i18n language key for the message - * @param array addition line parameters - */ - public function __construct($error) - { - $args = array_slice(func_get_args(), 1); - - // Fetch the error message - $message = Kohana::lang($error, $args); - - if ($message === $error OR empty($message)) - { - // Unable to locate the message for the error - $message = 'Unknown Exception: '.$error; - } - - // Sets $this->message the proper way - parent::__construct($message); - } - - /** - * Magic method for converting an object to a string. - * - * @return string i18n message - */ - public function __toString() - { - return (string) $this->message; - } - - /** - * Fetch the template name. - * - * @return string - */ - public function getTemplate() - { - return $this->template; - } - - /** - * Sends an Internal Server Error header. - * - * @return void - */ - public function sendHeaders() - { - // Send the 500 header - header('HTTP/1.1 500 Internal Server Error'); - } - -} // End Kohana Exception - -/** - * Creates a custom exception. - */ -class Kohana_User_Exception extends Kohana_Exception { - - /** - * Set exception title and message. - * - * @param string exception title string - * @param string exception message string - * @param string custom error template - */ - public function __construct($title, $message, $template = FALSE) - { - Exception::__construct($message); - - $this->code = $title; - - if ($template !== FALSE) - { - $this->template = $template; - } - } - -} // End Kohana PHP Exception - -/** - * Creates a Page Not Found exception. - */ -class Kohana_404_Exception extends Kohana_Exception { - - protected $code = E_PAGE_NOT_FOUND; - - /** - * Set internal properties. - * - * @param string URL of page - * @param string custom error template - */ - public function __construct($page = FALSE, $template = FALSE) - { - if ($page === FALSE) - { - // Construct the page URI using Router properties - $page = Router::$current_uri.Router::$url_suffix.Router::$query_string; - } - - Exception::__construct(Kohana::lang('core.page_not_found', $page)); - - $this->template = $template; - } - - /** - * Sends "File Not Found" headers, to emulate server behavior. - * - * @return void - */ - public function sendHeaders() - { - // Send the 404 header - header('HTTP/1.1 404 File Not Found'); - } - -} // End Kohana 404 Exception |