PageRenderTime 1108ms CodeModel.GetById 131ms app.highlight 551ms RepoModel.GetById 360ms app.codeStats 0ms

/system/classes/Kohana/Core.php

https://bitbucket.org/chrispiechowicz/zepto
PHP | 1048 lines | 539 code | 116 blank | 393 comment | 49 complexity | da8c804e6bd0fd9fcd939202037ec179 MD5 | raw file
   1<?php defined('SYSPATH') OR die('No direct script access.');
   2/**
   3 * Contains the most low-level helpers methods in Kohana:
   4 *
   5 * - Environment initialization
   6 * - Locating files within the cascading filesystem
   7 * - Auto-loading and transparent extension of classes
   8 * - Variable and path debugging
   9 *
  10 * @package    Kohana
  11 * @category   Base
  12 * @author     Kohana Team
  13 * @copyright  (c) 2008-2012 Kohana Team
  14 * @license    http://kohanaframework.org/license
  15 */
  16class Kohana_Core {
  17
  18	// Release version and codename
  19	const VERSION  = '3.3.0';
  20	const CODENAME = 'badius';
  21
  22	// Common environment type constants for consistency and convenience
  23	const PRODUCTION  = 10;
  24	const STAGING     = 20;
  25	const TESTING     = 30;
  26	const DEVELOPMENT = 40;
  27
  28	// Security check that is added to all generated PHP files
  29	const FILE_SECURITY = '<?php defined(\'SYSPATH\') OR die(\'No direct script access.\');';
  30
  31	// Format of cache files: header, cache name, and data
  32	const FILE_CACHE = ":header \n\n// :name\n\n:data\n";
  33
  34	/**
  35	 * @var  string  Current environment name
  36	 */
  37	public static $environment = Kohana::DEVELOPMENT;
  38
  39	/**
  40	 * @var  boolean  True if Kohana is running on windows
  41	 */
  42	public static $is_windows = FALSE;
  43
  44	/**
  45	 * @var  boolean  True if [magic quotes](http://php.net/manual/en/security.magicquotes.php) is enabled.
  46	 */
  47	public static $magic_quotes = FALSE;
  48
  49	/**
  50	 * @var  boolean  TRUE if PHP safe mode is on
  51	 */
  52	public static $safe_mode = FALSE;
  53
  54	/**
  55	 * @var  string
  56	 */
  57	public static $content_type = 'text/html';
  58
  59	/**
  60	 * @var  string  character set of input and output
  61	 */
  62	public static $charset = 'utf-8';
  63
  64	/**
  65	 * @var  string  the name of the server Kohana is hosted upon
  66	 */
  67	public static $server_name = '';
  68
  69	/**
  70	 * @var  array   list of valid host names for this instance
  71	 */
  72	public static $hostnames = array();
  73
  74	/**
  75	 * @var  string  base URL to the application
  76	 */
  77	public static $base_url = '/';
  78
  79	/**
  80	 * @var  string  Application index file, added to links generated by Kohana. Set by [Kohana::init]
  81	 */
  82	public static $index_file = 'index.php';
  83
  84	/**
  85	 * @var  string  Cache directory, used by [Kohana::cache]. Set by [Kohana::init]
  86	 */
  87	public static $cache_dir;
  88
  89	/**
  90	 * @var  integer  Default lifetime for caching, in seconds, used by [Kohana::cache]. Set by [Kohana::init]
  91	 */
  92	public static $cache_life = 60;
  93
  94	/**
  95	 * @var  boolean  Whether to use internal caching for [Kohana::find_file], does not apply to [Kohana::cache]. Set by [Kohana::init]
  96	 */
  97	public static $caching = FALSE;
  98
  99	/**
 100	 * @var  boolean  Whether to enable [profiling](kohana/profiling). Set by [Kohana::init]
 101	 */
 102	public static $profiling = TRUE;
 103
 104	/**
 105	 * @var  boolean  Enable Kohana catching and displaying PHP errors and exceptions. Set by [Kohana::init]
 106	 */
 107	public static $errors = TRUE;
 108
 109	/**
 110	 * @var  array  Types of errors to display at shutdown
 111	 */
 112	public static $shutdown_errors = array(E_PARSE, E_ERROR, E_USER_ERROR);
 113
 114	/**
 115	 * @var  boolean  set the X-Powered-By header
 116	 */
 117	public static $expose = FALSE;
 118
 119	/**
 120	 * @var  Log  logging object
 121	 */
 122	public static $log;
 123
 124	/**
 125	 * @var  Config  config object
 126	 */
 127	public static $config;
 128
 129	/**
 130	 * @var  boolean  Has [Kohana::init] been called?
 131	 */
 132	protected static $_init = FALSE;
 133
 134	/**
 135	 * @var  array   Currently active modules
 136	 */
 137	protected static $_modules = array();
 138
 139	/**
 140	 * @var  array   Include paths that are used to find files
 141	 */
 142	protected static $_paths = array(APPPATH, SYSPATH);
 143
 144	/**
 145	 * @var  array   File path cache, used when caching is true in [Kohana::init]
 146	 */
 147	protected static $_files = array();
 148
 149	/**
 150	 * @var  boolean  Has the file path cache changed during this execution?  Used internally when when caching is true in [Kohana::init]
 151	 */
 152	protected static $_files_changed = FALSE;
 153
 154	/**
 155	 * Initializes the environment:
 156	 *
 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
 162	 *
 163	 * The following settings can be set:
 164	 *
 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
 176	 *
 177	 * @throws  Kohana_Exception
 178	 * @param   array   $settings   Array of settings.  See above.
 179	 * @return  void
 180	 * @uses    Kohana::globals
 181	 * @uses    Kohana::sanitize
 182	 * @uses    Kohana::cache
 183	 * @uses    Profiler
 184	 */
 185	public static function init(array $settings = NULL)
 186	{
 187		if (Kohana::$_init)
 188		{
 189			// Do not allow execution twice
 190			return;
 191		}
 192
 193		// Kohana is now initialized
 194		Kohana::$_init = TRUE;
 195
 196		if (isset($settings['profile']))
 197		{
 198			// Enable profiling
 199			Kohana::$profiling = (bool) $settings['profile'];
 200		}
 201
 202		// Start an output buffer
 203		ob_start();
 204
 205		if (isset($settings['errors']))
 206		{
 207			// Enable error handling
 208			Kohana::$errors = (bool) $settings['errors'];
 209		}
 210
 211		if (Kohana::$errors === TRUE)
 212		{
 213			// Enable Kohana exception handling, adds stack traces and error source.
 214			set_exception_handler(array('Kohana_Exception', 'handler'));
 215
 216			// Enable Kohana error handling, converts all PHP errors to exceptions.
 217			set_error_handler(array('Kohana', 'error_handler'));
 218		}
 219
 220		/**
 221		 * Enable xdebug parameter collection in development mode to improve fatal stack traces.
 222		 */
 223		if (Kohana::$environment == Kohana::DEVELOPMENT AND extension_loaded('xdebug'))
 224		{
 225		    ini_set('xdebug.collect_params', 3);
 226		}
 227
 228		// Enable the Kohana shutdown handler, which catches E_FATAL errors.
 229		register_shutdown_function(array('Kohana', 'shutdown_handler'));
 230
 231		if (ini_get('register_globals'))
 232		{
 233			// Reverse the effects of register_globals
 234			Kohana::globals();
 235		}
 236
 237		if (isset($settings['expose']))
 238		{
 239			Kohana::$expose = (bool) $settings['expose'];
 240		}
 241
 242		// Determine if we are running in a Windows environment
 243		Kohana::$is_windows = (DIRECTORY_SEPARATOR === '\\');
 244
 245		// Determine if we are running in safe mode
 246		Kohana::$safe_mode = (bool) ini_get('safe_mode');
 247
 248		if (isset($settings['cache_dir']))
 249		{
 250			if ( ! is_dir($settings['cache_dir']))
 251			{
 252				try
 253				{
 254					// Create the cache directory
 255					mkdir($settings['cache_dir'], 0755, TRUE);
 256
 257					// Set permissions (must be manually set to fix umask issues)
 258					chmod($settings['cache_dir'], 0755);
 259				}
 260				catch (Exception $e)
 261				{
 262					throw new Kohana_Exception('Could not create cache directory :dir',
 263						array(':dir' => Debug::path($settings['cache_dir'])));
 264				}
 265			}
 266
 267			// Set the cache directory path
 268			Kohana::$cache_dir = realpath($settings['cache_dir']);
 269		}
 270		else
 271		{
 272			// Use the default cache directory
 273			Kohana::$cache_dir = APPPATH.'cache';
 274		}
 275
 276		if ( ! is_writable(Kohana::$cache_dir))
 277		{
 278			throw new Kohana_Exception('Directory :dir must be writable',
 279				array(':dir' => Debug::path(Kohana::$cache_dir)));
 280		}
 281
 282		if (isset($settings['cache_life']))
 283		{
 284			// Set the default cache lifetime
 285			Kohana::$cache_life = (int) $settings['cache_life'];
 286		}
 287
 288		if (isset($settings['caching']))
 289		{
 290			// Enable or disable internal caching
 291			Kohana::$caching = (bool) $settings['caching'];
 292		}
 293
 294		if (Kohana::$caching === TRUE)
 295		{
 296			// Load the file path cache
 297			Kohana::$_files = Kohana::cache('Kohana::find_file()');
 298		}
 299
 300		if (isset($settings['charset']))
 301		{
 302			// Set the system character set
 303			Kohana::$charset = strtolower($settings['charset']);
 304		}
 305
 306		if (function_exists('mb_internal_encoding'))
 307		{
 308			// Set the MB extension encoding to the same character set
 309			mb_internal_encoding(Kohana::$charset);
 310		}
 311
 312		if (isset($settings['base_url']))
 313		{
 314			// Set the base URL
 315			Kohana::$base_url = rtrim($settings['base_url'], '/').'/';
 316		}
 317
 318		if (isset($settings['index_file']))
 319		{
 320			// Set the index file
 321			Kohana::$index_file = trim($settings['index_file'], '/');
 322		}
 323
 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());
 326
 327		// Sanitize all request variables
 328		$_GET    = Kohana::sanitize($_GET);
 329		$_POST   = Kohana::sanitize($_POST);
 330		$_COOKIE = Kohana::sanitize($_COOKIE);
 331
 332		// Load the logger if one doesn't already exist
 333		if ( ! Kohana::$log instanceof Log)
 334		{
 335			Kohana::$log = Log::instance();
 336		}
 337
 338		// Load the config if one doesn't already exist
 339		if ( ! Kohana::$config instanceof Config)
 340		{
 341			Kohana::$config = new Config;
 342		}
 343	}
 344
 345	/**
 346	 * Cleans up the environment:
 347	 *
 348	 * - Restore the previous error and exception handlers
 349	 * - Destroy the Kohana::$log and Kohana::$config objects
 350	 *
 351	 * @return  void
 352	 */
 353	public static function deinit()
 354	{
 355		if (Kohana::$_init)
 356		{
 357			// Removed the autoloader
 358			spl_autoload_unregister(array('Kohana', 'auto_load'));
 359
 360			if (Kohana::$errors)
 361			{
 362				// Go back to the previous error handler
 363				restore_error_handler();
 364
 365				// Go back to the previous exception handler
 366				restore_exception_handler();
 367			}
 368
 369			// Destroy objects created by init
 370			Kohana::$log = Kohana::$config = NULL;
 371
 372			// Reset internal storage
 373			Kohana::$_modules = Kohana::$_files = array();
 374			Kohana::$_paths   = array(APPPATH, SYSPATH);
 375
 376			// Reset file cache status
 377			Kohana::$_files_changed = FALSE;
 378
 379			// Kohana is no longer initialized
 380			Kohana::$_init = FALSE;
 381		}
 382	}
 383
 384	/**
 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]
 388	 *
 389	 * This is called automatically by [Kohana::init] if `register_globals` is
 390	 * on.
 391	 *
 392	 *
 393	 * [ref-wikibooks]: http://en.wikibooks.org/wiki/PHP_Programming/Register_Globals
 394	 *
 395	 * @return  void
 396	 */
 397	public static function globals()
 398	{
 399		if (isset($_REQUEST['GLOBALS']) OR isset($_FILES['GLOBALS']))
 400		{
 401			// Prevent malicious GLOBALS overload attack
 402			echo "Global variable overload attack detected! Request aborted.\n";
 403
 404			// Exit with an error status
 405			exit(1);
 406		}
 407
 408		// Get the variable names of all globals
 409		$global_variables = array_keys($GLOBALS);
 410
 411		// Remove the standard global variables from the list
 412		$global_variables = array_diff($global_variables, array(
 413			'_COOKIE',
 414			'_ENV',
 415			'_GET',
 416			'_FILES',
 417			'_POST',
 418			'_REQUEST',
 419			'_SERVER',
 420			'_SESSION',
 421			'GLOBALS',
 422		));
 423
 424		foreach ($global_variables as $name)
 425		{
 426			// Unset the global variable, effectively disabling register_globals
 427			unset($GLOBALS[$name]);
 428		}
 429	}
 430
 431	/**
 432	 * Recursively sanitizes an input variable:
 433	 *
 434	 * - Strips slashes if magic quotes are enabled
 435	 * - Normalizes all newlines to LF
 436	 *
 437	 * @param   mixed   $value  any variable
 438	 * @return  mixed   sanitized variable
 439	 */
 440	public static function sanitize($value)
 441	{
 442		if (is_array($value) OR is_object($value))
 443		{
 444			foreach ($value as $key => $val)
 445			{
 446				// Recursively clean each value
 447				$value[$key] = Kohana::sanitize($val);
 448			}
 449		}
 450		elseif (is_string($value))
 451		{
 452			if (Kohana::$magic_quotes === TRUE)
 453			{
 454				// Remove slashes added by magic quotes
 455				$value = stripslashes($value);
 456			}
 457
 458			if (strpos($value, "\r") !== FALSE)
 459			{
 460				// Standardize newlines
 461				$value = str_replace(array("\r\n", "\r"), "\n", $value);
 462			}
 463		}
 464
 465		return $value;
 466	}
 467
 468	/**
 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.
 472	 *
 473	 *     // Loads classes/My/Class/Name.php
 474	 *     Kohana::auto_load('My_Class_Name');
 475	 *
 476	 * or with a custom directory:
 477	 *
 478	 *     // Loads vendor/My/Class/Name.php
 479	 *     Kohana::auto_load('My_Class_Name', 'vendor');
 480	 *
 481	 * You should never have to call this function, as simply calling a class
 482	 * will cause it to be called.
 483	 *
 484	 * This function must be enabled as an autoloader in the bootstrap:
 485	 *
 486	 *     spl_autoload_register(array('Kohana', 'auto_load'));
 487	 *
 488	 * @param   string  $class      Class name
 489	 * @param   string  $directory  Directory to load from
 490	 * @return  boolean
 491	 */
 492	public static function auto_load($class, $directory = 'classes')
 493	{
 494		// Transform the class name according to PSR-0
 495		$class     = ltrim($class, '\\');
 496		$file      = '';
 497		$namespace = '';
 498
 499		if ($last_namespace_position = strripos($class, '\\'))
 500		{
 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;
 504		}
 505
 506		$file .= str_replace('_', DIRECTORY_SEPARATOR, $class);
 507
 508		if ($path = Kohana::find_file($directory, $file))
 509		{
 510			// Load the class file
 511			require $path;
 512
 513			// Class has been found
 514			return TRUE;
 515		}
 516
 517		// Class is not in the filesystem
 518		return FALSE;
 519	}
 520
 521	/**
 522	 * Provides auto-loading support of classes that follow Kohana's old class
 523	 * naming conventions.
 524	 * 
 525	 * This is included for compatibility purposes with older modules.
 526	 *
 527	 * @param   string  $class      Class name
 528	 * @param   string  $directory  Directory to load from
 529	 * @return  boolean
 530	 */
 531	public static function auto_load_lowercase($class, $directory = 'classes')
 532	{
 533		// Transform the class name into a path
 534		$file = str_replace('_', DIRECTORY_SEPARATOR, strtolower($class));
 535
 536		if ($path = Kohana::find_file($directory, $file))
 537		{
 538			// Load the class file
 539			require $path;
 540
 541			// Class has been found
 542			return TRUE;
 543		}
 544
 545		// Class is not in the filesystem
 546		return FALSE;
 547	}
 548
 549	/**
 550	 * Changes the currently enabled modules. Module paths may be relative
 551	 * or absolute, but must point to a directory:
 552	 *
 553	 *     Kohana::modules(array('modules/foo', MODPATH.'bar'));
 554	 *
 555	 * @param   array   $modules    list of module paths
 556	 * @return  array   enabled modules
 557	 */
 558	public static function modules(array $modules = NULL)
 559	{
 560		if ($modules === NULL)
 561		{
 562			// Not changing modules, just return the current set
 563			return Kohana::$_modules;
 564		}
 565
 566		// Start a new list of include paths, APPPATH first
 567		$paths = array(APPPATH);
 568
 569		foreach ($modules as $name => $path)
 570		{
 571			if (is_dir($path))
 572			{
 573				// Add the module to include paths
 574				$paths[] = $modules[$name] = realpath($path).DIRECTORY_SEPARATOR;
 575			}
 576			else
 577			{
 578				// This module is invalid, remove it
 579				throw new Kohana_Exception('Attempted to load an invalid or missing module \':module\' at \':path\'', array(
 580					':module' => $name,
 581					':path'   => Debug::path($path),
 582				));
 583			}
 584		}
 585
 586		// Finish the include paths by adding SYSPATH
 587		$paths[] = SYSPATH;
 588
 589		// Set the new include paths
 590		Kohana::$_paths = $paths;
 591
 592		// Set the current module list
 593		Kohana::$_modules = $modules;
 594
 595		foreach (Kohana::$_modules as $path)
 596		{
 597			$init = $path.'init'.EXT;
 598
 599			if (is_file($init))
 600			{
 601				// Include the module initialization file once
 602				require_once $init;
 603			}
 604		}
 605
 606		return Kohana::$_modules;
 607	}
 608
 609	/**
 610	 * Returns the the currently active include paths, including the
 611	 * application, system, and each module's path.
 612	 *
 613	 * @return  array
 614	 */
 615	public static function include_paths()
 616	{
 617		return Kohana::$_paths;
 618	}
 619
 620	/**
 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
 623	 * can be included.
 624	 *
 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.
 629	 *
 630	 * If no extension is given, the default extension (`EXT` set in
 631	 * `index.php`) will be used.
 632	 *
 633	 *     // Returns an absolute path to views/template.php
 634	 *     Kohana::find_file('views', 'template');
 635	 *
 636	 *     // Returns an absolute path to media/css/style.css
 637	 *     Kohana::find_file('media', 'css/style', 'css');
 638	 *
 639	 *     // Returns an array of all the "mimes" configuration files
 640	 *     Kohana::find_file('config', 'mimes');
 641	 *
 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
 648	 */
 649	public static function find_file($dir, $file, $ext = NULL, $array = FALSE)
 650	{
 651		if ($ext === NULL)
 652		{
 653			// Use the default extension
 654			$ext = EXT;
 655		}
 656		elseif ($ext)
 657		{
 658			// Prefix the extension with a period
 659			$ext = ".{$ext}";
 660		}
 661		else
 662		{
 663			// Use no extension
 664			$ext = '';
 665		}
 666
 667		// Create a partial path of the filename
 668		$path = $dir.DIRECTORY_SEPARATOR.$file.$ext;
 669
 670		if (Kohana::$caching === TRUE AND isset(Kohana::$_files[$path.($array ? '_array' : '_path')]))
 671		{
 672			// This path has been cached
 673			return Kohana::$_files[$path.($array ? '_array' : '_path')];
 674		}
 675
 676		if (Kohana::$profiling === TRUE AND class_exists('Profiler', FALSE))
 677		{
 678			// Start a new benchmark
 679			$benchmark = Profiler::start('Kohana', __FUNCTION__);
 680		}
 681
 682		if ($array OR $dir === 'config' OR $dir === 'i18n' OR $dir === 'messages')
 683		{
 684			// Include paths must be searched in reverse
 685			$paths = array_reverse(Kohana::$_paths);
 686
 687			// Array of files that have been found
 688			$found = array();
 689
 690			foreach ($paths as $dir)
 691			{
 692				if (is_file($dir.$path))
 693				{
 694					// This path has a file, add it to the list
 695					$found[] = $dir.$path;
 696				}
 697			}
 698		}
 699		else
 700		{
 701			// The file has not been found yet
 702			$found = FALSE;
 703
 704			foreach (Kohana::$_paths as $dir)
 705			{
 706				if (is_file($dir.$path))
 707				{
 708					// A path has been found
 709					$found = $dir.$path;
 710
 711					// Stop searching
 712					break;
 713				}
 714			}
 715		}
 716
 717		if (Kohana::$caching === TRUE)
 718		{
 719			// Add the path to the cache
 720			Kohana::$_files[$path.($array ? '_array' : '_path')] = $found;
 721
 722			// Files have been changed
 723			Kohana::$_files_changed = TRUE;
 724		}
 725
 726		if (isset($benchmark))
 727		{
 728			// Stop the benchmark
 729			Profiler::stop($benchmark);
 730		}
 731
 732		return $found;
 733	}
 734
 735	/**
 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.
 739	 *
 740	 *     // Find all view files.
 741	 *     $views = Kohana::list_files('views');
 742	 *
 743	 * @param   string  $directory  directory name
 744	 * @param   array   $paths      list of paths to search
 745	 * @return  array
 746	 */
 747	public static function list_files($directory = NULL, array $paths = NULL)
 748	{
 749		if ($directory !== NULL)
 750		{
 751			// Add the directory separator
 752			$directory .= DIRECTORY_SEPARATOR;
 753		}
 754
 755		if ($paths === NULL)
 756		{
 757			// Use the default paths
 758			$paths = Kohana::$_paths;
 759		}
 760
 761		// Create an array for the files
 762		$found = array();
 763
 764		foreach ($paths as $path)
 765		{
 766			if (is_dir($path.$directory))
 767			{
 768				// Create a new directory iterator
 769				$dir = new DirectoryIterator($path.$directory);
 770
 771				foreach ($dir as $file)
 772				{
 773					// Get the file name
 774					$filename = $file->getFilename();
 775
 776					if ($filename[0] === '.' OR $filename[strlen($filename)-1] === '~')
 777					{
 778						// Skip all hidden files and UNIX backup files
 779						continue;
 780					}
 781
 782					// Relative filename is the array key
 783					$key = $directory.$filename;
 784
 785					if ($file->isDir())
 786					{
 787						if ($sub_dir = Kohana::list_files($key, $paths))
 788						{
 789							if (isset($found[$key]))
 790							{
 791								// Append the sub-directory list
 792								$found[$key] += $sub_dir;
 793							}
 794							else
 795							{
 796								// Create a new sub-directory list
 797								$found[$key] = $sub_dir;
 798							}
 799						}
 800					}
 801					else
 802					{
 803						if ( ! isset($found[$key]))
 804						{
 805							// Add new files to the list
 806							$found[$key] = realpath($file->getPathName());
 807						}
 808					}
 809				}
 810			}
 811		}
 812
 813		// Sort the results alphabetically
 814		ksort($found);
 815
 816		return $found;
 817	}
 818
 819	/**
 820	 * Loads a file within a totally empty scope and returns the output:
 821	 *
 822	 *     $foo = Kohana::load('foo.php');
 823	 *
 824	 * @param   string  $file
 825	 * @return  mixed
 826	 */
 827	public static function load($file)
 828	{
 829		return include $file;
 830	}
 831
 832	/**
 833	 * Provides simple file-based caching for strings and arrays:
 834	 *
 835	 *     // Set the "foo" cache
 836	 *     Kohana::cache('foo', 'hello, world');
 837	 *
 838	 *     // Get the "foo" cache
 839	 *     $foo = Kohana::cache('foo');
 840	 *
 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.
 844	 *
 845	 * The cache directory and default cache lifetime is set by [Kohana::init]
 846	 *
 847	 * [ref-var]: http://php.net/var_export
 848	 *
 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
 855	 */
 856	public static function cache($name, $data = NULL, $lifetime = NULL)
 857	{
 858		// Cache file is a hash of the name
 859		$file = sha1($name).'.txt';
 860
 861		// Cache directories are split by keys to prevent filesystem overload
 862		$dir = Kohana::$cache_dir.DIRECTORY_SEPARATOR.$file[0].$file[1].DIRECTORY_SEPARATOR;
 863
 864		if ($lifetime === NULL)
 865		{
 866			// Use the default lifetime
 867			$lifetime = Kohana::$cache_life;
 868		}
 869
 870		if ($data === NULL)
 871		{
 872			if (is_file($dir.$file))
 873			{
 874				if ((time() - filemtime($dir.$file)) < $lifetime)
 875				{
 876					// Return the cache
 877					try
 878					{
 879						return unserialize(file_get_contents($dir.$file));
 880					}
 881					catch (Exception $e)
 882					{
 883						// Cache is corrupt, let return happen normally.
 884					}
 885				}
 886				else
 887				{
 888					try
 889					{
 890						// Cache has expired
 891						unlink($dir.$file);
 892					}
 893					catch (Exception $e)
 894					{
 895						// Cache has mostly likely already been deleted,
 896						// let return happen normally.
 897					}
 898				}
 899			}
 900
 901			// Cache not found
 902			return NULL;
 903		}
 904
 905		if ( ! is_dir($dir))
 906		{
 907			// Create the cache directory
 908			mkdir($dir, 0777, TRUE);
 909
 910			// Set permissions (must be manually set to fix umask issues)
 911			chmod($dir, 0777);
 912		}
 913
 914		// Force the data to be a string
 915		$data = serialize($data);
 916
 917		try
 918		{
 919			// Write the cache
 920			return (bool) file_put_contents($dir.$file, $data, LOCK_EX);
 921		}
 922		catch (Exception $e)
 923		{
 924			// Failed to write cache
 925			return FALSE;
 926		}
 927	}
 928
 929	/**
 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.
 934	 *
 935	 *     // Get "username" from messages/text.php
 936	 *     $username = Kohana::message('text', 'username');
 937	 *
 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
 943	 * @uses    Arr::merge
 944	 * @uses    Arr::path
 945	 */
 946	public static function message($file, $path = NULL, $default = NULL)
 947	{
 948		static $messages;
 949
 950		if ( ! isset($messages[$file]))
 951		{
 952			// Create a new message list
 953			$messages[$file] = array();
 954
 955			if ($files = Kohana::find_file('messages', $file))
 956			{
 957				foreach ($files as $f)
 958				{
 959					// Combine all the messages recursively
 960					$messages[$file] = Arr::merge($messages[$file], Kohana::load($f));
 961				}
 962			}
 963		}
 964
 965		if ($path === NULL)
 966		{
 967			// Return all of the messages
 968			return $messages[$file];
 969		}
 970		else
 971		{
 972			// Get a message using the path
 973			return Arr::path($messages[$file], $path, $default);
 974		}
 975	}
 976
 977	/**
 978	 * PHP error handler, converts all errors into ErrorExceptions. This handler
 979	 * respects error_reporting settings.
 980	 *
 981	 * @throws  ErrorException
 982	 * @return  TRUE
 983	 */
 984	public static function error_handler($code, $error, $file = NULL, $line = NULL)
 985	{
 986		if (error_reporting() & $code)
 987		{
 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);
 991		}
 992
 993		// Do not execute the PHP error handler
 994		return TRUE;
 995	}
 996
 997	/**
 998	 * Catches errors that are not caught by the error handler, such as E_PARSE.
 999	 *
1000	 * @uses    Kohana_Exception::handler
1001	 * @return  void
1002	 */
1003	public static function shutdown_handler()
1004	{
1005		if ( ! Kohana::$_init)
1006		{
1007			// Do not execute when not active
1008			return;
1009		}
1010
1011		try
1012		{
1013			if (Kohana::$caching === TRUE AND Kohana::$_files_changed === TRUE)
1014			{
1015				// Write the file path cache
1016				Kohana::cache('Kohana::find_file()', Kohana::$_files);
1017			}
1018		}
1019		catch (Exception $e)
1020		{
1021			// Pass the exception to the handler
1022			Kohana_Exception::handler($e);
1023		}
1024
1025		if (Kohana::$errors AND $error = error_get_last() AND in_array($error['type'], Kohana::$shutdown_errors))
1026		{
1027			// Clean the output buffer
1028			ob_get_level() AND ob_clean();
1029
1030			// Fake an exception for nice debugging
1031			Kohana_Exception::handler(new ErrorException($error['message'], $error['type'], 0, $error['file'], $error['line']));
1032
1033			// Shutdown now to avoid a "death loop"
1034			exit(1);
1035		}
1036	}
1037
1038	/**
1039	 * Generates a version string based on the variables defined above.
1040	 * 
1041	 * @return string
1042	 */
1043	public static function version()
1044	{
1045		return 'Kohana Framework '.Kohana::VERSION.' ('.Kohana::CODENAME.')';
1046	}
1047
1048} // End Kohana