1 <?php
defined('SYSPATH') OR die('No direct access allowed.');
3 require_once('op5/log.php');
6 # Hack to enable PHP version < 5.2
8 if (!defined('E_RECOVERABLE_ERROR'))
9 define('E_RECOVERABLE_ERROR', 4096);
10 if (!defined('FILTER_VALIDATE_IP'))
11 define('FILTER_VALIDATE_IP', false);
12 if (!defined('FILTER_FLAG_IPV4'))
13 define('FILTER_FLAG_IPV4', false);
15 if (!function_exists('spl_object_hash')) {
17 * Returns the hash of the unique identifier for the object.
19 * @param object $object Object
20 * @author Rafael M. Salvioni
23 function spl_object_hash($object)
25 if (!is_object($object))
26 $object = arr
::to_object($object);
27 if (is_object($object)) {
28 ob_start(); var_dump($object); $dump = ob_get_contents(); ob_end_clean();
29 if (preg_match('/^object\(([a-z0-9_]+)\)\#(\d)+/i', $dump, $match)) {
30 return md5($match[1] . $match[2]);
33 trigger_error(__FUNCTION__
. "() expects parameter 1 to be object", E_USER_WARNING
);
38 if (!function_exists('filter_var')) {
39 function filter_var($input) {
44 # End PHP version hack
48 * Provides Kohana-specific helper functions. This is where the magic happens!
50 * $Id: Kohana.php 3917 2009-01-21 03:06:22Z zombor $
54 * @copyright (c) 2007-2008 Kohana Team
55 * @license http://kohanaphp.com/license.html
59 // The singleton instance of the controller
60 public static $instance;
62 // Output buffering level
63 private static $buffer_level;
65 // Will be set to TRUE when an exception is caught
66 public static $has_error = FALSE;
68 // The final output that will displayed by Kohana
69 public static $output = '';
71 // The current user agent
72 public static $user_agent;
75 public static $locale;
78 private static $configuration;
81 private static $include_paths;
87 private static $cache_lifetime;
89 // Internal caches and write status
90 private static $internal_cache = array();
91 private static $write_cache;
94 * Sets up the PHP environment. Adds error/exception handling, output
95 * buffering, and adds an auto-loading method for loading classes.
97 * This method is run immediately when this file is loaded, and is
98 * benchmarked as environment_setup.
100 * For security, this function also destroys the $_REQUEST global variable.
101 * Using the proper global (GET, POST, COOKIE, etc) is inherently more secure.
102 * The recommended way to fetch a global variable is using the Input library.
103 * @see http://www.php.net/globals
107 public static function setup()
111 // This function can only be run once
115 // Start the environment setup benchmark
116 Benchmark
::start(SYSTEM_BENCHMARK
.'_environment_setup');
118 // Define Kohana error constant
119 define('E_KOHANA', 42);
121 // Define 404 error constant
122 define('E_PAGE_NOT_FOUND', 43);
124 // Define database error constant
125 define('E_DATABASE_ERROR', 44);
127 // Add APPPATH as the first path
128 self
::$include_paths = array(APPPATH
);
129 foreach (glob(MODPATH
.'*', GLOB_ONLYDIR
) as $path)
131 self
::$include_paths[] = $path.'/';
133 // Add SYSPATH as the last path
134 self
::$include_paths[] = SYSPATH
;
136 if (self
::$cache_lifetime = self
::config('core.internal_cache'))
138 // Load cached configuration and language files
139 self
::$internal_cache['configuration'] = self
::cache('configuration', self
::$cache_lifetime);
140 self
::$internal_cache['language'] = self
::cache('language', self
::$cache_lifetime);
142 // Load cached file paths
143 self
::$internal_cache['find_file_paths'] = self
::cache('find_file_paths', self
::$cache_lifetime);
145 // Enable cache saving
146 Event
::add('system.shutdown', array(__CLASS__
, 'internal_cache_save'));
149 // Disable notices and "strict" errors
150 $ER = error_reporting(~E_NOTICE
& ~E_STRICT
);
152 // Set the user agent
153 self
::$user_agent = trim($_SERVER['HTTP_USER_AGENT']);
155 if (function_exists('date_default_timezone_set'))
157 $timezone = self
::config('locale.timezone');
159 // Set default timezone, due to increased validation of date settings
160 // which cause massive amounts of E_NOTICEs to be generated in PHP 5.2+
161 date_default_timezone_set(empty($timezone) ?
date_default_timezone_get() : $timezone);
164 // Restore error reporting
165 error_reporting($ER);
167 // Start output buffering
168 ob_start(array(__CLASS__
, 'output_buffer'));
170 // Save buffering level
171 self
::$buffer_level = ob_get_level();
174 spl_autoload_register(array('Kohana', 'auto_load'));
177 if (PHP_SAPI
!== 'cli' && !defined('SKIP_KOHANA')) {
178 set_error_handler(array('Kohana', 'exception_handler'));
180 // Set exception handler
181 set_exception_handler(array('Kohana', 'exception_handler'));
183 // Send default text/html UTF-8 header
184 header('Content-Type: text/html; charset=UTF-8');
188 $locales = self
::config('locale.language');
190 // Make first locale UTF-8
191 $locales[0] .= '.UTF-8';
193 // Set locale information
194 self
::$locale = setlocale(LC_ALL
, $locales);
196 // Enable Kohana routing
197 Event
::add('system.routing', array('Router', 'find_uri'));
198 Event
::add('system.routing', array('Router', 'setup'));
200 // Enable Kohana controller initialization
201 Event
::add('system.execute', array('Kohana', 'instance'));
203 // Enable Kohana 404 pages
204 Event
::add('system.404', array('Kohana', 'show_404'));
206 // Enable Kohana output handling
207 Event
::add('system.shutdown', array('Kohana', 'shutdown'));
209 if (self
::config('core.enable_hooks') === TRUE)
211 // Find all the hook files
212 $hooks = self
::list_files('hooks', TRUE);
214 foreach ($hooks as $file)
221 // Setup is complete, prevent it from being run again
224 // Stop the environment setup routine
225 Benchmark
::stop(SYSTEM_BENCHMARK
.'_environment_setup');
229 * Loads the controller and initializes it. Runs the pre_controller,
230 * post_controller_constructor, and post_controller events. Triggers
231 * a system.404 event when the route cannot be mapped to a controller.
233 * This method is benchmarked as controller_setup and controller_execution.
235 * @return object instance of controller
237 public static function & instance()
239 if (self
::$instance === NULL)
241 Benchmark
::start(SYSTEM_BENCHMARK
.'_controller_setup');
243 if (Router
::$method[0] === '_')
245 // Do not allow access to hidden methods
246 Event
::run('system.404');
249 // Include the Controller file
250 require Router
::$controller_path;
254 // Start validation of the controller
255 $class = new ReflectionClass(ucfirst(Router
::$controller).'_Controller');
257 catch (ReflectionException
$e)
259 // Controller does not exist
260 Event
::run('system.404');
263 if ($class->isAbstract() OR (IN_PRODUCTION
AND $class->getConstant('ALLOW_PRODUCTION') == FALSE))
265 // Controller is not allowed to run in production
266 Event
::run('system.404');
269 // Run system.pre_controller
270 Event
::run('system.pre_controller');
272 // Create a new controller instance
273 $controller = $class->newInstance();
275 // Controller constructor has been executed
276 Event
::run('system.post_controller_constructor', $controller);
280 // Load the controller method
281 $method = $class->getMethod(Router
::$method);
283 if ($method->isProtected() or $method->isPrivate())
285 // Do not attempt to invoke protected methods
286 throw new ReflectionException('protected controller method');
290 $arguments = Router
::$arguments;
292 catch (ReflectionException
$e)
294 // Use __call instead
295 $method = $class->getMethod('__call');
297 // Use arguments in __call format
298 $arguments = array(Router
::$method, Router
::$arguments);
301 // Stop the controller setup benchmark
302 Benchmark
::stop(SYSTEM_BENCHMARK
.'_controller_setup');
304 // Start the controller execution benchmark
305 Benchmark
::start(SYSTEM_BENCHMARK
.'_controller_execution');
307 // Execute the controller method
308 $method->invokeArgs($controller, $arguments);
310 // Controller method has been executed
311 Event
::run('system.post_controller', $controller);
313 // Stop the controller execution benchmark
314 Benchmark
::stop(SYSTEM_BENCHMARK
.'_controller_execution');
317 return self
::$instance;
321 * Get all include paths.
322 * APPPATH is the first path, followed by module
323 * paths in the order they are configured, follow by the SYSPATH.
326 * boolean re-process the include paths, we don't do that...
330 public static function include_paths($process = FALSE) {
331 return self
::$include_paths;
335 * Remove include paths given a certain pattern.
336 * Useful for replacing modules
339 public static function remove_include_paths($pattern) {
340 self
::$include_paths = array_filter(self
::$include_paths,
341 function ($path) use($pattern) {
342 return !preg_match($pattern, $path);
347 * Add include path, useful for unit testing of external libraries
349 public static function add_include_path($path) {
350 self
::$include_paths[] = $path;
354 * Get a config item or group.
356 * @param string item name
357 * @param boolean force a forward slash (/) at the end of the item
358 * @param boolean is the item required?
361 public static function config($key, $slash = FALSE, $required = TRUE)
363 if (self
::$configuration === NULL)
365 // Load core configuration
366 self
::$configuration['core'] = self
::config_load('core');
368 // Re-parse the include paths
369 self
::include_paths(TRUE);
372 // Get the group name from the key
373 $group = explode('.', $key, 2);
376 if ( ! isset(self
::$configuration[$group]))
378 // Load the configuration group
379 self
::$configuration[$group] = self
::config_load($group, $required);
382 // Get the value of the key string
383 $value = self
::key_string(self
::$configuration, $key);
385 if ($slash === TRUE AND is_string($value) AND $value !== '')
387 // Force the value to end with "/"
388 $value = rtrim($value, '/').'/';
395 * Sets a configuration item, if allowed.
397 * @param string config key string
398 * @param string config value
401 public static function config_set($key, $value)
403 // Do this to make sure that the config array is already loaded
406 if (substr($key, 0, 7) === 'routes.')
408 // Routes cannot contain sub keys due to possible dots in regex
409 $keys = explode('.', $key, 2);
413 // Convert dot-noted key string to an array
414 $keys = explode('.', $key);
417 // Used for recursion
418 $conf =& self
::$configuration;
419 $last = count($keys) - 1;
421 foreach ($keys as $i => $k)
433 if ($key === 'core.modules')
435 // Reprocess the include paths
436 self
::include_paths(TRUE);
443 * Load a config file.
445 * @param string config filename, without extension
446 * @param boolean is the file required?
449 public static function config_load($name, $required = TRUE)
451 if ($name === 'core')
453 // Load the application configuration file
454 require APPPATH
.'config/config'.EXT
;
455 if (is_file(APPPATH
.'config/custom/config'.EXT
))
456 include APPPATH
.'config/custom/config'.EXT
;
458 if ( ! isset($config['site_domain']))
460 // Invalid config file
461 die('Your Kohana application configuration file is not valid.');
467 if (isset(self
::$internal_cache['configuration'][$name]))
468 return self
::$internal_cache['configuration'][$name];
470 // Load matching configs
471 $configuration = array();
473 if ($files = self
::find_file('config', $name, $required))
476 foreach ($files as $file)
480 if (isset($config) AND is_array($config))
482 // Merge in configuration
483 $configuration = array_merge($configuration, $config);
488 if ($files = self
::find_file('config/custom', $name, false))
490 foreach ($files as $file)
493 if (isset($config) and is_array($config))
495 $configuration = array_merge($configuration, $config);
500 if ( ! isset(self
::$write_cache['configuration']))
503 self
::$write_cache['configuration'] = TRUE;
506 return self
::$internal_cache['configuration'][$name] = $configuration;
510 * Clears a config group from the cached configuration.
512 * @param string config group
515 public static function config_clear($group)
517 // Remove the group from config
518 unset(self
::$configuration[$group], self
::$internal_cache['configuration'][$group]);
520 if ( ! isset(self
::$write_cache['configuration']))
523 self
::$write_cache['configuration'] = TRUE;
528 * Add a new message to the log.
530 * @param string type of message
531 * @param string message text
534 public static function log($type, $message)
536 op5log
::instance()->log('ninja', $type, $message);
540 * Load data from a simple cache file. This should only be used internally,
541 * and is NOT a replacement for the Cache library.
543 * @param string unique name of cache
544 * @param integer expiration in seconds
547 public static function cache($name, $lifetime)
551 $path = APPPATH
.'cache/kohana_'.$name;
555 // Check the file modification time
556 if ((time() - filemtime($path)) < $lifetime)
559 return unserialize(file_get_contents($path));
563 // Cache is invalid, delete it
574 * Save data to a simple cache file. This should only be used internally, and
575 * is NOT a replacement for the Cache library.
577 * @param string cache name
578 * @param mixed data to cache
579 * @param integer expiration in seconds
582 public static function cache_save($name, $data, $lifetime)
587 $path = APPPATH
.'cache/kohana_'.$name;
592 return (is_file($path) and unlink($path));
596 // Write data to cache file
597 return (bool) file_put_contents($path, serialize($data));
602 * Kohana output handler.
604 * @param string current output buffer
607 public static function output_buffer($output)
609 if ( ! Event
::has_run('system.send_headers'))
611 // Run the send_headers event, specifically for cookies being set
612 Event
::run('system.send_headers');
616 self
::$output = $output;
618 // Set and return the final output
623 * Closes all open output buffers, either by flushing or cleaning all
624 * open buffers, including the Kohana output buffer.
626 * @param boolean disable to clear buffers, rather than flushing
629 public static function close_buffers($flush = TRUE)
631 if (ob_get_level() >= self
::$buffer_level)
633 // Set the close function
634 $close = ($flush === TRUE) ?
'ob_end_flush' : 'ob_end_clean';
636 while (ob_get_level() > self
::$buffer_level)
638 // Flush or clean the buffer
642 // This will flush the Kohana buffer, which sets self::$output
645 // Reset the buffer level
646 self
::$buffer_level = ob_get_level();
651 * Triggers the shutdown of Kohana by closing the output buffer, runs the system.display event.
655 public static function shutdown()
657 // Close output buffers
658 self
::close_buffers(TRUE);
660 // Run the output event
661 Event
::run('system.display', self
::$output);
663 // Render the final output
664 self
::render(self
::$output);
668 * Inserts global Kohana variables into the generated output and prints it.
670 * @param string final output that will displayed
673 public static function render($output)
675 // Fetch memory usage in MB
676 $memory = function_exists('memory_get_usage') ?
(memory_get_usage() / 1024 / 1024) : 0;
678 // Fetch benchmark for page execution time
679 $benchmark = Benchmark
::get(SYSTEM_BENCHMARK
.'_total_execution');
681 if (self
::config('core.render_stats') === TRUE)
683 // Replace the global template variables
684 $output = str_replace(
698 number_format($memory, 2).'MB',
699 count(get_included_files()),
705 if ($level = self
::config('core.output_compression') AND ini_get('output_handler') !== 'ob_gzhandler' AND (int) ini_get('zlib.output_compression') === 0)
707 if ($level < 1 OR $level > 9)
709 // Normalize the level to be an integer between 1 and 9. This
710 // step must be done to prevent gzencode from triggering an error
711 $level = max(1, min($level, 9));
714 if (stripos(@$_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE)
718 elseif (stripos(@$_SERVER['HTTP_ACCEPT_ENCODING'], 'deflate') !== FALSE)
720 $compress = 'deflate';
724 if (isset($compress) AND $level > 0)
729 // Compress output using gzip
730 $output = gzencode($output, $level);
733 // Compress output using zlib (HTTP deflate)
734 $output = gzdeflate($output, $level);
738 // This header must be sent with compressed content to prevent
739 // browser caches from breaking
740 header('Vary: Accept-Encoding');
742 // Send the content encoding header
743 header('Content-Encoding: '.$compress);
745 // Sending Content-Length in CGI can result in unexpected behavior
746 if (stripos(PHP_SAPI
, 'cgi') === FALSE)
748 header('Content-Length: '.strlen($output));
756 * Displays a 404 page.
758 * @throws Kohana_404_Exception
759 * @param string URI of page
760 * @param string custom template
763 public static function show_404($page = FALSE, $template = FALSE)
765 throw new Kohana_404_Exception($page, $template);
769 * Dual-purpose PHP error and exception handler. Uses the kohana_error_page
770 * view to display the message.
772 * @param integer|object exception object or error code
773 * @param string error message
774 * @param string filename
775 * @param integer line number
778 public static function exception_handler($exception, $message = NULL, $file = NULL, $line = NULL)
781 // PHP errors have 5 args, always
782 $PHP_ERROR = (func_num_args() === 5);
784 // Test to see if errors should be displayed
785 if ($PHP_ERROR AND (error_reporting() & $exception) === 0)
788 // This is useful for hooks to determine if a page has an error
789 self
::$has_error = TRUE;
791 // Error handling will use exactly 5 args, every time
796 $template = 'kohana_error_page';
800 $code = $exception->getCode();
801 $type = get_class($exception);
802 $message = $exception->getMessage();
803 $file = $exception->getFile();
804 $line = $exception->getLine();
805 $template = ($exception instanceof Kohana_Exception
) ?
$exception->getTemplate() : 'kohana_error_page';
808 if (is_numeric($code))
810 $codes = self
::lang('errors');
812 if ( ! empty($codes[$code]))
814 list($level, $error, $description) = $codes[$code];
819 $error = $PHP_ERROR ?
'Unknown Error' : get_class($exception);
825 // Custom error message, this will never be logged
831 // Remove the DOCROOT from the path, as a security precaution
832 $file = str_replace('\\', '/', realpath($file));
833 $file = preg_replace('|^'.preg_quote(DOCROOT
).'|', '', $file);
835 self
::log('error', self
::lang('core.uncaught_exception', $type, $message, $file, $line));
839 $description = self
::lang('errors.'.E_RECOVERABLE_ERROR
);
840 $description = is_array($description) ?
$description[2] : '';
842 if ( ! headers_sent())
844 header('HTTP/1.1 500 Internal Server Error');
849 if(!headers_sent()) {
850 if (method_exists($exception, 'sendHeaders'))
852 $exception->sendHeaders();
854 header('HTTP/1.1 500 Internal Server Error');
859 while (ob_get_level() > self
::$buffer_level)
861 // Close open buffers
865 // Test if display_errors is on
866 if (self
::$configuration['core']['display_errors'] === TRUE)
870 // Remove the first entry of debug_backtrace(), it is the exception_handler call
871 $trace = $PHP_ERROR ?
array_slice(debug_backtrace(), 1) : $exception->getTrace();
873 // Beautify backtrace
874 $trace = self
::backtrace($trace);
878 require self
::find_file('views', empty($template) ?
'kohana_error_page' : $template);
882 // Get the i18n messages
883 $error = self
::lang('core.generic_error');
884 $message = self
::lang('core.errors_disabled', url
::site(), url
::site(Router
::$current_uri));
886 // Load the errors_disabled view
887 require self
::find_file('views', 'kohana_error_disabled');
890 if ( ! Event
::has_run('system.shutdown'))
892 // Run the shutdown even to ensure a clean exit
893 Event
::run('system.shutdown');
896 // Turn off error reporting
898 } catch( Exception
$e ) {
899 /* Exceptions in an exceptionhandler results in "Exception thrown without a stack trace in "Unkonwn"
900 * Better to just print the exception ugly, so we get some kind of useful information instaead
902 while( @ob_end_clean
() ) {}
903 print "Exception during error handler: ".$e->getMessage()."\n";
904 print $e->getTraceAsString();
911 * Provides class auto-loading.
913 * @throws Kohana_Exception
914 * @param string name of class
917 public static function auto_load($class)
919 if (class_exists($class, FALSE))
922 if (($suffix = strrpos($class, '_')) > 0)
924 // Find the class suffix
925 $suffix = substr($class, $suffix +
1);
933 if ($suffix === 'Core')
936 $file = substr($class, 0, -5);
938 elseif ($suffix === 'Controller')
940 $type = 'controllers';
941 // Lowercase filename
942 $file = strtolower(substr($class, 0, -11));
944 elseif ($suffix === 'Model')
947 // Lowercase filename
948 $file = strtolower(substr($class, 0, -6));
950 elseif ($suffix === 'Driver')
952 $type = 'libraries/drivers';
953 $file = str_replace('_', '/', substr($class, 0, -7));
955 elseif ($suffix === 'Widget')
958 $classname = substr($class, 0, -7);
959 $file = $classname . '/' . $classname;
963 // This could be either a library or a helper, but libraries must
964 // always be capitalized, so we check if the first character is
965 // uppercase. If it is, we are loading a library, not a helper.
966 $type = ($class[0] < 'a') ?
'libraries' : 'helpers';
970 if ($filename = self
::find_file($type, $file))
977 // The class could not be found
981 if ($filename = self
::find_file($type, self
::$configuration['core']['extension_prefix'].$class))
983 // Load the class extension
986 elseif ($suffix !== 'Core' AND class_exists($class.'_Core', FALSE))
988 // Class extension to be evaluated
989 $extension = 'class '.$class.' extends '.$class.'_Core { }';
991 // Start class analysis
992 $core = new ReflectionClass($class.'_Core');
994 if ($core->isAbstract())
996 // Make the extension abstract
997 $extension = 'abstract '.$extension;
1000 // Transparent class extensions are handled using eval. This is
1001 // a disgusting hack, but it gets the job done.
1009 * Find a resource file in a given directory. Files will be located according
1010 * to the order of the include paths. Configuration and i18n files will be
1011 * returned in reverse order.
1013 * @throws Kohana_Exception if file is required and not found
1014 * @param string directory to search in
1015 * @param string filename to look for (including extension only if 4th parameter is TRUE)
1016 * @param boolean file required
1017 * @param string file extension
1018 * @return array if the type is config, i18n or l10n
1019 * @return string if the file is found
1020 * @return FALSE if the file is not found
1022 public static function find_file($directory, $filename, $required = FALSE, $ext = FALSE)
1024 // NOTE: This test MUST be not be a strict comparison (===), or empty
1025 // extensions will be allowed!
1028 // Use the default extension
1033 // Add a period before the extension
1038 $search = $directory.'/'.$filename.$ext;
1040 if (isset(self
::$internal_cache['find_file_paths'][$search]))
1041 return self
::$internal_cache['find_file_paths'][$search];
1043 // Load include paths
1044 $paths = self
::$include_paths;
1046 // Nothing found, yet
1049 if ($directory === 'config' OR $directory === 'i18n' OR $directory === 'config/custom')
1051 // Search in reverse, for merging
1052 $paths = array_reverse($paths);
1054 foreach ($paths as $path)
1056 if (is_file($path.$search))
1058 // A matching file has been found
1059 $found[] = $path.$search;
1065 foreach ($paths as $path)
1067 if (is_file($path.$search))
1069 // A matching file has been found
1070 $found = $path.$search;
1078 if ($found === NULL)
1080 if ($required === TRUE)
1082 // Directory i18n key
1083 $directory = 'core.'.inflector
::singular($directory);
1085 // If the file is required, throw an exception
1086 throw new Kohana_Exception('core.resource_not_found', self
::lang($directory), $filename);
1090 // Nothing was found, return FALSE
1095 if ( ! isset(self
::$write_cache['find_file_paths']))
1097 // Write cache at shutdown
1098 self
::$write_cache['find_file_paths'] = TRUE;
1101 return self
::$internal_cache['find_file_paths'][$search] = $found;
1105 * Lists all files and directories in a resource path.
1107 * @param string directory to search
1108 * @param boolean list all files to the maximum depth?
1109 * @param string full path to search (used for recursion, *never* set this manually)
1110 * @return array filenames and directories
1112 public static function list_files($directory, $recursive = FALSE, $path = FALSE)
1116 if ($path === FALSE)
1118 $paths = array_reverse(self
::include_paths());
1120 foreach ($paths as $path)
1122 // Recursively get and merge all files
1123 $files = array_merge($files, self
::list_files($directory, $recursive, $path.$directory));
1128 $path = rtrim($path, '/').'/';
1130 if (is_readable($path))
1132 $items = (array) glob($path.'*');
1134 foreach ($items as $index => $item)
1136 $files[] = $item = str_replace('\\', '/', $item);
1139 if (is_dir($item) AND $recursive == TRUE)
1141 // Filename should only be the basename
1142 $item = pathinfo($item, PATHINFO_BASENAME
);
1144 // Append sub-directory search
1145 $files = array_merge($files, self
::list_files($directory, TRUE, $path.$item));
1155 * Fetch an i18n language item.
1157 * @param string language key to fetch
1158 * @param array additional information to insert into the line
1159 * @return string i18n language string, or the requested key if the i18n item is not found
1161 public static function lang($key, $args = array())
1163 // Extract the main group from the key
1164 $group = explode('.', $key, 2);
1168 $locale = self
::config('locale.language.0');
1170 if ( ! isset(self
::$internal_cache['language'][$locale][$group]))
1172 // Messages for this group
1173 $messages = array();
1175 if ($files = self
::find_file('i18n', $locale.'/'.$group))
1177 foreach ($files as $file)
1181 // Merge in configuration
1182 if ( ! empty($lang) AND is_array($lang))
1184 foreach ($lang as $k => $v)
1192 if ( ! isset(self
::$write_cache['language']))
1194 // Write language cache
1195 self
::$write_cache['language'] = TRUE;
1198 self
::$internal_cache['language'][$locale][$group] = $messages;
1201 // Get the line from cache
1202 $line = self
::key_string(self
::$internal_cache['language'][$locale], $key);
1206 self
::log('error', 'Missing i18n entry '.$key.' for language '.$locale);
1208 // Return the key string as fallback
1212 if (is_string($line) AND func_num_args() > 1)
1214 $args = array_slice(func_get_args(), 1);
1216 // Add the arguments into the line
1217 $line = vsprintf($line, is_array($args[0]) ?
$args[0] : $args);
1224 * Returns the value of a key, defined by a 'dot-noted' string, from an array.
1226 * @param array array to search
1227 * @param string dot-noted string: foo.bar.baz
1228 * @return string if the key is found
1229 * @return void if the key is not found
1231 public static function key_string($array, $keys)
1237 $keys = explode('.', $keys);
1242 $key = array_shift($keys);
1244 if (isset($array[$key]))
1246 if (is_array($array[$key]) AND ! empty($keys))
1248 // Dig down to prepare the next loop
1249 $array = $array[$key];
1253 // Requested key was found
1254 return $array[$key];
1259 // Requested key is not set
1263 while ( ! empty($keys));
1269 * Sets values in an array by using a 'dot-noted' string.
1271 * @param array array to set keys in (reference)
1272 * @param string dot-noted string: foo.bar.baz
1273 * @return mixed fill value for the key
1276 public static function key_string_set( & $array, $keys, $fill = NULL)
1278 if (is_object($array) AND ($array instanceof ArrayObject
))
1281 $array_copy = $array->getArrayCopy();
1284 $array_object = TRUE;
1288 if ( ! is_array($array))
1290 // Must always be an array
1291 $array = (array) $array;
1294 // Copy is a reference to the array
1295 $array_copy =& $array;
1302 $keys = explode('.', $keys);
1304 // Create reference to the array
1305 $row =& $array_copy;
1307 for ($i = 0, $end = count($keys) - 1; $i <= $end; $i++
)
1309 // Get the current key
1312 if ( ! isset($row[$key]))
1314 if (isset($keys[$i +
1]))
1316 // Make the value an array
1317 $row[$key] = array();
1325 elseif (isset($keys[$i +
1]))
1327 // Make the value an array
1328 $row[$key] = (array) $row[$key];
1331 // Go down a level, creating a new row reference
1335 if (isset($array_object))
1337 // Swap the array back in
1338 $array->exchangeArray($array_copy);
1343 * Retrieves current user agent information:
1344 * keys: browser, version, platform, mobile, robot, referrer, languages, charsets
1345 * tests: is_browser, is_mobile, is_robot, accept_lang, accept_charset
1347 * @param string key or test name
1348 * @param string used with "accept" tests: user_agent(accept_lang, en)
1349 * @return array languages and charsets
1350 * @return string all other keys
1351 * @return boolean all tests
1353 public static function user_agent($key = 'agent', $compare = NULL)
1357 // Return the raw string
1358 if ($key === 'agent')
1359 return self
::$user_agent;
1363 // Parse the user agent and extract basic information
1364 $agents = self
::config('user_agents');
1366 foreach ($agents as $type => $data)
1368 foreach ($data as $agent => $name)
1370 if (stripos(self
::$user_agent, $agent) !== FALSE)
1372 if ($type === 'browser' AND preg_match('|'.preg_quote($agent).'[^0-9.]*+([0-9.][0-9.a-z]*)|i', self
::$user_agent, $match))
1374 // Set the browser version
1375 $info['version'] = $match[1];
1378 // Set the agent name
1379 $info[$type] = $name;
1386 if (empty($info[$key]))
1394 $return = ! empty($info[substr($key, 3)]);
1398 if ( ! empty($_SERVER['HTTP_ACCEPT_LANGUAGE']))
1400 if (preg_match_all('/[-a-z]{2,}/', strtolower(trim($_SERVER['HTTP_ACCEPT_LANGUAGE'])), $matches))
1403 $return = $matches[0];
1409 if ( ! empty($_SERVER['HTTP_ACCEPT_CHARSET']))
1411 if (preg_match_all('/[-a-z0-9]{2,}/', strtolower(trim($_SERVER['HTTP_ACCEPT_CHARSET'])), $matches))
1414 $return = $matches[0];
1419 if ( ! empty($_SERVER['HTTP_REFERER']))
1422 $return = trim($_SERVER['HTTP_REFERER']);
1427 // Cache the return value
1428 isset($return) and $info[$key] = $return;
1431 if ( ! empty($compare))
1433 // The comparison must always be lowercase
1434 $compare = strtolower($compare);
1439 // Check if the lange is accepted
1440 return in_array($compare, self
::user_agent('languages'));
1442 case 'accept_charset':
1443 // Check if the charset is accepted
1444 return in_array($compare, self
::user_agent('charsets'));
1447 // Invalid comparison
1453 // Return the key, if set
1454 return isset($info[$key]) ?
$info[$key] : NULL;
1458 * Quick debugging of any variable. Any number of parameters can be set.
1462 public static function debug()
1464 if (func_num_args() === 0)
1468 $params = func_get_args();
1471 foreach ($params as $var)
1473 $output[] = '<pre>('.gettype($var).') '.html
::specialchars(print_r($var, TRUE)).'</pre>';
1476 return implode("\n", $output);
1480 * Displays nice backtrace information.
1481 * @see http://php.net/debug_backtrace
1483 * @param array backtrace generated by an exception or debug_backtrace
1486 public static function backtrace($trace)
1488 $arg_badword = array(
1489 'passw', /* password and passwd */
1495 if ( ! is_array($trace))
1501 foreach ($trace as $entry)
1505 if (isset($entry['file']))
1507 $temp .= self
::lang('core.error_file_line', preg_replace('!^'.preg_quote(DOCROOT
).'!', '', $entry['file']), $entry['line']);
1513 if (isset($entry['class']))
1515 // Add class and call type
1516 $temp .= $entry['class'].$entry['type'];
1518 $reflclass = new ReflectionClass($entry['class']);
1519 } catch( Exception
$e ) {
1520 // Don't care about the problem... just don't expand variable names in that case
1526 $temp .= $entry['function'].'( ';
1528 $reflmethod = false;
1531 $reflmethod = $reflclass->getMethod($entry['function']);
1533 $reflmethod = new ReflectionFunction($entry['function']);
1534 } catch( Exception
$e ) {
1535 // Don't care about the problem... just don't expand variable names in that case
1536 $reflmethod = false;
1538 // Add function args
1539 if (isset($entry['args']) AND is_array($entry['args']))
1541 // Separator starts as nothing
1547 $reflargs = $reflmethod->getParameters();
1548 } catch( Exception
$e ) {
1549 // Don't care about the problem... just don't expand variable names in that case
1553 while ($arg = array_shift($entry['args']))
1557 if( !empty($reflargs) )
1558 $argname = array_shift( $reflargs )->getName();
1559 } catch( Exception
$e ) {
1560 // Don't care about the problem... just don't expand variable names in that case
1564 if (is_string($arg) AND substr($arg, 0, 4) !== "unix" AND is_file($arg))
1566 // Remove docroot from filename
1567 $arg = preg_replace('!^'.preg_quote(DOCROOT
).'!', '', $arg);
1570 foreach($arg_badword as $badword) {
1571 if( stripos($argname, $badword) !== false )
1575 $temp .= $sep.$argname.' = '.html
::specialchars(print_r($arg, TRUE));
1577 // Change separator to a comma
1582 $temp .= ' )</pre></li>';
1587 return '<ul class="backtrace">'.implode("\n", $output).'</ul>';
1591 * Saves the internal caches: configuration, include paths, etc.
1595 public static function internal_cache_save()
1597 if ( ! is_array(self
::$write_cache))
1600 // Get internal cache names
1601 $caches = array_keys(self
::$write_cache);
1606 foreach ($caches as $cache)
1608 if (isset(self
::$internal_cache[$cache]))
1610 // Write the cache file
1611 self
::cache_save($cache, self
::$internal_cache[$cache], self
::$configuration['core']['internal_cache']);
1613 // A cache has been written
1624 * Creates a generic i18n exception.
1626 class Kohana_Exception
extends Exception
{
1629 protected $template = 'kohana_error_page';
1632 protected $header = FALSE;
1635 protected $code = E_KOHANA
;
1638 * Set exception message.
1640 * @param string i18n language key for the message
1641 * @param array addition line parameters
1643 public function __construct($error)
1645 $args = array_slice(func_get_args(), 1);
1647 // Fetch the error message
1648 $message = Kohana
::lang($error, $args);
1650 if ($message === $error OR empty($message))
1652 // Unable to locate the message for the error
1653 $message = 'Unknown Exception: '.$error;
1656 // Sets $this->message the proper way
1657 parent
::__construct($message);
1661 * Magic method for converting an object to a string.
1663 * @return string i18n message
1665 public function __toString()
1667 return (string) $this->message
;
1671 * Fetch the template name.
1675 public function getTemplate()
1677 return $this->template
;
1681 * Sends an Internal Server Error header.
1685 public function sendHeaders()
1687 // Send the 500 header
1688 header('HTTP/1.1 500 Internal Server Error');
1691 } // End Kohana Exception
1694 * Creates a custom exception.
1696 class Kohana_User_Exception
extends Kohana_Exception
{
1699 * Set exception title and message.
1701 * @param string exception title string
1702 * @param string exception message string
1703 * @param string custom error template
1705 public function __construct($title, $message, $template = FALSE)
1707 Exception
::__construct($message);
1709 $this->code
= $title;
1711 if ($template !== FALSE)
1713 $this->template
= $template;
1717 } // End Kohana PHP Exception
1720 * Creates a Page Not Found exception.
1722 class Kohana_404_Exception
extends Kohana_Exception
{
1724 protected $code = E_PAGE_NOT_FOUND
;
1727 * Set internal properties.
1729 * @param string URL of page
1730 * @param string custom error template
1732 public function __construct($page = FALSE, $template = FALSE)
1734 if ($page === FALSE)
1736 // Construct the page URI using Router properties
1737 $page = Router
::$current_uri.Router
::$url_suffix.Router
::$query_string;
1740 Exception
::__construct(Kohana
::lang('core.page_not_found', $page));
1742 $this->template
= $template;
1746 * Sends "File Not Found" headers, to emulate server behavior.
1750 public function sendHeaders()
1752 // Send the 404 header
1753 header('HTTP/1.1 404 File Not Found');
1756 } // End Kohana 404 Exception