1 <?php
defined('SYSPATH') OR die('No direct script access.');
3 * Contains the most low-level helpers methods in Kohana:
5 * - Environment initialization
6 * - Locating files within the cascading filesystem
7 * - Auto-loading and transparent extension of classes
8 * - Variable and path debugging
13 * @copyright (c) 2008-2012 Kohana Team
14 * @license http://kohanaframework.org/license
18 // Release version and codename
19 const VERSION
= '3.3.0';
20 const CODENAME
= 'badius';
22 // Common environment type constants for consistency and convenience
23 const PRODUCTION
= 10;
26 const DEVELOPMENT
= 40;
28 // Security check that is added to all generated PHP files
29 const FILE_SECURITY
= '<?php defined(\'SYSPATH\') OR die(\'No direct script access.\');';
31 // Format of cache files: header, cache name, and data
32 const FILE_CACHE
= ":header \n\n// :name\n\n:data\n";
35 * @var string Current environment name
37 public static $environment = Kohana
::DEVELOPMENT
;
40 * @var boolean True if Kohana is running on windows
42 public static $is_windows = FALSE;
45 * @var boolean True if [magic quotes](http://php.net/manual/en/security.magicquotes.php) is enabled.
47 public static $magic_quotes = FALSE;
50 * @var boolean TRUE if PHP safe mode is on
52 public static $safe_mode = FALSE;
57 public static $content_type = 'text/html';
60 * @var string character set of input and output
62 public static $charset = 'utf-8';
65 * @var string the name of the server Kohana is hosted upon
67 public static $server_name = '';
70 * @var array list of valid host names for this instance
72 public static $hostnames = array();
75 * @var string base URL to the application
77 public static $base_url = '/';
80 * @var string Application index file, added to links generated by Kohana. Set by [Kohana::init]
82 public static $index_file = 'index.php';
85 * @var string Cache directory, used by [Kohana::cache]. Set by [Kohana::init]
87 public static $cache_dir;
90 * @var integer Default lifetime for caching, in seconds, used by [Kohana::cache]. Set by [Kohana::init]
92 public static $cache_life = 60;
95 * @var boolean Whether to use internal caching for [Kohana::find_file], does not apply to [Kohana::cache]. Set by [Kohana::init]
97 public static $caching = FALSE;
100 * @var boolean Whether to enable [profiling](kohana/profiling). Set by [Kohana::init]
102 public static $profiling = TRUE;
105 * @var boolean Enable Kohana catching and displaying PHP errors and exceptions. Set by [Kohana::init]
107 public static $errors = TRUE;
110 * @var array Types of errors to display at shutdown
112 public static $shutdown_errors = array(E_PARSE
, E_ERROR
, E_USER_ERROR
);
115 * @var boolean set the X-Powered-By header
117 public static $expose = FALSE;
120 * @var Log logging object
125 * @var Config config object
127 public static $config;
130 * @var boolean Has [Kohana::init] been called?
132 protected static $_init = FALSE;
135 * @var array Currently active modules
137 protected static $_modules = array();
140 * @var array Include paths that are used to find files
142 protected static $_paths = array(APPPATH
, SYSPATH
);
145 * @var array File path cache, used when caching is true in [Kohana::init]
147 protected static $_files = array();
150 * @var boolean Has the file path cache changed during this execution? Used internally when when caching is true in [Kohana::init]
152 protected static $_files_changed = FALSE;
155 * Initializes the environment:
157 * - Disables register_globals and magic_quotes_gpc
158 * - Determines the current environment
159 * - Set global settings
160 * - Sanitizes GET, POST, and COOKIE variables
161 * - Converts GET, POST, and COOKIE variables to the global character set
163 * The following settings can be set:
165 * Type | Setting | Description | Default Value
166 * ----------|------------|------------------------------------------------|---------------
167 * `string` | base_url | The base URL for your application. This should be the *relative* path from your DOCROOT to your `index.php` file, in other words, if Kohana is in a subfolder, set this to the subfolder name, otherwise leave it as the default. **The leading slash is required**, trailing slash is optional. | `"/"`
168 * `string` | index_file | The name of the [front controller](http://en.wikipedia.org/wiki/Front_Controller_pattern). This is used by Kohana to generate relative urls like [HTML::anchor()] and [URL::base()]. This is usually `index.php`. To [remove index.php from your urls](tutorials/clean-urls), set this to `FALSE`. | `"index.php"`
169 * `string` | charset | Character set used for all input and output | `"utf-8"`
170 * `string` | cache_dir | Kohana's cache directory. Used by [Kohana::cache] for simple internal caching, like [Fragments](kohana/fragments) and **\[caching database queries](this should link somewhere)**. This has nothing to do with the [Cache module](cache). | `APPPATH."cache"`
171 * `integer` | cache_life | Lifetime, in seconds, of items cached by [Kohana::cache] | `60`
172 * `boolean` | errors | Should Kohana catch PHP errors and uncaught Exceptions and show the `error_view`. See [Error Handling](kohana/errors) for more info. <br /> <br /> Recommended setting: `TRUE` while developing, `FALSE` on production servers. | `TRUE`
173 * `boolean` | profile | Whether to enable the [Profiler](kohana/profiling). <br /> <br />Recommended setting: `TRUE` while developing, `FALSE` on production servers. | `TRUE`
174 * `boolean` | caching | Cache file locations to speed up [Kohana::find_file]. This has nothing to do with [Kohana::cache], [Fragments](kohana/fragments) or the [Cache module](cache). <br /> <br /> Recommended setting: `FALSE` while developing, `TRUE` on production servers. | `FALSE`
175 * `boolean` | expose | Set the X-Powered-By header
177 * @throws Kohana_Exception
178 * @param array $settings Array of settings. See above.
180 * @uses Kohana::globals
181 * @uses Kohana::sanitize
182 * @uses Kohana::cache
185 public static function init(array $settings = NULL)
189 // Do not allow execution twice
193 // Kohana is now initialized
194 Kohana
::$_init = TRUE;
196 if (isset($settings['profile']))
199 Kohana
::$profiling = (bool) $settings['profile'];
202 // Start an output buffer
205 if (isset($settings['errors']))
207 // Enable error handling
208 Kohana
::$errors = (bool) $settings['errors'];
211 if (Kohana
::$errors === TRUE)
213 // Enable Kohana exception handling, adds stack traces and error source.
214 set_exception_handler(array('Kohana_Exception', 'handler'));
216 // Enable Kohana error handling, converts all PHP errors to exceptions.
217 set_error_handler(array('Kohana', 'error_handler'));
221 * Enable xdebug parameter collection in development mode to improve fatal stack traces.
223 if (Kohana
::$environment == Kohana
::DEVELOPMENT
AND extension_loaded('xdebug'))
225 ini_set('xdebug.collect_params', 3);
228 // Enable the Kohana shutdown handler, which catches E_FATAL errors.
229 register_shutdown_function(array('Kohana', 'shutdown_handler'));
231 if (ini_get('register_globals'))
233 // Reverse the effects of register_globals
237 if (isset($settings['expose']))
239 Kohana
::$expose = (bool) $settings['expose'];
242 // Determine if we are running in a Windows environment
243 Kohana
::$is_windows = (DIRECTORY_SEPARATOR
=== '\\');
245 // Determine if we are running in safe mode
246 Kohana
::$safe_mode = (bool) ini_get('safe_mode');
248 if (isset($settings['cache_dir']))
250 if ( ! is_dir($settings['cache_dir']))
254 // Create the cache directory
255 mkdir($settings['cache_dir'], 0755, TRUE);
257 // Set permissions (must be manually set to fix umask issues)
258 chmod($settings['cache_dir'], 0755);
262 throw new Kohana_Exception('Could not create cache directory :dir',
263 array(':dir' => Debug
::path($settings['cache_dir'])));
267 // Set the cache directory path
268 Kohana
::$cache_dir = realpath($settings['cache_dir']);
272 // Use the default cache directory
273 Kohana
::$cache_dir = APPPATH
.'cache';
276 if ( ! is_writable(Kohana
::$cache_dir))
278 throw new Kohana_Exception('Directory :dir must be writable',
279 array(':dir' => Debug
::path(Kohana
::$cache_dir)));
282 if (isset($settings['cache_life']))
284 // Set the default cache lifetime
285 Kohana
::$cache_life = (int) $settings['cache_life'];
288 if (isset($settings['caching']))
290 // Enable or disable internal caching
291 Kohana
::$caching = (bool) $settings['caching'];
294 if (Kohana
::$caching === TRUE)
296 // Load the file path cache
297 Kohana
::$_files = Kohana
::cache('Kohana::find_file()');
300 if (isset($settings['charset']))
302 // Set the system character set
303 Kohana
::$charset = strtolower($settings['charset']);
306 if (function_exists('mb_internal_encoding'))
308 // Set the MB extension encoding to the same character set
309 mb_internal_encoding(Kohana
::$charset);
312 if (isset($settings['base_url']))
315 Kohana
::$base_url = rtrim($settings['base_url'], '/').'/';
318 if (isset($settings['index_file']))
320 // Set the index file
321 Kohana
::$index_file = trim($settings['index_file'], '/');
324 // Determine if the extremely evil magic quotes are enabled
325 Kohana
::$magic_quotes = (version_compare(PHP_VERSION
, '5.4') < 0 AND get_magic_quotes_gpc());
327 // Sanitize all request variables
328 $_GET = Kohana
::sanitize($_GET);
329 $_POST = Kohana
::sanitize($_POST);
330 $_COOKIE = Kohana
::sanitize($_COOKIE);
332 // Load the logger if one doesn't already exist
333 if ( ! Kohana
::$log instanceof Log
)
335 Kohana
::$log = Log
::instance();
338 // Load the config if one doesn't already exist
339 if ( ! Kohana
::$config instanceof Config
)
341 Kohana
::$config = new Config
;
346 * Cleans up the environment:
348 * - Restore the previous error and exception handlers
349 * - Destroy the Kohana::$log and Kohana::$config objects
353 public static function deinit()
357 // Removed the autoloader
358 spl_autoload_unregister(array('Kohana', 'auto_load'));
362 // Go back to the previous error handler
363 restore_error_handler();
365 // Go back to the previous exception handler
366 restore_exception_handler();
369 // Destroy objects created by init
370 Kohana
::$log = Kohana
::$config = NULL;
372 // Reset internal storage
373 Kohana
::$_modules = Kohana
::$_files = array();
374 Kohana
::$_paths = array(APPPATH
, SYSPATH
);
376 // Reset file cache status
377 Kohana
::$_files_changed = FALSE;
379 // Kohana is no longer initialized
380 Kohana
::$_init = FALSE;
385 * Reverts the effects of the `register_globals` PHP setting by unsetting
386 * all global varibles except for the default super globals (GPCS, etc),
387 * which is a [potential security hole.][ref-wikibooks]
389 * This is called automatically by [Kohana::init] if `register_globals` is
393 * [ref-wikibooks]: http://en.wikibooks.org/wiki/PHP_Programming/Register_Globals
397 public static function globals()
399 if (isset($_REQUEST['GLOBALS']) OR isset($_FILES['GLOBALS']))
401 // Prevent malicious GLOBALS overload attack
402 echo "Global variable overload attack detected! Request aborted.\n";
404 // Exit with an error status
408 // Get the variable names of all globals
409 $global_variables = array_keys($GLOBALS);
411 // Remove the standard global variables from the list
412 $global_variables = array_diff($global_variables, array(
424 foreach ($global_variables as $name)
426 // Unset the global variable, effectively disabling register_globals
427 unset($GLOBALS[$name]);
432 * Recursively sanitizes an input variable:
434 * - Strips slashes if magic quotes are enabled
435 * - Normalizes all newlines to LF
437 * @param mixed $value any variable
438 * @return mixed sanitized variable
440 public static function sanitize($value)
442 if (is_array($value) OR is_object($value))
444 foreach ($value as $key => $val)
446 // Recursively clean each value
447 $value[$key] = Kohana
::sanitize($val);
450 elseif (is_string($value))
452 if (Kohana
::$magic_quotes === TRUE)
454 // Remove slashes added by magic quotes
455 $value = stripslashes($value);
458 if (strpos($value, "\r") !== FALSE)
460 // Standardize newlines
461 $value = str_replace(array("\r\n", "\r"), "\n", $value);
469 * Provides auto-loading support of classes that follow Kohana's [class
470 * naming conventions](kohana/conventions#class-names-and-file-location).
471 * See [Loading Classes](kohana/autoloading) for more information.
473 * // Loads classes/My/Class/Name.php
474 * Kohana::auto_load('My_Class_Name');
476 * or with a custom directory:
478 * // Loads vendor/My/Class/Name.php
479 * Kohana::auto_load('My_Class_Name', 'vendor');
481 * You should never have to call this function, as simply calling a class
482 * will cause it to be called.
484 * This function must be enabled as an autoloader in the bootstrap:
486 * spl_autoload_register(array('Kohana', 'auto_load'));
488 * @param string $class Class name
489 * @param string $directory Directory to load from
492 public static function auto_load($class, $directory = 'classes')
494 // Transform the class name according to PSR-0
495 $class = ltrim($class, '\\');
499 if ($last_namespace_position = strripos($class, '\\'))
501 $namespace = substr($class, 0, $last_namespace_position);
502 $class = substr($class, $last_namespace_position +
1);
503 $file = str_replace('\\', DIRECTORY_SEPARATOR
, $namespace).DIRECTORY_SEPARATOR
;
506 $file .= str_replace('_', DIRECTORY_SEPARATOR
, $class);
508 if ($path = Kohana
::find_file($directory, $file))
510 // Load the class file
513 // Class has been found
517 // Class is not in the filesystem
522 * Provides auto-loading support of classes that follow Kohana's old class
523 * naming conventions.
525 * This is included for compatibility purposes with older modules.
527 * @param string $class Class name
528 * @param string $directory Directory to load from
531 public static function auto_load_lowercase($class, $directory = 'classes')
533 // Transform the class name into a path
534 $file = str_replace('_', DIRECTORY_SEPARATOR
, strtolower($class));
536 if ($path = Kohana
::find_file($directory, $file))
538 // Load the class file
541 // Class has been found
545 // Class is not in the filesystem
550 * Changes the currently enabled modules. Module paths may be relative
551 * or absolute, but must point to a directory:
553 * Kohana::modules(array('modules/foo', MODPATH.'bar'));
555 * @param array $modules list of module paths
556 * @return array enabled modules
558 public static function modules(array $modules = NULL)
560 if ($modules === NULL)
562 // Not changing modules, just return the current set
563 return Kohana
::$_modules;
566 // Start a new list of include paths, APPPATH first
567 $paths = array(APPPATH
);
569 foreach ($modules as $name => $path)
573 // Add the module to include paths
574 $paths[] = $modules[$name] = realpath($path).DIRECTORY_SEPARATOR
;
578 // This module is invalid, remove it
579 throw new Kohana_Exception('Attempted to load an invalid or missing module \':module\' at \':path\'', array(
581 ':path' => Debug
::path($path),
586 // Finish the include paths by adding SYSPATH
589 // Set the new include paths
590 Kohana
::$_paths = $paths;
592 // Set the current module list
593 Kohana
::$_modules = $modules;
595 foreach (Kohana
::$_modules as $path)
597 $init = $path.'init'.EXT
;
601 // Include the module initialization file once
606 return Kohana
::$_modules;
610 * Returns the the currently active include paths, including the
611 * application, system, and each module's path.
615 public static function include_paths()
617 return Kohana
::$_paths;
621 * Searches for a file in the [Cascading Filesystem](kohana/files), and
622 * returns the path to the file that has the highest precedence, so that it
625 * When searching the "config", "messages", or "i18n" directories, or when
626 * the `$array` flag is set to true, an array of all the files that match
627 * that path in the [Cascading Filesystem](kohana/files) will be returned.
628 * These files will return arrays which must be merged together.
630 * If no extension is given, the default extension (`EXT` set in
631 * `index.php`) will be used.
633 * // Returns an absolute path to views/template.php
634 * Kohana::find_file('views', 'template');
636 * // Returns an absolute path to media/css/style.css
637 * Kohana::find_file('media', 'css/style', 'css');
639 * // Returns an array of all the "mimes" configuration files
640 * Kohana::find_file('config', 'mimes');
642 * @param string $dir directory name (views, i18n, classes, extensions, etc.)
643 * @param string $file filename with subdirectory
644 * @param string $ext extension to search for
645 * @param boolean $array return an array of files?
646 * @return array a list of files when $array is TRUE
647 * @return string single file path
649 public static function find_file($dir, $file, $ext = NULL, $array = FALSE)
653 // Use the default extension
658 // Prefix the extension with a period
667 // Create a partial path of the filename
668 $path = $dir.DIRECTORY_SEPARATOR
.$file.$ext;
670 if (Kohana
::$caching === TRUE AND isset(Kohana
::$_files[$path.($array ?
'_array' : '_path')]))
672 // This path has been cached
673 return Kohana
::$_files[$path.($array ?
'_array' : '_path')];
676 if (Kohana
::$profiling === TRUE AND class_exists('Profiler', FALSE))
678 // Start a new benchmark
679 $benchmark = Profiler
::start('Kohana', __FUNCTION__
);
682 if ($array OR $dir === 'config' OR $dir === 'i18n' OR $dir === 'messages')
684 // Include paths must be searched in reverse
685 $paths = array_reverse(Kohana
::$_paths);
687 // Array of files that have been found
690 foreach ($paths as $dir)
692 if (is_file($dir.$path))
694 // This path has a file, add it to the list
695 $found[] = $dir.$path;
701 // The file has not been found yet
704 foreach (Kohana
::$_paths as $dir)
706 if (is_file($dir.$path))
708 // A path has been found
717 if (Kohana
::$caching === TRUE)
719 // Add the path to the cache
720 Kohana
::$_files[$path.($array ?
'_array' : '_path')] = $found;
722 // Files have been changed
723 Kohana
::$_files_changed = TRUE;
726 if (isset($benchmark))
728 // Stop the benchmark
729 Profiler
::stop($benchmark);
736 * Recursively finds all of the files in the specified directory at any
737 * location in the [Cascading Filesystem](kohana/files), and returns an
738 * array of all the files found, sorted alphabetically.
740 * // Find all view files.
741 * $views = Kohana::list_files('views');
743 * @param string $directory directory name
744 * @param array $paths list of paths to search
747 public static function list_files($directory = NULL, array $paths = NULL)
749 if ($directory !== NULL)
751 // Add the directory separator
752 $directory .= DIRECTORY_SEPARATOR
;
757 // Use the default paths
758 $paths = Kohana
::$_paths;
761 // Create an array for the files
764 foreach ($paths as $path)
766 if (is_dir($path.$directory))
768 // Create a new directory iterator
769 $dir = new DirectoryIterator($path.$directory);
771 foreach ($dir as $file)
774 $filename = $file->getFilename();
776 if ($filename[0] === '.' OR $filename[strlen($filename)-1] === '~')
778 // Skip all hidden files and UNIX backup files
782 // Relative filename is the array key
783 $key = $directory.$filename;
787 if ($sub_dir = Kohana
::list_files($key, $paths))
789 if (isset($found[$key]))
791 // Append the sub-directory list
792 $found[$key] +
= $sub_dir;
796 // Create a new sub-directory list
797 $found[$key] = $sub_dir;
803 if ( ! isset($found[$key]))
805 // Add new files to the list
806 $found[$key] = realpath($file->getPathName());
813 // Sort the results alphabetically
820 * Loads a file within a totally empty scope and returns the output:
822 * $foo = Kohana::load('foo.php');
824 * @param string $file
827 public static function load($file)
829 return include $file;
833 * Provides simple file-based caching for strings and arrays:
835 * // Set the "foo" cache
836 * Kohana::cache('foo', 'hello, world');
838 * // Get the "foo" cache
839 * $foo = Kohana::cache('foo');
841 * All caches are stored as PHP code, generated with [var_export][ref-var].
842 * Caching objects may not work as expected. Storing references or an
843 * object or array that has recursion will cause an E_FATAL.
845 * The cache directory and default cache lifetime is set by [Kohana::init]
847 * [ref-var]: http://php.net/var_export
849 * @throws Kohana_Exception
850 * @param string $name name of the cache
851 * @param mixed $data data to cache
852 * @param integer $lifetime number of seconds the cache is valid for
853 * @return mixed for getting
854 * @return boolean for setting
856 public static function cache($name, $data = NULL, $lifetime = NULL)
858 // Cache file is a hash of the name
859 $file = sha1($name).'.txt';
861 // Cache directories are split by keys to prevent filesystem overload
862 $dir = Kohana
::$cache_dir.DIRECTORY_SEPARATOR
.$file[0].$file[1].DIRECTORY_SEPARATOR
;
864 if ($lifetime === NULL)
866 // Use the default lifetime
867 $lifetime = Kohana
::$cache_life;
872 if (is_file($dir.$file))
874 if ((time() - filemtime($dir.$file)) < $lifetime)
879 return unserialize(file_get_contents($dir.$file));
883 // Cache is corrupt, let return happen normally.
895 // Cache has mostly likely already been deleted,
896 // let return happen normally.
907 // Create the cache directory
908 mkdir($dir, 0777, TRUE);
910 // Set permissions (must be manually set to fix umask issues)
914 // Force the data to be a string
915 $data = serialize($data);
920 return (bool) file_put_contents($dir.$file, $data, LOCK_EX
);
924 // Failed to write cache
930 * Get a message from a file. Messages are arbitary strings that are stored
931 * in the `messages/` directory and reference by a key. Translation is not
932 * performed on the returned values. See [message files](kohana/files/messages)
933 * for more information.
935 * // Get "username" from messages/text.php
936 * $username = Kohana::message('text', 'username');
938 * @param string $file file name
939 * @param string $path key path to get
940 * @param mixed $default default value if the path does not exist
941 * @return string message string for the given path
942 * @return array complete message list, when no path is specified
946 public static function message($file, $path = NULL, $default = NULL)
950 if ( ! isset($messages[$file]))
952 // Create a new message list
953 $messages[$file] = array();
955 if ($files = Kohana
::find_file('messages', $file))
957 foreach ($files as $f)
959 // Combine all the messages recursively
960 $messages[$file] = Arr
::merge($messages[$file], Kohana
::load($f));
967 // Return all of the messages
968 return $messages[$file];
972 // Get a message using the path
973 return Arr
::path($messages[$file], $path, $default);
978 * PHP error handler, converts all errors into ErrorExceptions. This handler
979 * respects error_reporting settings.
981 * @throws ErrorException
984 public static function error_handler($code, $error, $file = NULL, $line = NULL)
986 if (error_reporting() & $code)
988 // This error is not suppressed by current error reporting settings
989 // Convert the error into an ErrorException
990 throw new ErrorException($error, $code, 0, $file, $line);
993 // Do not execute the PHP error handler
998 * Catches errors that are not caught by the error handler, such as E_PARSE.
1000 * @uses Kohana_Exception::handler
1003 public static function shutdown_handler()
1005 if ( ! Kohana
::$_init)
1007 // Do not execute when not active
1013 if (Kohana
::$caching === TRUE AND Kohana
::$_files_changed === TRUE)
1015 // Write the file path cache
1016 Kohana
::cache('Kohana::find_file()', Kohana
::$_files);
1019 catch (Exception
$e)
1021 // Pass the exception to the handler
1022 Kohana_Exception
::handler($e);
1025 if (Kohana
::$errors AND $error = error_get_last() AND in_array($error['type'], Kohana
::$shutdown_errors))
1027 // Clean the output buffer
1028 ob_get_level() AND ob_clean();
1030 // Fake an exception for nice debugging
1031 Kohana_Exception
::handler(new ErrorException($error['message'], $error['type'], 0, $error['file'], $error['line']));
1033 // Shutdown now to avoid a "death loop"
1039 * Generates a version string based on the variables defined above.
1043 public static function version()
1045 return 'Kohana Framework '.Kohana
::VERSION
.' ('.Kohana
::CODENAME
.')';