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 if (self
::$cache_lifetime = self
::config('core.internal_cache'))
129 // Load cached configuration and language files
130 self
::$internal_cache['configuration'] = self
::cache('configuration', self
::$cache_lifetime);
131 self
::$internal_cache['language'] = self
::cache('language', self
::$cache_lifetime);
133 // Load cached file paths
134 self
::$internal_cache['find_file_paths'] = self
::cache('find_file_paths', self
::$cache_lifetime);
136 // Enable cache saving
137 Event
::add('system.shutdown', array(__CLASS__
, 'internal_cache_save'));
140 // Disable notices and "strict" errors
141 $ER = error_reporting(~E_NOTICE
& ~E_STRICT
);
143 // Set the user agent
144 self
::$user_agent = trim($_SERVER['HTTP_USER_AGENT']);
146 if (function_exists('date_default_timezone_set'))
148 $timezone = self
::config('locale.timezone');
150 // Set default timezone, due to increased validation of date settings
151 // which cause massive amounts of E_NOTICEs to be generated in PHP 5.2+
152 date_default_timezone_set(empty($timezone) ?
date_default_timezone_get() : $timezone);
155 // Restore error reporting
156 error_reporting($ER);
158 // Start output buffering
159 ob_start(array(__CLASS__
, 'output_buffer'));
161 // Save buffering level
162 self
::$buffer_level = ob_get_level();
165 spl_autoload_register(array('Kohana', 'auto_load'));
168 if (PHP_SAPI
!== 'cli') {
169 set_error_handler(array('Kohana', 'exception_handler'));
171 // Set exception handler
172 set_exception_handler(array('Kohana', 'exception_handler'));
174 // Send default text/html UTF-8 header
175 header('Content-Type: text/html; charset=UTF-8');
179 $locales = self
::config('locale.language');
181 // Make first locale UTF-8
182 $locales[0] .= '.UTF-8';
184 // Set locale information
185 self
::$locale = setlocale(LC_ALL
, $locales);
187 // Enable Kohana routing
188 Event
::add('system.routing', array('Router', 'find_uri'));
189 Event
::add('system.routing', array('Router', 'setup'));
191 // Enable Kohana controller initialization
192 Event
::add('system.execute', array('Kohana', 'instance'));
194 // Enable Kohana 404 pages
195 Event
::add('system.404', array('Kohana', 'show_404'));
197 // Enable Kohana output handling
198 Event
::add('system.shutdown', array('Kohana', 'shutdown'));
200 if (self
::config('core.enable_hooks') === TRUE)
202 // Find all the hook files
203 $hooks = self
::list_files('hooks', TRUE);
205 foreach ($hooks as $file)
212 // Setup is complete, prevent it from being run again
215 // Stop the environment setup routine
216 Benchmark
::stop(SYSTEM_BENCHMARK
.'_environment_setup');
220 * Loads the controller and initializes it. Runs the pre_controller,
221 * post_controller_constructor, and post_controller events. Triggers
222 * a system.404 event when the route cannot be mapped to a controller.
224 * This method is benchmarked as controller_setup and controller_execution.
226 * @return object instance of controller
228 public static function & instance()
230 if (self
::$instance === NULL)
232 Benchmark
::start(SYSTEM_BENCHMARK
.'_controller_setup');
234 if (Router
::$method[0] === '_')
236 // Do not allow access to hidden methods
237 Event
::run('system.404');
240 // Include the Controller file
241 require Router
::$controller_path;
245 // Start validation of the controller
246 $class = new ReflectionClass(ucfirst(Router
::$controller).'_Controller');
248 catch (ReflectionException
$e)
250 // Controller does not exist
251 Event
::run('system.404');
254 if ($class->isAbstract() OR (IN_PRODUCTION
AND $class->getConstant('ALLOW_PRODUCTION') == FALSE))
256 // Controller is not allowed to run in production
257 Event
::run('system.404');
260 // Run system.pre_controller
261 Event
::run('system.pre_controller');
263 // Create a new controller instance
264 $controller = $class->newInstance();
266 // Controller constructor has been executed
267 Event
::run('system.post_controller_constructor', $controller);
271 // Load the controller method
272 $method = $class->getMethod(Router
::$method);
274 if ($method->isProtected() or $method->isPrivate())
276 // Do not attempt to invoke protected methods
277 throw new ReflectionException('protected controller method');
281 $arguments = Router
::$arguments;
283 catch (ReflectionException
$e)
285 // Use __call instead
286 $method = $class->getMethod('__call');
288 // Use arguments in __call format
289 $arguments = array(Router
::$method, Router
::$arguments);
292 // Stop the controller setup benchmark
293 Benchmark
::stop(SYSTEM_BENCHMARK
.'_controller_setup');
295 // Start the controller execution benchmark
296 Benchmark
::start(SYSTEM_BENCHMARK
.'_controller_execution');
298 // Execute the controller method
299 $method->invokeArgs($controller, $arguments);
301 // Controller method has been executed
302 Event
::run('system.post_controller', $controller);
304 // Stop the controller execution benchmark
305 Benchmark
::stop(SYSTEM_BENCHMARK
.'_controller_execution');
308 return self
::$instance;
312 * Get all include paths. APPPATH is the first path, followed by module
313 * paths in the order they are configured, follow by the SYSPATH.
315 * @param boolean re-process the include paths
318 public static function include_paths($process = FALSE)
320 if ($process === TRUE)
322 // Add APPPATH as the first path
323 self
::$include_paths = array(APPPATH
);
325 foreach (glob(MODPATH
.'*', GLOB_ONLYDIR
) as $path)
327 self
::$include_paths[] = $path.'/';
330 // Add SYSPATH as the last path
331 self
::$include_paths[] = SYSPATH
;
334 return self
::$include_paths;
338 * Get a config item or group.
340 * @param string item name
341 * @param boolean force a forward slash (/) at the end of the item
342 * @param boolean is the item required?
345 public static function config($key, $slash = FALSE, $required = TRUE)
347 if (self
::$configuration === NULL)
349 // Load core configuration
350 self
::$configuration['core'] = self
::config_load('core');
352 // Re-parse the include paths
353 self
::include_paths(TRUE);
356 // Get the group name from the key
357 $group = explode('.', $key, 2);
360 if ( ! isset(self
::$configuration[$group]))
362 // Load the configuration group
363 self
::$configuration[$group] = self
::config_load($group, $required);
366 // Get the value of the key string
367 $value = self
::key_string(self
::$configuration, $key);
369 if ($slash === TRUE AND is_string($value) AND $value !== '')
371 // Force the value to end with "/"
372 $value = rtrim($value, '/').'/';
379 * Sets a configuration item, if allowed.
381 * @param string config key string
382 * @param string config value
385 public static function config_set($key, $value)
387 // Do this to make sure that the config array is already loaded
390 if (substr($key, 0, 7) === 'routes.')
392 // Routes cannot contain sub keys due to possible dots in regex
393 $keys = explode('.', $key, 2);
397 // Convert dot-noted key string to an array
398 $keys = explode('.', $key);
401 // Used for recursion
402 $conf =& self
::$configuration;
403 $last = count($keys) - 1;
405 foreach ($keys as $i => $k)
417 if ($key === 'core.modules')
419 // Reprocess the include paths
420 self
::include_paths(TRUE);
427 * Load a config file.
429 * @param string config filename, without extension
430 * @param boolean is the file required?
433 public static function config_load($name, $required = TRUE)
435 if ($name === 'core')
437 // Load the application configuration file
438 require APPPATH
.'config/config'.EXT
;
439 if (is_file(APPPATH
.'config/custom/config'.EXT
))
440 include APPPATH
.'config/custom/config'.EXT
;
442 if ( ! isset($config['site_domain']))
444 // Invalid config file
445 die('Your Kohana application configuration file is not valid.');
451 if (isset(self
::$internal_cache['configuration'][$name]))
452 return self
::$internal_cache['configuration'][$name];
454 // Load matching configs
455 $configuration = array();
457 if ($files = self
::find_file('config', $name, $required))
460 foreach ($files as $file)
464 if (isset($config) AND is_array($config))
466 // Merge in configuration
467 $configuration = array_merge($configuration, $config);
472 if ($files = self
::find_file('config/custom', $name, false))
474 foreach ($files as $file)
477 if (isset($config) and is_array($config))
479 $configuration = array_merge($configuration, $config);
484 if ( ! isset(self
::$write_cache['configuration']))
487 self
::$write_cache['configuration'] = TRUE;
490 return self
::$internal_cache['configuration'][$name] = $configuration;
494 * Clears a config group from the cached configuration.
496 * @param string config group
499 public static function config_clear($group)
501 // Remove the group from config
502 unset(self
::$configuration[$group], self
::$internal_cache['configuration'][$group]);
504 if ( ! isset(self
::$write_cache['configuration']))
507 self
::$write_cache['configuration'] = TRUE;
512 * Add a new message to the log.
514 * @param string type of message
515 * @param string message text
518 public static function log($type, $message)
520 op5log
::instance()->log('ninja', $type, $message);
524 * Load data from a simple cache file. This should only be used internally,
525 * and is NOT a replacement for the Cache library.
527 * @param string unique name of cache
528 * @param integer expiration in seconds
531 public static function cache($name, $lifetime)
535 $path = APPPATH
.'cache/kohana_'.$name;
539 // Check the file modification time
540 if ((time() - filemtime($path)) < $lifetime)
543 return unserialize(file_get_contents($path));
547 // Cache is invalid, delete it
558 * Save data to a simple cache file. This should only be used internally, and
559 * is NOT a replacement for the Cache library.
561 * @param string cache name
562 * @param mixed data to cache
563 * @param integer expiration in seconds
566 public static function cache_save($name, $data, $lifetime)
571 $path = APPPATH
.'cache/kohana_'.$name;
576 return (is_file($path) and unlink($path));
580 // Write data to cache file
581 return (bool) file_put_contents($path, serialize($data));
586 * Kohana output handler.
588 * @param string current output buffer
591 public static function output_buffer($output)
593 if ( ! Event
::has_run('system.send_headers'))
595 // Run the send_headers event, specifically for cookies being set
596 Event
::run('system.send_headers');
600 self
::$output = $output;
602 // Set and return the final output
607 * Closes all open output buffers, either by flushing or cleaning all
608 * open buffers, including the Kohana output buffer.
610 * @param boolean disable to clear buffers, rather than flushing
613 public static function close_buffers($flush = TRUE)
615 if (ob_get_level() >= self
::$buffer_level)
617 // Set the close function
618 $close = ($flush === TRUE) ?
'ob_end_flush' : 'ob_end_clean';
620 while (ob_get_level() > self
::$buffer_level)
622 // Flush or clean the buffer
626 // This will flush the Kohana buffer, which sets self::$output
629 // Reset the buffer level
630 self
::$buffer_level = ob_get_level();
635 * Triggers the shutdown of Kohana by closing the output buffer, runs the system.display event.
639 public static function shutdown()
641 // Close output buffers
642 self
::close_buffers(TRUE);
644 // Run the output event
645 Event
::run('system.display', self
::$output);
647 // Render the final output
648 self
::render(self
::$output);
652 * Inserts global Kohana variables into the generated output and prints it.
654 * @param string final output that will displayed
657 public static function render($output)
659 // Fetch memory usage in MB
660 $memory = function_exists('memory_get_usage') ?
(memory_get_usage() / 1024 / 1024) : 0;
662 // Fetch benchmark for page execution time
663 $benchmark = Benchmark
::get(SYSTEM_BENCHMARK
.'_total_execution');
665 if (self
::config('core.render_stats') === TRUE)
667 // Replace the global template variables
668 $output = str_replace(
682 number_format($memory, 2).'MB',
683 count(get_included_files()),
689 if ($level = self
::config('core.output_compression') AND ini_get('output_handler') !== 'ob_gzhandler' AND (int) ini_get('zlib.output_compression') === 0)
691 if ($level < 1 OR $level > 9)
693 // Normalize the level to be an integer between 1 and 9. This
694 // step must be done to prevent gzencode from triggering an error
695 $level = max(1, min($level, 9));
698 if (stripos(@$_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE)
702 elseif (stripos(@$_SERVER['HTTP_ACCEPT_ENCODING'], 'deflate') !== FALSE)
704 $compress = 'deflate';
708 if (isset($compress) AND $level > 0)
713 // Compress output using gzip
714 $output = gzencode($output, $level);
717 // Compress output using zlib (HTTP deflate)
718 $output = gzdeflate($output, $level);
722 // This header must be sent with compressed content to prevent
723 // browser caches from breaking
724 header('Vary: Accept-Encoding');
726 // Send the content encoding header
727 header('Content-Encoding: '.$compress);
729 // Sending Content-Length in CGI can result in unexpected behavior
730 if (stripos(PHP_SAPI
, 'cgi') === FALSE)
732 header('Content-Length: '.strlen($output));
740 * Displays a 404 page.
742 * @throws Kohana_404_Exception
743 * @param string URI of page
744 * @param string custom template
747 public static function show_404($page = FALSE, $template = FALSE)
749 throw new Kohana_404_Exception($page, $template);
753 * Dual-purpose PHP error and exception handler. Uses the kohana_error_page
754 * view to display the message.
756 * @param integer|object exception object or error code
757 * @param string error message
758 * @param string filename
759 * @param integer line number
762 public static function exception_handler($exception, $message = NULL, $file = NULL, $line = NULL)
765 // PHP errors have 5 args, always
766 $PHP_ERROR = (func_num_args() === 5);
768 // Test to see if errors should be displayed
769 if ($PHP_ERROR AND (error_reporting() & $exception) === 0)
772 // This is useful for hooks to determine if a page has an error
773 self
::$has_error = TRUE;
775 // Error handling will use exactly 5 args, every time
780 $template = 'kohana_error_page';
784 $code = $exception->getCode();
785 $type = get_class($exception);
786 $message = $exception->getMessage();
787 $file = $exception->getFile();
788 $line = $exception->getLine();
789 $template = ($exception instanceof Kohana_Exception
) ?
$exception->getTemplate() : 'kohana_error_page';
792 if (is_numeric($code))
794 $codes = self
::lang('errors');
796 if ( ! empty($codes[$code]))
798 list($level, $error, $description) = $codes[$code];
803 $error = $PHP_ERROR ?
'Unknown Error' : get_class($exception);
809 // Custom error message, this will never be logged
815 // Remove the DOCROOT from the path, as a security precaution
816 $file = str_replace('\\', '/', realpath($file));
817 $file = preg_replace('|^'.preg_quote(DOCROOT
).'|', '', $file);
819 if ($level <= self
::$configuration['core']['log_threshold'])
822 self
::log('error', self
::lang('core.uncaught_exception', $type, $message, $file, $line));
827 $description = self
::lang('errors.'.E_RECOVERABLE_ERROR
);
828 $description = is_array($description) ?
$description[2] : '';
830 if ( ! headers_sent())
832 header('HTTP/1.1 500 Internal Server Error');
837 if(!headers_sent()) {
838 if (method_exists($exception, 'sendHeaders'))
840 $exception->sendHeaders();
842 header('HTTP/1.1 500 Internal Server Error');
847 while (ob_get_level() > self
::$buffer_level)
849 // Close open buffers
853 // Test if display_errors is on
854 if (self
::$configuration['core']['display_errors'] === TRUE)
858 // Remove the first entry of debug_backtrace(), it is the exception_handler call
859 $trace = $PHP_ERROR ?
array_slice(debug_backtrace(), 1) : $exception->getTrace();
861 // Beautify backtrace
862 $trace = self
::backtrace($trace);
866 require self
::find_file('views', empty($template) ?
'kohana_error_page' : $template);
870 // Get the i18n messages
871 $error = self
::lang('core.generic_error');
872 $message = self
::lang('core.errors_disabled', url
::site(), url
::site(Router
::$current_uri));
874 // Load the errors_disabled view
875 require self
::find_file('views', 'kohana_error_disabled');
878 if ( ! Event
::has_run('system.shutdown'))
880 // Run the shutdown even to ensure a clean exit
881 Event
::run('system.shutdown');
884 // Turn off error reporting
886 } catch( Exception
$e ) {
887 /* Exceptions in an exceptionhandler results in "Exception thrown without a stack trace in "Unkonwn"
888 * Better to just print the exception ugly, so we get some kind of useful information instaead
890 while( @ob_end_clean
() ) {}
891 print "Exception during error handler: ".$e->getMessage()."\n";
892 print $e->getTraceAsString();
899 * Provides class auto-loading.
901 * @throws Kohana_Exception
902 * @param string name of class
905 public static function auto_load($class)
907 if (class_exists($class, FALSE))
910 if (($suffix = strrpos($class, '_')) > 0)
912 // Find the class suffix
913 $suffix = substr($class, $suffix +
1);
921 if ($suffix === 'Core')
924 $file = substr($class, 0, -5);
926 elseif ($suffix === 'Controller')
928 $type = 'controllers';
929 // Lowercase filename
930 $file = strtolower(substr($class, 0, -11));
932 elseif ($suffix === 'Model')
935 // Lowercase filename
936 $file = strtolower(substr($class, 0, -6));
938 elseif ($suffix === 'Driver')
940 $type = 'libraries/drivers';
941 $file = str_replace('_', '/', substr($class, 0, -7));
943 elseif ($suffix === 'Widget')
946 $classname = substr($class, 0, -7);
947 $file = $classname . '/' . $classname;
951 // This could be either a library or a helper, but libraries must
952 // always be capitalized, so we check if the first character is
953 // uppercase. If it is, we are loading a library, not a helper.
954 $type = ($class[0] < 'a') ?
'libraries' : 'helpers';
958 if ($filename = self
::find_file($type, $file))
965 // The class could not be found
969 if ($filename = self
::find_file($type, self
::$configuration['core']['extension_prefix'].$class))
971 // Load the class extension
974 elseif ($suffix !== 'Core' AND class_exists($class.'_Core', FALSE))
976 // Class extension to be evaluated
977 $extension = 'class '.$class.' extends '.$class.'_Core { }';
979 // Start class analysis
980 $core = new ReflectionClass($class.'_Core');
982 if ($core->isAbstract())
984 // Make the extension abstract
985 $extension = 'abstract '.$extension;
988 // Transparent class extensions are handled using eval. This is
989 // a disgusting hack, but it gets the job done.
997 * Find a resource file in a given directory. Files will be located according
998 * to the order of the include paths. Configuration and i18n files will be
999 * returned in reverse order.
1001 * @throws Kohana_Exception if file is required and not found
1002 * @param string directory to search in
1003 * @param string filename to look for (including extension only if 4th parameter is TRUE)
1004 * @param boolean file required
1005 * @param string file extension
1006 * @return array if the type is config, i18n or l10n
1007 * @return string if the file is found
1008 * @return FALSE if the file is not found
1010 public static function find_file($directory, $filename, $required = FALSE, $ext = FALSE)
1012 // NOTE: This test MUST be not be a strict comparison (===), or empty
1013 // extensions will be allowed!
1016 // Use the default extension
1021 // Add a period before the extension
1026 $search = $directory.'/'.$filename.$ext;
1028 if (isset(self
::$internal_cache['find_file_paths'][$search]))
1029 return self
::$internal_cache['find_file_paths'][$search];
1031 // Load include paths
1032 $paths = self
::$include_paths;
1034 // Nothing found, yet
1037 if ($directory === 'config' OR $directory === 'i18n' OR $directory === 'config/custom')
1039 // Search in reverse, for merging
1040 $paths = array_reverse($paths);
1042 foreach ($paths as $path)
1044 if (is_file($path.$search))
1046 // A matching file has been found
1047 $found[] = $path.$search;
1053 foreach ($paths as $path)
1055 if (is_file($path.$search))
1057 // A matching file has been found
1058 $found = $path.$search;
1066 if ($found === NULL)
1068 if ($required === TRUE)
1070 // Directory i18n key
1071 $directory = 'core.'.inflector
::singular($directory);
1073 // If the file is required, throw an exception
1074 throw new Kohana_Exception('core.resource_not_found', self
::lang($directory), $filename);
1078 // Nothing was found, return FALSE
1083 if ( ! isset(self
::$write_cache['find_file_paths']))
1085 // Write cache at shutdown
1086 self
::$write_cache['find_file_paths'] = TRUE;
1089 return self
::$internal_cache['find_file_paths'][$search] = $found;
1093 * Lists all files and directories in a resource path.
1095 * @param string directory to search
1096 * @param boolean list all files to the maximum depth?
1097 * @param string full path to search (used for recursion, *never* set this manually)
1098 * @return array filenames and directories
1100 public static function list_files($directory, $recursive = FALSE, $path = FALSE)
1104 if ($path === FALSE)
1106 $paths = array_reverse(self
::include_paths());
1108 foreach ($paths as $path)
1110 // Recursively get and merge all files
1111 $files = array_merge($files, self
::list_files($directory, $recursive, $path.$directory));
1116 $path = rtrim($path, '/').'/';
1118 if (is_readable($path))
1120 $items = (array) glob($path.'*');
1122 foreach ($items as $index => $item)
1124 $files[] = $item = str_replace('\\', '/', $item);
1127 if (is_dir($item) AND $recursive == TRUE)
1129 // Filename should only be the basename
1130 $item = pathinfo($item, PATHINFO_BASENAME
);
1132 // Append sub-directory search
1133 $files = array_merge($files, self
::list_files($directory, TRUE, $path.$item));
1143 * Fetch an i18n language item.
1145 * @param string language key to fetch
1146 * @param array additional information to insert into the line
1147 * @return string i18n language string, or the requested key if the i18n item is not found
1149 public static function lang($key, $args = array())
1151 // Extract the main group from the key
1152 $group = explode('.', $key, 2);
1156 $locale = self
::config('locale.language.0');
1158 if ( ! isset(self
::$internal_cache['language'][$locale][$group]))
1160 // Messages for this group
1161 $messages = array();
1163 if ($files = self
::find_file('i18n', $locale.'/'.$group))
1165 foreach ($files as $file)
1169 // Merge in configuration
1170 if ( ! empty($lang) AND is_array($lang))
1172 foreach ($lang as $k => $v)
1180 if ( ! isset(self
::$write_cache['language']))
1182 // Write language cache
1183 self
::$write_cache['language'] = TRUE;
1186 self
::$internal_cache['language'][$locale][$group] = $messages;
1189 // Get the line from cache
1190 $line = self
::key_string(self
::$internal_cache['language'][$locale], $key);
1194 self
::log('error', 'Missing i18n entry '.$key.' for language '.$locale);
1196 // Return the key string as fallback
1200 if (is_string($line) AND func_num_args() > 1)
1202 $args = array_slice(func_get_args(), 1);
1204 // Add the arguments into the line
1205 $line = vsprintf($line, is_array($args[0]) ?
$args[0] : $args);
1212 * Returns the value of a key, defined by a 'dot-noted' string, from an array.
1214 * @param array array to search
1215 * @param string dot-noted string: foo.bar.baz
1216 * @return string if the key is found
1217 * @return void if the key is not found
1219 public static function key_string($array, $keys)
1225 $keys = explode('.', $keys);
1230 $key = array_shift($keys);
1232 if (isset($array[$key]))
1234 if (is_array($array[$key]) AND ! empty($keys))
1236 // Dig down to prepare the next loop
1237 $array = $array[$key];
1241 // Requested key was found
1242 return $array[$key];
1247 // Requested key is not set
1251 while ( ! empty($keys));
1257 * Sets values in an array by using a 'dot-noted' string.
1259 * @param array array to set keys in (reference)
1260 * @param string dot-noted string: foo.bar.baz
1261 * @return mixed fill value for the key
1264 public static function key_string_set( & $array, $keys, $fill = NULL)
1266 if (is_object($array) AND ($array instanceof ArrayObject
))
1269 $array_copy = $array->getArrayCopy();
1272 $array_object = TRUE;
1276 if ( ! is_array($array))
1278 // Must always be an array
1279 $array = (array) $array;
1282 // Copy is a reference to the array
1283 $array_copy =& $array;
1290 $keys = explode('.', $keys);
1292 // Create reference to the array
1293 $row =& $array_copy;
1295 for ($i = 0, $end = count($keys) - 1; $i <= $end; $i++
)
1297 // Get the current key
1300 if ( ! isset($row[$key]))
1302 if (isset($keys[$i +
1]))
1304 // Make the value an array
1305 $row[$key] = array();
1313 elseif (isset($keys[$i +
1]))
1315 // Make the value an array
1316 $row[$key] = (array) $row[$key];
1319 // Go down a level, creating a new row reference
1323 if (isset($array_object))
1325 // Swap the array back in
1326 $array->exchangeArray($array_copy);
1331 * Retrieves current user agent information:
1332 * keys: browser, version, platform, mobile, robot, referrer, languages, charsets
1333 * tests: is_browser, is_mobile, is_robot, accept_lang, accept_charset
1335 * @param string key or test name
1336 * @param string used with "accept" tests: user_agent(accept_lang, en)
1337 * @return array languages and charsets
1338 * @return string all other keys
1339 * @return boolean all tests
1341 public static function user_agent($key = 'agent', $compare = NULL)
1345 // Return the raw string
1346 if ($key === 'agent')
1347 return self
::$user_agent;
1351 // Parse the user agent and extract basic information
1352 $agents = self
::config('user_agents');
1354 foreach ($agents as $type => $data)
1356 foreach ($data as $agent => $name)
1358 if (stripos(self
::$user_agent, $agent) !== FALSE)
1360 if ($type === 'browser' AND preg_match('|'.preg_quote($agent).'[^0-9.]*+([0-9.][0-9.a-z]*)|i', self
::$user_agent, $match))
1362 // Set the browser version
1363 $info['version'] = $match[1];
1366 // Set the agent name
1367 $info[$type] = $name;
1374 if (empty($info[$key]))
1382 $return = ! empty($info[substr($key, 3)]);
1386 if ( ! empty($_SERVER['HTTP_ACCEPT_LANGUAGE']))
1388 if (preg_match_all('/[-a-z]{2,}/', strtolower(trim($_SERVER['HTTP_ACCEPT_LANGUAGE'])), $matches))
1391 $return = $matches[0];
1397 if ( ! empty($_SERVER['HTTP_ACCEPT_CHARSET']))
1399 if (preg_match_all('/[-a-z0-9]{2,}/', strtolower(trim($_SERVER['HTTP_ACCEPT_CHARSET'])), $matches))
1402 $return = $matches[0];
1407 if ( ! empty($_SERVER['HTTP_REFERER']))
1410 $return = trim($_SERVER['HTTP_REFERER']);
1415 // Cache the return value
1416 isset($return) and $info[$key] = $return;
1419 if ( ! empty($compare))
1421 // The comparison must always be lowercase
1422 $compare = strtolower($compare);
1427 // Check if the lange is accepted
1428 return in_array($compare, self
::user_agent('languages'));
1430 case 'accept_charset':
1431 // Check if the charset is accepted
1432 return in_array($compare, self
::user_agent('charsets'));
1435 // Invalid comparison
1441 // Return the key, if set
1442 return isset($info[$key]) ?
$info[$key] : NULL;
1446 * Quick debugging of any variable. Any number of parameters can be set.
1450 public static function debug()
1452 if (func_num_args() === 0)
1456 $params = func_get_args();
1459 foreach ($params as $var)
1461 $output[] = '<pre>('.gettype($var).') '.html
::specialchars(print_r($var, TRUE)).'</pre>';
1464 return implode("\n", $output);
1468 * Displays nice backtrace information.
1469 * @see http://php.net/debug_backtrace
1471 * @param array backtrace generated by an exception or debug_backtrace
1474 public static function backtrace($trace)
1476 $arg_badword = array(
1477 'passw', /* password and passwd */
1483 if ( ! is_array($trace))
1489 foreach ($trace as $entry)
1493 if (isset($entry['file']))
1495 $temp .= self
::lang('core.error_file_line', preg_replace('!^'.preg_quote(DOCROOT
).'!', '', $entry['file']), $entry['line']);
1501 if (isset($entry['class']))
1503 // Add class and call type
1504 $temp .= $entry['class'].$entry['type'];
1506 $reflclass = new ReflectionClass($entry['class']);
1507 } catch( Exception
$e ) {
1508 // Don't care about the problem... just don't expand variable names in that case
1514 $temp .= $entry['function'].'( ';
1516 $reflmethod = false;
1519 $reflmethod = $reflclass->getMethod($entry['function']);
1521 $reflmethod = new ReflectionFunction($entry['function']);
1522 } catch( Exception
$e ) {
1523 // Don't care about the problem... just don't expand variable names in that case
1524 $reflmethod = false;
1526 // Add function args
1527 if (isset($entry['args']) AND is_array($entry['args']))
1529 // Separator starts as nothing
1535 $reflargs = $reflmethod->getParameters();
1536 } catch( Exception
$e ) {
1537 // Don't care about the problem... just don't expand variable names in that case
1541 while ($arg = array_shift($entry['args']))
1545 if( !empty($reflargs) )
1546 $argname = array_shift( $reflargs )->getName();
1547 } catch( Exception
$e ) {
1548 // Don't care about the problem... just don't expand variable names in that case
1552 if (is_string($arg) AND substr($arg, 0, 4) !== "unix" AND is_file($arg))
1554 // Remove docroot from filename
1555 $arg = preg_replace('!^'.preg_quote(DOCROOT
).'!', '', $arg);
1558 foreach($arg_badword as $badword) {
1559 if( stripos($argname, $badword) !== false )
1563 $temp .= $sep.$argname.' = '.html
::specialchars(print_r($arg, TRUE));
1565 // Change separator to a comma
1570 $temp .= ' )</pre></li>';
1575 return '<ul class="backtrace">'.implode("\n", $output).'</ul>';
1579 * Saves the internal caches: configuration, include paths, etc.
1583 public static function internal_cache_save()
1585 if ( ! is_array(self
::$write_cache))
1588 // Get internal cache names
1589 $caches = array_keys(self
::$write_cache);
1594 foreach ($caches as $cache)
1596 if (isset(self
::$internal_cache[$cache]))
1598 // Write the cache file
1599 self
::cache_save($cache, self
::$internal_cache[$cache], self
::$configuration['core']['internal_cache']);
1601 // A cache has been written
1612 * Creates a generic i18n exception.
1614 class Kohana_Exception
extends Exception
{
1617 protected $template = 'kohana_error_page';
1620 protected $header = FALSE;
1623 protected $code = E_KOHANA
;
1626 * Set exception message.
1628 * @param string i18n language key for the message
1629 * @param array addition line parameters
1631 public function __construct($error)
1633 $args = array_slice(func_get_args(), 1);
1635 // Fetch the error message
1636 $message = Kohana
::lang($error, $args);
1638 if ($message === $error OR empty($message))
1640 // Unable to locate the message for the error
1641 $message = 'Unknown Exception: '.$error;
1644 // Sets $this->message the proper way
1645 parent
::__construct($message);
1649 * Magic method for converting an object to a string.
1651 * @return string i18n message
1653 public function __toString()
1655 return (string) $this->message
;
1659 * Fetch the template name.
1663 public function getTemplate()
1665 return $this->template
;
1669 * Sends an Internal Server Error header.
1673 public function sendHeaders()
1675 // Send the 500 header
1676 header('HTTP/1.1 500 Internal Server Error');
1679 } // End Kohana Exception
1682 * Creates a custom exception.
1684 class Kohana_User_Exception
extends Kohana_Exception
{
1687 * Set exception title and message.
1689 * @param string exception title string
1690 * @param string exception message string
1691 * @param string custom error template
1693 public function __construct($title, $message, $template = FALSE)
1695 Exception
::__construct($message);
1697 $this->code
= $title;
1699 if ($template !== FALSE)
1701 $this->template
= $template;
1705 } // End Kohana PHP Exception
1708 * Creates a Page Not Found exception.
1710 class Kohana_404_Exception
extends Kohana_Exception
{
1712 protected $code = E_PAGE_NOT_FOUND
;
1715 * Set internal properties.
1717 * @param string URL of page
1718 * @param string custom error template
1720 public function __construct($page = FALSE, $template = FALSE)
1722 if ($page === FALSE)
1724 // Construct the page URI using Router properties
1725 $page = Router
::$current_uri.Router
::$url_suffix.Router
::$query_string;
1728 Exception
::__construct(Kohana
::lang('core.page_not_found', $page));
1730 $this->template
= $template;
1734 * Sends "File Not Found" headers, to emulate server behavior.
1738 public function sendHeaders()
1740 // Send the 404 header
1741 header('HTTP/1.1 404 File Not Found');
1744 } // End Kohana 404 Exception