1, 'alert' => 2, 'info' => 3, 'debug' => 4, ); // Internal caches and write status private static $internal_cache = array(); private static $write_cache; private static $internal_cache_path; private static $internal_cache_key; private static $internal_cache_encrypt; /** * Sets up the PHP environment. Adds error/exception handling, output * buffering, and adds an auto-loading method for loading classes. * * This method is run immediately when this file is loaded, and is * benchmarked as environment_setup. * * For security, this function also destroys the $_REQUEST global variable. * Using the proper global (GET, POST, COOKIE, etc) is inherently more secure. * The recommended way to fetch a global variable is using the Input library. * @see http://www.php.net/globals * * @return void */ public static function setup() { static $run; // This function can only be run once if ($run === TRUE) return; // Start the environment setup benchmark Benchmark::start(SYSTEM_BENCHMARK.'_environment_setup'); // Define Kohana error constant define('E_KOHANA', 42); // Define 404 error constant define('E_PAGE_NOT_FOUND', 43); // Define database error constant define('E_DATABASE_ERROR', 44); if (self::$cache_lifetime = self::config('core.internal_cache')) { // Are we using encryption for caches? self::$internal_cache_encrypt = self::config('core.internal_cache_encrypt'); if(self::$internal_cache_encrypt===TRUE) { self::$internal_cache_key = self::config('core.internal_cache_key'); // Be sure the key is of acceptable length for the mcrypt algorithm used self::$internal_cache_key = substr(self::$internal_cache_key, 0, 24); } // Set the directory to be used for the internal cache if ( ! self::$internal_cache_path = self::config('core.internal_cache_path')) { self::$internal_cache_path = APPPATH.'cache/'; } // Load cached configuration and language files self::$internal_cache['configuration'] = self::cache('configuration', self::$cache_lifetime); self::$internal_cache['language'] = self::cache('language', self::$cache_lifetime); // Load cached file paths self::$internal_cache['find_file_paths'] = self::cache('find_file_paths', self::$cache_lifetime); // Enable cache saving Event::add('system.shutdown', array(__CLASS__, 'internal_cache_save')); } // Disable notices and "strict" errors $ER = error_reporting(~E_NOTICE & ~E_STRICT); // Set the user agent self::$user_agent = ( ! empty($_SERVER['HTTP_USER_AGENT']) ? trim($_SERVER['HTTP_USER_AGENT']) : ''); if (function_exists('date_default_timezone_set')) { $timezone = self::config('locale.timezone'); // Set default timezone, due to increased validation of date settings // which cause massive amounts of E_NOTICEs to be generated in PHP 5.2+ date_default_timezone_set(empty($timezone) ? date_default_timezone_get() : $timezone); } // Restore error reporting error_reporting($ER); // Start output buffering ob_start(array(__CLASS__, 'output_buffer')); // Save buffering level self::$buffer_level = ob_get_level(); // Set autoloader spl_autoload_register(array('Kohana', 'auto_load')); // Set error handler set_error_handler(array('Kohana', 'exception_handler')); // Set exception handler set_exception_handler(array('Kohana', 'exception_handler')); // Send default text/html UTF-8 header header('Content-Type: text/html; charset=UTF-8'); // Load locales $locales = self::config('locale.language'); // Make first locale UTF-8 $locales[0] .= '.UTF-8'; // Set locale information self::$locale = setlocale(LC_ALL, $locales); if (self::$configuration['core']['log_threshold'] > 0) { // Set the log directory self::log_directory(self::$configuration['core']['log_directory']); // Enable log writing at shutdown register_shutdown_function(array(__CLASS__, 'log_save')); } // Enable Kohana routing Event::add('system.routing', array('Router', 'find_uri')); Event::add('system.routing', array('Router', 'setup')); // Enable Kohana controller initialization Event::add('system.execute', array('Kohana', 'instance')); // Enable Kohana 404 pages Event::add('system.404', array('Kohana', 'show_404')); // Enable Kohana output handling Event::add('system.shutdown', array('Kohana', 'shutdown')); if (self::config('core.enable_hooks') === TRUE) { // Find all the hook files $hooks = self::list_files('hooks', TRUE); foreach ($hooks as $file) { // Load the hook include $file; } } // Setup is complete, prevent it from being run again $run = TRUE; // Stop the environment setup routine Benchmark::stop(SYSTEM_BENCHMARK.'_environment_setup'); } /** * Loads the controller and initializes it. Runs the pre_controller, * post_controller_constructor, and post_controller events. Triggers * a system.404 event when the route cannot be mapped to a controller. * * This method is benchmarked as controller_setup and controller_execution. * * @return object instance of controller */ public static function & instance() { if (self::$instance === NULL) { Benchmark::start(SYSTEM_BENCHMARK.'_controller_setup'); // Include the Controller file require Router::$controller_path; try { // Start validation of the controller $class = new ReflectionClass(ucfirst(Router::$controller).'_Controller'); } catch (ReflectionException $e) { // Controller does not exist Event::run('system.404'); } if ($class->isAbstract() OR (IN_PRODUCTION AND $class->getConstant('ALLOW_PRODUCTION') == FALSE)) { // Controller is not allowed to run in production Event::run('system.404'); } // Run system.pre_controller Event::run('system.pre_controller'); // Create a new controller instance $controller = $class->newInstance(); // Controller constructor has been executed Event::run('system.post_controller_constructor'); try { // Load the controller method $method = $class->getMethod(Router::$method); // Method exists if (Router::$method[0] === '_') { // Do not allow access to hidden methods Event::run('system.404'); } if ($method->isProtected() or $method->isPrivate()) { // Do not attempt to invoke protected methods throw new ReflectionException('protected controller method'); } // Default arguments $arguments = Router::$arguments; } catch (ReflectionException $e) { // Use __call instead $method = $class->getMethod('__call'); // Use arguments in __call format $arguments = array(Router::$method, Router::$arguments); } // Stop the controller setup benchmark Benchmark::stop(SYSTEM_BENCHMARK.'_controller_setup'); // Start the controller execution benchmark Benchmark::start(SYSTEM_BENCHMARK.'_controller_execution'); // Execute the controller method $method->invokeArgs($controller, $arguments); // Controller method has been executed Event::run('system.post_controller'); // Stop the controller execution benchmark Benchmark::stop(SYSTEM_BENCHMARK.'_controller_execution'); } return self::$instance; } /** * Get all include paths. APPPATH is the first path, followed by module * paths in the order they are configured, follow by the SYSPATH. * * @param boolean re-process the include paths * @return array */ public static function include_paths($process = FALSE) { if ($process === TRUE) { // Add APPPATH as the first path self::$include_paths = array(APPPATH); foreach (self::$configuration['core']['modules'] as $path) { if ($path = str_replace('\\', '/', realpath($path))) { // Add a valid path self::$include_paths[] = $path.'/'; } } // Add SYSPATH as the last path self::$include_paths[] = SYSPATH; } return self::$include_paths; } /** * Get a config item or group. * * @param string item name * @param boolean force a forward slash (/) at the end of the item * @param boolean is the item required? * @return mixed */ public static function config($key, $slash = FALSE, $required = TRUE) { if (self::$configuration === NULL) { // Load core configuration self::$configuration['core'] = self::config_load('core'); // Re-parse the include paths self::include_paths(TRUE); } // Get the group name from the key $group = explode('.', $key, 2); $group = $group[0]; if ( ! isset(self::$configuration[$group])) { // Load the configuration group self::$configuration[$group] = self::config_load($group, $required); } // Get the value of the key string $value = self::key_string(self::$configuration, $key); if ($slash === TRUE AND is_string($value) AND $value !== '') { // Force the value to end with "/" $value = rtrim($value, '/').'/'; } return $value; } /** * Sets a configuration item, if allowed. * * @param string config key string * @param string config value * @return boolean */ public static function config_set($key, $value) { // Do this to make sure that the config array is already loaded self::config($key); if (substr($key, 0, 7) === 'routes.') { // Routes cannot contain sub keys due to possible dots in regex $keys = explode('.', $key, 2); } else { // Convert dot-noted key string to an array $keys = explode('.', $key); } // Used for recursion $conf =& self::$configuration; $last = count($keys) - 1; foreach ($keys as $i => $k) { if ($i === $last) { $conf[$k] = $value; } else { $conf =& $conf[$k]; } } if ($key === 'core.modules') { // Reprocess the include paths self::include_paths(TRUE); } return TRUE; } /** * Load a config file. * * @param string config filename, without extension * @param boolean is the file required? * @return array */ public static function config_load($name, $required = TRUE) { if ($name === 'core') { // Load the application configuration file require APPPATH.'config/config'.EXT; if ( ! isset($config['site_domain'])) { // Invalid config file die('Your Kohana application configuration file is not valid.'); } return $config; } if (isset(self::$internal_cache['configuration'][$name])) return self::$internal_cache['configuration'][$name]; // Load matching configs $configuration = array(); if ($files = self::find_file('config', $name, $required)) { foreach ($files as $file) { require $file; if (isset($config) AND is_array($config)) { // Merge in configuration $configuration = array_merge($configuration, $config); } } } if ( ! isset(self::$write_cache['configuration'])) { // Cache has changed self::$write_cache['configuration'] = TRUE; } return self::$internal_cache['configuration'][$name] = $configuration; } /** * Clears a config group from the cached configuration. * * @param string config group * @return void */ public static function config_clear($group) { // Remove the group from config unset(self::$configuration[$group], self::$internal_cache['configuration'][$group]); if ( ! isset(self::$write_cache['configuration'])) { // Cache has changed self::$write_cache['configuration'] = TRUE; } } /** * Add a new message to the log. * * @param string type of message * @param string message text * @return void */ public static function log($type, $message) { if (self::$log_levels[$type] <= self::$configuration['core']['log_threshold']) { $message = array(date('Y-m-d H:i:s P'), $type, $message); // Run the system.log event Event::run('system.log', $message); self::$log[] = $message; } } /** * Save all currently logged messages. * * @return void */ public static function log_save() { if (empty(self::$log) OR self::$configuration['core']['log_threshold'] < 1) return; // Filename of the log $filename = self::log_directory().date('Y-m-d').'.log'.EXT; if ( ! is_file($filename)) { // Write the SYSPATH checking header file_put_contents($filename, ''.PHP_EOL.PHP_EOL); // Prevent external writes chmod($filename, 0644); } // Messages to write $messages = array(); do { // Load the next mess list ($date, $type, $text) = array_shift(self::$log); // Add a new message line $messages[] = $date.' --- '.$type.': '.$text; } while ( ! empty(self::$log)); // Write messages to log file file_put_contents($filename, implode(PHP_EOL, $messages).PHP_EOL, FILE_APPEND); } /** * Get or set the logging directory. * * @param string new log directory * @return string */ public static function log_directory($dir = NULL) { static $directory; if ( ! empty($dir)) { // Get the directory path $dir = realpath($dir); if (is_dir($dir) AND is_writable($dir)) { // Change the log directory $directory = str_replace('\\', '/', $dir).'/'; } else { // Log directory is invalid throw new Kohana_Exception('core.log_dir_unwritable', $dir); } } return $directory; } /** * Load data from a simple cache file. This should only be used internally, * and is NOT a replacement for the Cache library. * * @param string unique name of cache * @param integer expiration in seconds * @return mixed */ public static function cache($name, $lifetime) { if ($lifetime > 0) { $path = self::$internal_cache_path.'kohana_'.$name; if (is_file($path)) { // Check the file modification time if ((time() - filemtime($path)) < $lifetime) { // Cache is valid! Now, do we need to decrypt it? if(self::$internal_cache_encrypt===TRUE) { $data = file_get_contents($path); $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB); $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND); $decrypted_text = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, self::$internal_cache_key, $data, MCRYPT_MODE_ECB, $iv); $cache = unserialize($decrypted_text); // If the key changed, delete the cache file if(!$cache) unlink($path); // If cache is false (as above) return NULL, otherwise, return the cache return ($cache ? $cache : NULL); } else { return unserialize(file_get_contents($path)); } } else { // Cache is invalid, delete it unlink($path); } } } // No cache found return NULL; } /** * Save data to a simple cache file. This should only be used internally, and * is NOT a replacement for the Cache library. * * @param string cache name * @param mixed data to cache * @param integer expiration in seconds * @return boolean */ public static function cache_save($name, $data, $lifetime) { if ($lifetime < 1) return FALSE; $path = self::$internal_cache_path.'kohana_'.$name; if ($data === NULL) { // Delete cache return (is_file($path) and unlink($path)); } else { // Using encryption? Encrypt the data when we write it if(self::$internal_cache_encrypt===TRUE) { // Encrypt and write data to cache file $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB); $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND); // Serialize and encrypt! $encrypted_text = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, self::$internal_cache_key, serialize($data), MCRYPT_MODE_ECB, $iv); return (bool) file_put_contents($path, $encrypted_text); } else { // Write data to cache file return (bool) file_put_contents($path, serialize($data)); } } } /** * Kohana output handler. Called during ob_clean, ob_flush, and their variants. * * @param string current output buffer * @return string */ public static function output_buffer($output) { // Could be flushing, so send headers first if ( ! Event::has_run('system.send_headers')) { // Run the send_headers event Event::run('system.send_headers'); } self::$output = $output; // Set and return the final output return self::$output; } /** * Closes all open output buffers, either by flushing or cleaning, and stores the Kohana * output buffer for display during shutdown. * * @param boolean disable to clear buffers, rather than flushing * @return void */ public static function close_buffers($flush = TRUE) { if (ob_get_level() >= self::$buffer_level) { // Set the close function $close = ($flush === TRUE) ? 'ob_end_flush' : 'ob_end_clean'; while (ob_get_level() > self::$buffer_level) { // Flush or clean the buffer $close(); } // Store the Kohana output buffer 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() { // Close output buffers self::close_buffers(TRUE); // Run the output event Event::run('system.display', self::$output); // Render the final output self::render(self::$output); } /** * Inserts global Kohana variables into the generated output and prints it. * * @param string final output that will displayed * @return void */ public static function render($output) { if (self::config('core.render_stats') === TRUE) { // Fetch memory usage in MB $memory = function_exists('memory_get_usage') ? (memory_get_usage() / 1024 / 1024) : 0; // Fetch benchmark for page execution time $benchmark = Benchmark::get(SYSTEM_BENCHMARK.'_total_execution'); // Replace the global template variables $output = str_replace( array ( '{kohana_version}', '{kohana_codename}', '{execution_time}', '{memory_usage}', '{included_files}', ), array ( KOHANA_VERSION, KOHANA_CODENAME, $benchmark['time'], number_format($memory, 2).'MB', count(get_included_files()), ), $output ); } if ($level = self::config('core.output_compression') AND ini_get('output_handler') !== 'ob_gzhandler' AND (int) ini_get('zlib.output_compression') === 0) { if ($level < 1 OR $level > 9) { // Normalize the level to be an integer between 1 and 9. This // step must be done to prevent gzencode from triggering an error $level = max(1, min($level, 9)); } if (stripos(@$_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE) { $compress = 'gzip'; } elseif (stripos(@$_SERVER['HTTP_ACCEPT_ENCODING'], 'deflate') !== FALSE) { $compress = 'deflate'; } } if (isset($compress) AND $level > 0) { switch ($compress) { case 'gzip': // Compress output using gzip $output = gzencode($output, $level); break; case 'deflate': // Compress output using zlib (HTTP deflate) $output = gzdeflate($output, $level); break; } // This header must be sent with compressed content to prevent // browser caches from breaking header('Vary: Accept-Encoding'); // Send the content encoding header header('Content-Encoding: '.$compress); // Sending Content-Length in CGI can result in unexpected behavior if (stripos(PHP_SAPI, 'cgi') === FALSE) { header('Content-Length: '.strlen($output)); } } echo $output; } /** * Displays a 404 page. * * @throws Kohana_404_Exception * @param string URI of page * @param string custom template * @return void */ public static function show_404($page = FALSE, $template = FALSE) { throw new Kohana_404_Exception($page, $template); } /** * Dual-purpose PHP error and exception handler. Uses the kohana_error_page * view to display the message. * * @param integer|object exception object or error code * @param string error message * @param string filename * @param integer line number * @return void */ public static function exception_handler($exception, $message = NULL, $file = NULL, $line = NULL) { try { // PHP errors have 5 args, always $PHP_ERROR = (func_num_args() === 5); // Test to see if errors should be displayed if ($PHP_ERROR AND (error_reporting() & $exception) === 0) return; // This is useful for hooks to determine if a page has an error self::$has_error = TRUE; // Error handling will use exactly 5 args, every time if ($PHP_ERROR) { $code = $exception; $type = 'PHP Error'; $template = 'kohana_error_page'; } else { $code = $exception->getCode(); $type = get_class($exception); $message = $exception->getMessage(); $file = $exception->getFile(); $line = $exception->getLine(); $template = ($exception instanceof Kohana_Exception) ? $exception->getTemplate() : 'kohana_error_page'; } if (is_numeric($code)) { $codes = self::lang('errors'); if ( ! empty($codes[$code])) { list($level, $error, $description) = $codes[$code]; } else { $level = 1; $error = $PHP_ERROR ? 'Unknown Error' : get_class($exception); $description = ''; } } else { // Custom error message, this will never be logged $level = 5; $error = $code; $description = ''; } // Remove the DOCROOT from the path, as a security precaution $file = str_replace('\\', '/', realpath($file)); $file = preg_replace('|^'.preg_quote(DOCROOT).'|', '', $file); if ($level <= self::$configuration['core']['log_threshold']) { // Log the error self::log('error', self::lang('core.uncaught_exception', $type, $message, $file, $line)); } if ($PHP_ERROR) { //$description = self::lang('errors.'.E_RECOVERABLE_ERROR); $description = is_array($description) ? $description[2] : ''; if ( ! headers_sent()) { // Send the 500 header header('HTTP/1.1 500 Internal Server Error'); } } else { if (method_exists($exception, 'sendHeaders') AND ! headers_sent()) { // Send the headers if they have not already been sent $exception->sendHeaders(); } } // Close all output buffers except for Kohana while (ob_get_level() > self::$buffer_level) { ob_end_clean(); } // Test if display_errors is on if (self::$configuration['core']['display_errors'] === TRUE) { if ( ! IN_PRODUCTION AND $line != FALSE) { // Remove the first entry of debug_backtrace(), it is the exception_handler call $trace = $PHP_ERROR ? array_slice(debug_backtrace(), 1) : $exception->getTrace(); // Beautify backtrace $trace = self::backtrace($trace); } // Load the error require self::find_file('views', empty($template) ? 'kohana_error_page' : $template); } else { // Get the i18n messages $error = self::lang('core.generic_error'); $message = self::lang('core.errors_disabled', url::site(), url::site(Router::$current_uri)); // Load the errors_disabled view require self::find_file('views', 'kohana_error_disabled'); } if ( ! Event::has_run('system.shutdown')) { // Run the shutdown even to ensure a clean exit Event::run('system.shutdown'); } // Turn off error reporting error_reporting(0); exit; } catch (Exception $e) { if (IN_PRODUCTION) { die('Fatal Error'); } else { die('Fatal Error: '.$e->getMessage().' File: '.$e->getFile().' Line: '.$e->getLine()); } } } /** * Provides class auto-loading. * * @throws Kohana_Exception * @param string name of class * @return bool */ public static function auto_load($class) { if (class_exists($class, FALSE)) return TRUE; if (($suffix = strrpos($class, '_')) > 0) { // Find the class suffix $suffix = substr($class, $suffix + 1); } else { // No suffix $suffix = FALSE; } if ($suffix === 'Core') { $type = 'libraries'; $file = substr($class, 0, -5); } elseif ($suffix === 'Controller') { $type = 'controllers'; // Lowercase filename $file = strtolower(substr($class, 0, -11)); } elseif ($suffix === 'Model') { $type = 'models'; // Lowercase filename $file = strtolower(substr($class, 0, -6)); } elseif ($suffix === 'Driver') { $type = 'libraries/drivers'; $file = str_replace('_', '/', substr($class, 0, -7)); } else { // This could be either a library or a helper, but libraries must // always be capitalized, so we check if the first character is // uppercase. If it is, we are loading a library, not a helper. $type = ($class[0] < 'a') ? 'libraries' : 'helpers'; $file = $class; } if ($filename = self::find_file($type, $file)) { // Load the class require $filename; } else { // The class could not be found return FALSE; } if ($filename = self::find_file($type, self::$configuration['core']['extension_prefix'].$class)) { // Load the class extension require $filename; } elseif ($suffix !== 'Core' AND class_exists($class.'_Core', FALSE)) { // Class extension to be evaluated $extension = 'class '.$class.' extends '.$class.'_Core { }'; // Start class analysis $core = new ReflectionClass($class.'_Core'); if ($core->isAbstract()) { // Make the extension abstract $extension = 'abstract '.$extension; } // Transparent class extensions are handled using eval. This is // a disgusting hack, but it gets the job done. eval($extension); } return TRUE; } /** * Find a resource file in a given directory. Files will be located according * to the order of the include paths. Configuration and i18n files will be * returned in reverse order. * * @throws Kohana_Exception if file is required and not found * @param string directory to search in * @param string filename to look for (without extension) * @param boolean file required * @param string file extension * @return array if the type is config, i18n or l10n * @return string if the file is found * @return FALSE if the file is not found */ public static function find_file($directory, $filename, $required = FALSE, $ext = FALSE) { // NOTE: This test MUST be not be a strict comparison (===), or empty // extensions will be allowed! if ($ext == '') { // Use the default extension $ext = EXT; } else { // Add a period before the extension $ext = '.'.$ext; } // Search path $search = $directory.'/'.$filename.$ext; if (isset(self::$internal_cache['find_file_paths'][$search])) return self::$internal_cache['find_file_paths'][$search]; // Load include paths $paths = self::$include_paths; // Nothing found, yet $found = NULL; if ($directory === 'config' OR $directory === 'i18n') { // Search in reverse, for merging $paths = array_reverse($paths); foreach ($paths as $path) { if (is_file($path.$search)) { // A matching file has been found $found[] = $path.$search; } } } else { foreach ($paths as $path) { if (is_file($path.$search)) { // A matching file has been found $found = $path.$search; // Stop searching break; } } } if ($found === NULL) { if ($required === TRUE) { // Directory i18n key $directory = 'core.'.inflector::singular($directory); // If the file is required, throw an exception throw new Kohana_Exception('core.resource_not_found', self::lang($directory), $filename); } else { // Nothing was found, return FALSE $found = FALSE; } } if ( ! isset(self::$write_cache['find_file_paths'])) { // Write cache at shutdown self::$write_cache['find_file_paths'] = TRUE; } return self::$internal_cache['find_file_paths'][$search] = $found; } /** * Lists all files and directories in a resource path. * * @param string directory to search * @param boolean list all files to the maximum depth? * @param string full path to search (used for recursion, *never* set this manually) * @return array filenames and directories */ public static function list_files($directory, $recursive = FALSE, $path = FALSE) { $files = array(); if ($path === FALSE) { $paths = array_reverse(self::include_paths()); foreach ($paths as $path) { // Recursively get and merge all files $files = array_merge($files, self::list_files($directory, $recursive, $path.$directory)); } } else { $path = rtrim($path, '/').'/'; if (is_readable($path)) { $items = (array) glob($path.'*'); if ( ! empty($items)) { foreach ($items as $index => $item) { $files[] = $item = str_replace('\\', '/', $item); // Handle recursion if (is_dir($item) AND $recursive == TRUE) { // Filename should only be the basename $item = pathinfo($item, PATHINFO_BASENAME); // Append sub-directory search $files = array_merge($files, self::list_files($directory, TRUE, $path.$item)); } } } } } return $files; } /** * Fetch an i18n language item. * * @param string language key to fetch * @param array additional information to insert into the line * @return string i18n language string, or the requested key if the i18n item is not found */ public static function lang($key, $args = array()) { // Extract the main group from the key $group = explode('.', $key, 2); $group = $group[0]; // Get locale name $locale = self::config('locale.language.0'); if ( ! isset(self::$internal_cache['language'][$locale][$group])) { // Messages for this group $messages = array(); if ($files = self::find_file('i18n', $locale.'/'.$group)) { foreach ($files as $file) { include $file; // Merge in configuration if ( ! empty($lang) AND is_array($lang)) { foreach ($lang as $k => $v) { $messages[$k] = $v; } } } } if ( ! isset(self::$write_cache['language'])) { // Write language cache self::$write_cache['language'] = TRUE; } self::$internal_cache['language'][$locale][$group] = $messages; } // Get the line from cache $line = self::key_string(self::$internal_cache['language'][$locale], $key); if ($line === NULL) { self::log('error', 'Missing i18n entry '.$key.' for language '.$locale); // Return the key string as fallback return $key; } if (is_string($line) AND func_num_args() > 1) { $args = array_slice(func_get_args(), 1); // Add the arguments into the line $line = vsprintf($line, is_array($args[0]) ? $args[0] : $args); } return $line; } /** * Returns the value of a key, defined by a 'dot-noted' string, from an array. * * @param array array to search * @param string dot-noted string: foo.bar.baz * @return string if the key is found * @return void if the key is not found */ public static function key_string($array, $keys) { if (empty($array)) return NULL; // Prepare for loop $keys = explode('.', $keys); do { // Get the next key $key = array_shift($keys); if (isset($array[$key])) { if (is_array($array[$key]) AND ! empty($keys)) { // Dig down to prepare the next loop $array = $array[$key]; } else { // Requested key was found return $array[$key]; } } else { // Requested key is not set break; } } while ( ! empty($keys)); return NULL; } /** * Sets values in an array by using a 'dot-noted' string. * * @param array array to set keys in (reference) * @param string dot-noted string: foo.bar.baz * @return mixed fill value for the key * @return void */ public static function key_string_set( & $array, $keys, $fill = NULL) { if (is_object($array) AND ($array instanceof ArrayObject)) { // Copy the array $array_copy = $array->getArrayCopy(); // Is an object $array_object = TRUE; } else { if ( ! is_array($array)) { // Must always be an array $array = (array) $array; } // Copy is a reference to the array $array_copy =& $array; } if (empty($keys)) return $array; // Create keys $keys = explode('.', $keys); // Create reference to the array $row =& $array_copy; for ($i = 0, $end = count($keys) - 1; $i <= $end; $i++) { // Get the current key $key = $keys[$i]; if ( ! isset($row[$key])) { if (isset($keys[$i + 1])) { // Make the value an array $row[$key] = array(); } else { // Add the fill key $row[$key] = $fill; } } elseif (isset($keys[$i + 1])) { // Make the value an array $row[$key] = (array) $row[$key]; } // Go down a level, creating a new row reference $row =& $row[$key]; } if (isset($array_object)) { // Swap the array back in $array->exchangeArray($array_copy); } } /** * Retrieves current user agent information: * keys: browser, version, platform, mobile, robot, referrer, languages, charsets * tests: is_browser, is_mobile, is_robot, accept_lang, accept_charset * * @param string key or test name * @param string used with "accept" tests: user_agent(accept_lang, en) * @return array languages and charsets * @return string all other keys * @return boolean all tests */ public static function user_agent($key = 'agent', $compare = NULL) { static $info; // Return the raw string if ($key === 'agent') return self::$user_agent; if ($info === NULL) { // Parse the user agent and extract basic information $agents = self::config('user_agents'); foreach ($agents as $type => $data) { foreach ($data as $agent => $name) { if (stripos(self::$user_agent, $agent) !== FALSE) { if ($type === 'browser' AND preg_match('|'.preg_quote($agent).'[^0-9.]*+([0-9.][0-9.a-z]*)|i', self::$user_agent, $match)) { // Set the browser version $info['version'] = $match[1]; } // Set the agent name $info[$type] = $name; break; } } } } if (empty($info[$key])) { switch ($key) { case 'is_robot': case 'is_browser': case 'is_mobile': // A boolean result $return = ! empty($info[substr($key, 3)]); break; case 'languages': $return = array(); if ( ! empty($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { if (preg_match_all('/[-a-z]{2,}/', strtolower(trim($_SERVER['HTTP_ACCEPT_LANGUAGE'])), $matches)) { // Found a result $return = $matches[0]; } } break; case 'charsets': $return = array(); if ( ! empty($_SERVER['HTTP_ACCEPT_CHARSET'])) { if (preg_match_all('/[-a-z0-9]{2,}/', strtolower(trim($_SERVER['HTTP_ACCEPT_CHARSET'])), $matches)) { // Found a result $return = $matches[0]; } } break; case 'referrer': if ( ! empty($_SERVER['HTTP_REFERER'])) { // Found a result $return = trim($_SERVER['HTTP_REFERER']); } break; } // Cache the return value isset($return) and $info[$key] = $return; } if ( ! empty($compare)) { // The comparison must always be lowercase $compare = strtolower($compare); switch ($key) { case 'accept_lang': // Check if the lange is accepted return in_array($compare, self::user_agent('languages')); break; case 'accept_charset': // Check if the charset is accepted return in_array($compare, self::user_agent('charsets')); break; default: // Invalid comparison return FALSE; break; } } // Return the key, if set return isset($info[$key]) ? $info[$key] : NULL; } /** * Quick debugging of any variable. Any number of parameters can be set. * * @return string */ public static function debug() { if (func_num_args() === 0) return; // Get params $params = func_get_args(); $output = array(); foreach ($params as $var) { $output[] = '
('.gettype($var).') '.html::specialchars(print_r($var, TRUE)).'
'; } 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 = '
  • '; if (isset($entry['file'])) { $temp .= self::lang('core.error_file_line', preg_replace('!^'.preg_quote(DOCROOT).'!', '', $entry['file']), $entry['line']); } $temp .= '
    ';
    
    			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 .= ' )
  • '; $output[] = $temp; } return ''; } /** * Saves the internal caches: configuration, include paths, etc. * * @return boolean */ public static function internal_cache_save() { if ( ! is_array(self::$write_cache)) return FALSE; // Get internal cache names $caches = array_keys(self::$write_cache); // Nothing written $written = FALSE; foreach ($caches as $cache) { if (isset(self::$internal_cache[$cache])) { // Write the cache file self::cache_save($cache, self::$internal_cache[$cache], self::$configuration['core']['internal_cache']); // A cache has been written $written = TRUE; } } return $written; } } // End Kohana /** * Creates a generic i18n exception. */ class Kohana_Exception extends Exception { // Template file protected $template = 'kohana_error_page'; // Header protected $header = FALSE; // Error code protected $code = E_KOHANA; /** * Set exception message. * * @param string i18n language key for the message * @param array addition line parameters */ public function __construct($error) { $args = array_slice(func_get_args(), 1); // Fetch the error message $message = Kohana::lang($error, $args); if ($message === $error OR empty($message)) { // Unable to locate the message for the error $message = 'Unknown Exception: '.$error; } // Sets $this->message the proper way parent::__construct($message); } /** * Magic method for converting an object to a string. * * @return string i18n message */ public function __toString() { return (string) $this->message; } /** * Fetch the template name. * * @return string */ public function getTemplate() { return $this->template; } /** * Sends an Internal Server Error header. * * @return void */ public function sendHeaders() { // Send the 500 header header('HTTP/1.1 500 Internal Server Error'); } } // End Kohana Exception /** * Creates a custom exception. */ class Kohana_User_Exception extends Kohana_Exception { /** * Set exception title and message. * * @param string exception title string * @param string exception message string * @param string custom error template */ public function __construct($title, $message, $template = FALSE) { Exception::__construct($message); $this->code = $title; if ($template !== FALSE) { $this->template = $template; } } } // End Kohana PHP Exception /** * Creates a Page Not Found exception. */ class Kohana_404_Exception extends Kohana_Exception { protected $code = E_PAGE_NOT_FOUND; /** * Set internal properties. * * @param string URL of page * @param string custom error template */ public function __construct($page = FALSE, $template = FALSE) { if ($page === FALSE) { // Construct the page URI using Router properties $page = Router::$current_uri.Router::$url_suffix.Router::$query_string; } Exception::__construct(Kohana::lang('core.page_not_found', $page)); $this->template = $template; } /** * Sends "File Not Found" headers, to emulate server behavior. * * @return void */ public function sendHeaders() { // Send the 404 header header('HTTP/1.1 404 File Not Found'); } } // End Kohana 404 Exception