PageRenderTime 905ms CodeModel.GetById 122ms app.highlight 670ms RepoModel.GetById 100ms app.codeStats 0ms

/system/classes/kohana/core.php

https://bitbucket.org/alvinpd/monsterninja
PHP | 1505 lines | 1352 code | 39 blank | 114 comment | 7 complexity | 4503eafec3779de0762dfde1f6adf7de 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-2009 Kohana Team
  14 * @license    http://kohanaphp.com/license
  15 */
  16class Kohana_Core {
  17
  18	// Release version and codename
  19	const VERSION  = '3.0.6';
  20	const CODENAME = 'sumar hiti';
  21
  22	// Log message types
  23	const ERROR = 'ERROR';
  24	const DEBUG = 'DEBUG';
  25	const INFO  = 'INFO';
  26
  27	// Common environment type constants for consistency and convenience
  28	const PRODUCTION  = 'production';
  29	const STAGING     = 'staging';
  30	const TESTING     = 'testing';
  31	const DEVELOPMENT = 'development';
  32
  33	// Security check that is added to all generated PHP files
  34	const FILE_SECURITY = '<?php defined(\'SYSPATH\') or die(\'No direct script access.\');';
  35
  36	// Format of cache files: header, cache name, and data
  37	const FILE_CACHE = ":header \n\n// :name\n\n:data\n";
  38
  39	/**
  40	 * @var  array  PHP error code => human readable name
  41	 */
  42	public static $php_errors = array(
  43		E_ERROR              => 'Fatal Error',
  44		E_USER_ERROR         => 'User Error',
  45		E_PARSE              => 'Parse Error',
  46		E_WARNING            => 'Warning',
  47		E_USER_WARNING       => 'User Warning',
  48		E_STRICT             => 'Strict',
  49		E_NOTICE             => 'Notice',
  50		E_RECOVERABLE_ERROR  => 'Recoverable Error',
  51	);
  52
  53	/**
  54	 * @var  string  current environment name
  55	 */
  56	public static $environment = Kohana::DEVELOPMENT;
  57
  58	/**
  59	 * @var  boolean  command line environment?
  60	 */
  61	public static $is_cli = FALSE;
  62
  63	/**
  64	 * @var  boolean  Windows environment?
  65	 */
  66	public static $is_windows = FALSE;
  67
  68	/**
  69	 * @var  boolean  magic quotes enabled?
  70	 */
  71	public static $magic_quotes = FALSE;
  72
  73	/**
  74	 * @var  boolean  log errors and exceptions?
  75	 */
  76	public static $log_errors = FALSE;
  77
  78	/**
  79	 * @var  string  character set of input and output
  80	 */
  81	public static $charset = 'utf-8';
  82
  83	/**
  84	 * @var  string  base URL to the application
  85	 */
  86	public static $base_url = '/';
  87
  88	/**
  89	 * @var  string  application index file
  90	 */
  91	public static $index_file = 'index.php';
  92
  93	/**
  94	 * @var  string  cache directory
  95	 */
  96	public static $cache_dir;
  97
  98	/**
  99	 * @var  boolean  enabling internal caching?
 100	 */
 101	public static $caching = FALSE;
 102
 103	/**
 104	 * @var  boolean  enable core profiling?
 105	 */
 106	public static $profiling = TRUE;
 107
 108	/**
 109	 * @var  boolean  enable error handling?
 110	 */
 111	public static $errors = TRUE;
 112
 113	/**
 114	 * @var  array  types of errors to display at shutdown
 115	 */
 116	public static $shutdown_errors = array(E_PARSE, E_ERROR, E_USER_ERROR, E_COMPILE_ERROR);
 117
 118	/**
 119	 * @var  object  logging object
 120	 */
 121	public static $log;
 122
 123	/**
 124	 * @var  object  config object
 125	 */
 126	public static $config;
 127
 128	// Is the environment initialized?
 129	protected static $_init = FALSE;
 130
 131	// Currently active modules
 132	protected static $_modules = array();
 133
 134	// Include paths that are used to find files
 135	protected static $_paths = array(APPPATH, SYSPATH);
 136
 137	// File path cache
 138	protected static $_files = array();
 139
 140	// Has the file cache changed?
 141	protected static $_files_changed = FALSE;
 142
 143	/**
 144	 * Initializes the environment:
 145	 *
 146	 * - Disables register_globals and magic_quotes_gpc
 147	 * - Determines the current environment
 148	 * - Set global settings
 149	 * - Sanitizes GET, POST, and COOKIE variables
 150	 * - Converts GET, POST, and COOKIE variables to the global character set
 151	 *
 152	 * Any of the global settings can be set here:
 153	 *
 154	 * Type      | Setting    | Description                                    | Default Value
 155	 * ----------|------------|------------------------------------------------|---------------
 156	 * `boolean` | errors     | use internal error and exception handling?     | `TRUE`
 157	 * `boolean` | profile    | do internal benchmarking?                      | `TRUE`
 158	 * `boolean` | caching    | cache the location of files between requests?  | `FALSE`
 159	 * `string`  | charset    | character set used for all input and output    | `"utf-8"`
 160	 * `string`  | base_url   | set the base URL for the application           | `"/"`
 161	 * `string`  | index_file | set the index.php file name                    | `"index.php"`
 162	 * `string`  | cache_dir  | set the cache directory path                   | `APPPATH."cache"`
 163	 *
 164	 * @throws  Kohana_Exception
 165	 * @param   array   global settings
 166	 * @return  void
 167	 * @uses    Kohana::globals
 168	 * @uses    Kohana::sanitize
 169	 * @uses    Kohana::cache
 170	 * @uses    Profiler
 171	 */
 172	public static function init(array $settings = NULL)
 173	{
 174		if (Kohana::$_init)
 175		{
 176			// Do not allow execution twice
 177			return;
 178		}
 179
 180		// Kohana is now initialized
 181		Kohana::$_init = TRUE;
 182
 183		if (isset($settings['profile']))
 184		{
 185			// Enable profiling
 186			Kohana::$profiling = (bool) $settings['profile'];
 187		}
 188
 189		if (Kohana::$profiling === TRUE)
 190		{
 191			// Start a new benchmark
 192			$benchmark = Profiler::start('Kohana', __FUNCTION__);
 193		}
 194
 195		// Start an output buffer
 196		ob_start();
 197
 198		if (defined('E_DEPRECATED'))
 199		{
 200			// E_DEPRECATED only exists in PHP >= 5.3.0
 201			Kohana::$php_errors[E_DEPRECATED] = 'Deprecated';
 202		}
 203
 204		if (isset($settings['errors']))
 205		{
 206			// Enable error handling
 207			Kohana::$errors = (bool) $settings['errors'];
 208		}
 209
 210		if (Kohana::$errors === TRUE)
 211		{
 212			// Enable Kohana exception handling, adds stack traces and error source.
 213			set_exception_handler(array('Kohana', 'exception_handler'));
 214
 215			// Enable Kohana error handling, converts all PHP errors to exceptions.
 216			set_error_handler(array('Kohana', 'error_handler'));
 217		}
 218
 219		// Enable the Kohana shutdown handler, which catches E_FATAL errors.
 220		register_shutdown_function(array('Kohana', 'shutdown_handler'));
 221
 222		if (ini_get('register_globals'))
 223		{
 224			// Reverse the effects of register_globals
 225			Kohana::globals();
 226		}
 227
 228		// Determine if we are running in a command line environment
 229		Kohana::$is_cli = (PHP_SAPI === 'cli');
 230
 231		// Determine if we are running in a Windows environment
 232		Kohana::$is_windows = (DIRECTORY_SEPARATOR === '\\');
 233
 234		if (isset($settings['cache_dir']))
 235		{
 236			// Set the cache directory path
 237			Kohana::$cache_dir = realpath($settings['cache_dir']);
 238		}
 239		else
 240		{
 241			// Use the default cache directory
 242			Kohana::$cache_dir = APPPATH.'cache';
 243		}
 244
 245		if ( ! is_writable(Kohana::$cache_dir))
 246		{
 247			throw new Kohana_Exception('Directory :dir must be writable',
 248				array(':dir' => Kohana::debug_path(Kohana::$cache_dir)));
 249		}
 250
 251		if (isset($settings['caching']))
 252		{
 253			// Enable or disable internal caching
 254			Kohana::$caching = (bool) $settings['caching'];
 255		}
 256
 257		if (Kohana::$caching === TRUE)
 258		{
 259			// Load the file path cache
 260			Kohana::$_files = Kohana::cache('Kohana::find_file()');
 261		}
 262
 263		if (isset($settings['charset']))
 264		{
 265			// Set the system character set
 266			Kohana::$charset = strtolower($settings['charset']);
 267		}
 268
 269		if (function_exists('mb_internal_encoding'))
 270		{
 271			// Set the MB extension encoding to the same character set
 272			mb_internal_encoding(Kohana::$charset);
 273		}
 274
 275		if (isset($settings['base_url']))
 276		{
 277			// Set the base URL
 278			Kohana::$base_url = rtrim($settings['base_url'], '/').'/';
 279		}
 280
 281		if (isset($settings['index_file']))
 282		{
 283			// Set the index file
 284			Kohana::$index_file = trim($settings['index_file'], '/');
 285		}
 286
 287		// Determine if the extremely evil magic quotes are enabled
 288		Kohana::$magic_quotes = (bool) get_magic_quotes_gpc();
 289
 290		// Sanitize all request variables
 291		$_GET    = Kohana::sanitize($_GET);
 292		$_POST   = Kohana::sanitize($_POST);
 293		$_COOKIE = Kohana::sanitize($_COOKIE);
 294
 295		// Load the logger
 296		Kohana::$log = Kohana_Log::instance();
 297
 298		// Load the config
 299		Kohana::$config = Kohana_Config::instance();
 300
 301		if (isset($benchmark))
 302		{
 303			// Stop benchmarking
 304			Profiler::stop($benchmark);
 305		}
 306	}
 307
 308	/**
 309	 * Cleans up the environment:
 310	 *
 311	 * - Restore the previous error and exception handlers
 312	 * - Destroy the Kohana::$log and Kohana::$config objects
 313	 *
 314	 * @return  void
 315	 */
 316	public static function deinit()
 317	{
 318		if (Kohana::$_init)
 319		{
 320			// Removed the autoloader
 321			spl_autoload_unregister(array('Kohana', 'auto_load'));
 322
 323			if (Kohana::$errors)
 324			{
 325				// Go back to the previous error handler
 326				restore_error_handler();
 327
 328				// Go back to the previous exception handler
 329				restore_exception_handler();
 330			}
 331
 332			// Destroy objects created by init
 333			Kohana::$log = Kohana::$config = NULL;
 334
 335			// Reset internal storage
 336			Kohana::$_modules = Kohana::$_files = array();
 337			Kohana::$_paths   = array(APPPATH, SYSPATH);
 338
 339			// Reset file cache status
 340			Kohana::$_files_changed = FALSE;
 341
 342			// Kohana is no longer initialized
 343			Kohana::$_init = FALSE;
 344		}
 345	}
 346
 347	/**
 348	 * Reverts the effects of the `register_globals` PHP setting by unsetting
 349	 * all global varibles except for the default super globals (GPCS, etc).
 350	 *
 351	 *     if (ini_get('register_globals'))
 352	 *     {
 353	 *         Kohana::globals();
 354	 *     }
 355	 *
 356	 * @return  void
 357	 */
 358	public static function globals()
 359	{
 360		if (isset($_REQUEST['GLOBALS']) OR isset($_FILES['GLOBALS']))
 361		{
 362			// Prevent malicious GLOBALS overload attack
 363			echo "Global variable overload attack detected! Request aborted.\n";
 364
 365			// Exit with an error status
 366			exit(1);
 367		}
 368
 369		// Get the variable names of all globals
 370		$global_variables = array_keys($GLOBALS);
 371
 372		// Remove the standard global variables from the list
 373		$global_variables = array_diff($global_variables,
 374			array('GLOBALS', '_REQUEST', '_GET', '_POST', '_FILES', '_COOKIE', '_SERVER', '_ENV', '_SESSION'));
 375
 376		foreach ($global_variables as $name)
 377		{
 378			// Retrieve the global variable and make it null
 379			global $$name;
 380			$$name = NULL;
 381
 382			// Unset the global variable, effectively disabling register_globals
 383			unset($GLOBALS[$name], $$name);
 384		}
 385	}
 386
 387	/**
 388	 * Recursively sanitizes an input variable:
 389	 *
 390	 * - Strips slashes if magic quotes are enabled
 391	 * - Normalizes all newlines to LF
 392	 *
 393	 * @param   mixed  any variable
 394	 * @return  mixed  sanitized variable
 395	 */
 396	public static function sanitize($value)
 397	{
 398		if (is_array($value) OR is_object($value))
 399		{
 400			foreach ($value as $key => $val)
 401			{
 402				// Recursively clean each value
 403				$value[$key] = Kohana::sanitize($val);
 404			}
 405		}
 406		elseif (is_string($value))
 407		{
 408			if (Kohana::$magic_quotes === TRUE)
 409			{
 410				// Remove slashes added by magic quotes
 411				$value = stripslashes($value);
 412			}
 413
 414			if (strpos($value, "\r") !== FALSE)
 415			{
 416				// Standardize newlines
 417				$value = str_replace(array("\r\n", "\r"), "\n", $value);
 418			}
 419		}
 420
 421		return $value;
 422	}
 423
 424	/**
 425	 * Provides auto-loading support of Kohana classes, as well as transparent
 426	 * extension of classes that have a _Core suffix.
 427	 *
 428	 * Class names are converted to file names by making the class name
 429	 * lowercase and converting underscores to slashes:
 430	 *
 431	 *     // Loads classes/my/class/name.php
 432	 *     Kohana::auto_load('My_Class_Name');
 433	 *
 434	 * @param   string   class name
 435	 * @return  boolean
 436	 */
 437	public static function auto_load($class)
 438	{
 439		// Transform the class name into a path
 440		$file = str_replace('_', '/', strtolower($class));
 441
 442		if ($path = Kohana::find_file('classes', $file))
 443		{
 444			// Load the class file
 445			require $path;
 446
 447			// Class has been found
 448			return TRUE;
 449		}
 450
 451		// Class is not in the filesystem
 452		return FALSE;
 453	}
 454
 455	/**
 456	 * Changes the currently enabled modules. Module paths may be relative
 457	 * or absolute, but must point to a directory:
 458	 *
 459	 *     Kohana::modules(array('modules/foo', MODPATH.'bar'));
 460	 *
 461	 * @param   array  list of module paths
 462	 * @return  array  enabled modules
 463	 */
 464	public static function modules(array $modules = NULL)
 465	{
 466		if ($modules === NULL)
 467			return Kohana::$_modules;
 468
 469		if (Kohana::$profiling === TRUE)
 470		{
 471			// Start a new benchmark
 472			$benchmark = Profiler::start('Kohana', __FUNCTION__);
 473		}
 474
 475		// Start a new list of include paths, APPPATH first
 476		$paths = array(APPPATH);
 477
 478		foreach ($modules as $name => $path)
 479		{
 480			if (is_dir($path))
 481			{
 482				// Add the module to include paths
 483				$paths[] = $modules[$name] = realpath($path).DIRECTORY_SEPARATOR;
 484			}
 485			else
 486			{
 487				// This module is invalid, remove it
 488				unset($modules[$name]);
 489			}
 490		}
 491
 492		// Finish the include paths by adding SYSPATH
 493		$paths[] = SYSPATH;
 494
 495		// Set the new include paths
 496		Kohana::$_paths = $paths;
 497
 498		// Set the current module list
 499		Kohana::$_modules = $modules;
 500
 501		foreach (Kohana::$_modules as $path)
 502		{
 503			$init = $path.'init'.EXT;
 504
 505			if (is_file($init))
 506			{
 507				// Include the module initialization file once
 508				require_once $init;
 509			}
 510		}
 511
 512		if (isset($benchmark))
 513		{
 514			// Stop the benchmark
 515			Profiler::stop($benchmark);
 516		}
 517
 518		return Kohana::$_modules;
 519	}
 520
 521	/**
 522	 * Returns the the currently active include paths, including the
 523	 * application and system paths.
 524	 *
 525	 * @return  array
 526	 */
 527	public static function include_paths()
 528	{
 529		return Kohana::$_paths;
 530	}
 531
 532	/**
 533	 * Finds the path of a file by directory, filename, and extension.
 534	 * If no extension is given, the default EXT extension will be used.
 535	 *
 536	 * When searching the "config" or "i18n" directories, or when the
 537	 * $aggregate_files flag is set to true, an array of files
 538	 * will be returned. These files will return arrays which must be
 539	 * merged together.
 540	 *
 541	 *     // Returns an absolute path to views/template.php
 542	 *     Kohana::find_file('views', 'template');
 543	 *
 544	 *     // Returns an absolute path to media/css/style.css
 545	 *     Kohana::find_file('media', 'css/style', 'css');
 546	 *
 547	 *     // Returns an array of all the "mimes" configuration file
 548	 *     Kohana::find_file('config', 'mimes');
 549	 *
 550	 * @param   string   directory name (views, i18n, classes, extensions, etc.)
 551	 * @param   string   filename with subdirectory
 552	 * @param   string   extension to search for
 553	 * @param   boolean  return an array of files?
 554	 * @return  array    a list of files when $array is TRUE
 555	 * @return  string   single file path
 556	 */
 557	public static function find_file($dir, $file, $ext = NULL, $array = FALSE)
 558	{
 559		// Use the defined extension by default
 560		$ext = ($ext === NULL) ? EXT : '.'.$ext;
 561
 562		// Create a partial path of the filename
 563		$path = $dir.DIRECTORY_SEPARATOR.$file.$ext;
 564
 565		if (Kohana::$caching === TRUE AND isset(Kohana::$_files[$path]))
 566		{
 567			// This path has been cached
 568			return Kohana::$_files[$path];
 569		}
 570
 571		if (Kohana::$profiling === TRUE AND class_exists('Profiler', FALSE))
 572		{
 573			// Start a new benchmark
 574			$benchmark = Profiler::start('Kohana', __FUNCTION__);
 575		}
 576
 577		if ($array OR $dir === 'config' OR $dir === 'i18n' OR $dir === 'messages')
 578		{
 579			// Include paths must be searched in reverse
 580			$paths = array_reverse(Kohana::$_paths);
 581
 582			// Array of files that have been found
 583			$found = array();
 584
 585			foreach ($paths as $dir)
 586			{
 587				if (is_file($dir.$path))
 588				{
 589					// This path has a file, add it to the list
 590					$found[] = $dir.$path;
 591				}
 592			}
 593		}
 594		else
 595		{
 596			// The file has not been found yet
 597			$found = FALSE;
 598
 599			foreach (Kohana::$_paths as $dir)
 600			{
 601				if (is_file($dir.$path))
 602				{
 603					// A path has been found
 604					$found = $dir.$path;
 605
 606					// Stop searching
 607					break;
 608				}
 609			}
 610		}
 611
 612		if (Kohana::$caching === TRUE)
 613		{
 614			// Add the path to the cache
 615			Kohana::$_files[$path] = $found;
 616
 617			// Files have been changed
 618			Kohana::$_files_changed = TRUE;
 619		}
 620
 621		if (isset($benchmark))
 622		{
 623			// Stop the benchmark
 624			Profiler::stop($benchmark);
 625		}
 626
 627		return $found;
 628	}
 629
 630	/**
 631	 * Recursively finds all of the files in the specified directory.
 632	 *
 633	 *     $views = Kohana::list_files('views');
 634	 *
 635	 * @param   string  directory name
 636	 * @param   array   list of paths to search
 637	 * @return  array
 638	 */
 639	public static function list_files($directory = NULL, array $paths = NULL)
 640	{
 641		if ($directory !== NULL)
 642		{
 643			// Add the directory separator
 644			$directory .= DIRECTORY_SEPARATOR;
 645		}
 646
 647		if ($paths === NULL)
 648		{
 649			// Use the default paths
 650			$paths = Kohana::$_paths;
 651		}
 652
 653		// Create an array for the files
 654		$found = array();
 655
 656		foreach ($paths as $path)
 657		{
 658			if (is_dir($path.$directory))
 659			{
 660				// Create a new directory iterator
 661				$dir = new DirectoryIterator($path.$directory);
 662
 663				foreach ($dir as $file)
 664				{
 665					// Get the file name
 666					$filename = $file->getFilename();
 667
 668					if ($filename[0] === '.' OR $filename[strlen($filename)-1] === '~')
 669					{
 670						// Skip all hidden files and UNIX backup files
 671						continue;
 672					}
 673
 674					// Relative filename is the array key
 675					$key = $directory.$filename;
 676
 677					if ($file->isDir())
 678					{
 679						if ($sub_dir = Kohana::list_files($key, $paths))
 680						{
 681							if (isset($found[$key]))
 682							{
 683								// Append the sub-directory list
 684								$found[$key] += $sub_dir;
 685							}
 686							else
 687							{
 688								// Create a new sub-directory list
 689								$found[$key] = $sub_dir;
 690							}
 691						}
 692					}
 693					else
 694					{
 695						if ( ! isset($found[$key]))
 696						{
 697							// Add new files to the list
 698							$found[$key] = realpath($file->getPathName());
 699						}
 700					}
 701				}
 702			}
 703		}
 704
 705		// Sort the results alphabetically
 706		ksort($found);
 707
 708		return $found;
 709	}
 710
 711	/**
 712	 * Loads a file within a totally empty scope and returns the output:
 713	 *
 714	 *     $foo = Kohana::load('foo.php');
 715	 *
 716	 * @param   string
 717	 * @return  mixed
 718	 */
 719	public static function load($file)
 720	{
 721		return include $file;
 722	}
 723
 724	/**
 725	 * Creates a new configuration object for the requested group.
 726	 *
 727	 * @param   string   group name
 728	 * @return  Kohana_Config
 729	 */
 730	public static function config($group)
 731	{
 732		static $config;
 733
 734		if (strpos($group, '.') !== FALSE)
 735		{
 736			// Split the config group and path
 737			list ($group, $path) = explode('.', $group, 2);
 738		}
 739
 740		if ( ! isset($config[$group]))
 741		{
 742			// Load the config group into the cache
 743			$config[$group] = Kohana::$config->load($group);
 744		}
 745
 746		if (isset($path))
 747		{
 748			return Arr::path($config[$group], $path);
 749		}
 750		else
 751		{
 752			return $config[$group];
 753		}
 754	}
 755
 756	/**
 757	 * Provides simple file-based caching for strings and arrays:
 758	 *
 759	 *     // Set the "foo" cache
 760	 *     Kohana::cache('foo', 'hello, world');
 761	 *
 762	 *     // Get the "foo" cache
 763	 *     $foo = Kohana::cache('foo');
 764	 *
 765	 * All caches are stored as PHP code, generated with [var_export][ref-var].
 766	 * Caching objects may not work as expected. Storing references or an
 767	 * object or array that has recursion will cause an E_FATAL.
 768	 *
 769	 * [ref-var]: http://php.net/var_export
 770	 *
 771	 * @throws  Kohana_Exception
 772	 * @param   string   name of the cache
 773	 * @param   mixed    data to cache
 774	 * @param   integer  number of seconds the cache is valid for
 775	 * @return  mixed    for getting
 776	 * @return  boolean  for setting
 777	 */
 778	public static function cache($name, $data = NULL, $lifetime = 60)
 779	{
 780		// Cache file is a hash of the name
 781		$file = sha1($name).'.txt';
 782
 783		// Cache directories are split by keys to prevent filesystem overload
 784		$dir = Kohana::$cache_dir.DIRECTORY_SEPARATOR.$file[0].$file[1].DIRECTORY_SEPARATOR;
 785
 786		try
 787		{
 788			if ($data === NULL)
 789			{
 790				if (is_file($dir.$file))
 791				{
 792					if ((time() - filemtime($dir.$file)) < $lifetime)
 793					{
 794						// Return the cache
 795						return unserialize(file_get_contents($dir.$file));
 796					}
 797					else
 798					{
 799						try
 800						{
 801							// Cache has expired
 802							unlink($dir.$file);
 803						}
 804						catch (Exception $e)
 805						{
 806							// Cache has already been deleted
 807							return NULL;
 808						}
 809					}
 810				}
 811
 812				// Cache not found
 813				return NULL;
 814			}
 815
 816			if ( ! is_dir($dir))
 817			{
 818				// Create the cache directory
 819				mkdir($dir, 0777, TRUE);
 820
 821				// Set permissions (must be manually set to fix umask issues)
 822				chmod($dir, 0777);
 823			}
 824
 825			// Write the cache
 826			return (bool) file_put_contents($dir.$file, serialize($data));
 827		}
 828		catch (Exception $e)
 829		{
 830			throw $e;
 831		}
 832	}
 833
 834	/**
 835	 * Get a message from a file. Messages are arbitary strings that are stored
 836	 * in the messages/ directory and reference by a key. Translation is not
 837	 * performed on the returned values.
 838	 *
 839	 *     // Get "username" from messages/text.php
 840	 *     $username = Kohana::message('text', 'username');
 841	 *
 842	 * @param   string  file name
 843	 * @param   string  key path to get
 844	 * @param   mixed   default value if the path does not exist
 845	 * @return  string  message string for the given path
 846	 * @return  array   complete message list, when no path is specified
 847	 * @uses    Arr::merge
 848	 * @uses    Arr::path
 849	 */
 850	public static function message($file, $path = NULL, $default = NULL)
 851	{
 852		static $messages;
 853
 854		if ( ! isset($messages[$file]))
 855		{
 856			// Create a new message list
 857			$messages[$file] = array();
 858
 859			if ($files = Kohana::find_file('messages', $file))
 860			{
 861				foreach ($files as $f)
 862				{
 863					// Combine all the messages recursively
 864					$messages[$file] = Arr::merge($messages[$file], Kohana::load($f));
 865				}
 866			}
 867		}
 868
 869		if ($path === NULL)
 870		{
 871			// Return all of the messages
 872			return $messages[$file];
 873		}
 874		else
 875		{
 876			// Get a message using the path
 877			return Arr::path($messages[$file], $path, $default);
 878		}
 879	}
 880
 881	/**
 882	 * PHP error handler, converts all errors into ErrorExceptions. This handler
 883	 * respects error_reporting settings.
 884	 *
 885	 * @throws  ErrorException
 886	 * @return  TRUE
 887	 */
 888	public static function error_handler($code, $error, $file = NULL, $line = NULL)
 889	{
 890		if (error_reporting() & $code)
 891		{
 892			// This error is not suppressed by current error reporting settings
 893			// Convert the error into an ErrorException
 894			throw new ErrorException($error, $code, 0, $file, $line);
 895		}
 896
 897		// Do not execute the PHP error handler
 898		return TRUE;
 899	}
 900
 901	/**
 902	 * Inline exception handler, displays the error message, source of the
 903	 * exception, and the stack trace of the error.
 904	 *
 905	 * @uses    Kohana::exception_text
 906	 * @param   object   exception object
 907	 * @return  boolean
 908	 */
 909	public static function exception_handler(Exception $e)
 910	{
 911		try
 912		{
 913			// Get the exception information
 914			$type    = get_class($e);
 915			$code    = $e->getCode();
 916			$message = $e->getMessage();
 917			$file    = $e->getFile();
 918			$line    = $e->getLine();
 919
 920			// Create a text version of the exception
 921			$error = Kohana::exception_text($e);
 922
 923			if (is_object(Kohana::$log))
 924			{
 925				// Add this exception to the log
 926				Kohana::$log->add(Kohana::ERROR, $error);
 927
 928				// Make sure the logs are written
 929				Kohana::$log->write();
 930			}
 931
 932			if (Kohana::$is_cli)
 933			{
 934				// Just display the text of the exception
 935				echo "\n{$error}\n";
 936
 937				return TRUE;
 938			}
 939
 940			// Get the exception backtrace
 941			$trace = $e->getTrace();
 942
 943			if ($e instanceof ErrorException)
 944			{
 945				if (isset(Kohana::$php_errors[$code]))
 946				{
 947					// Use the human-readable error name
 948					$code = Kohana::$php_errors[$code];
 949				}
 950
 951				if (version_compare(PHP_VERSION, '5.3', '<'))
 952				{
 953					// Workaround for a bug in ErrorException::getTrace() that exists in
 954					// all PHP 5.2 versions. @see http://bugs.php.net/bug.php?id=45895
 955					for ($i = count($trace) - 1; $i > 0; --$i)
 956					{
 957						if (isset($trace[$i - 1]['args']))
 958						{
 959							// Re-position the args
 960							$trace[$i]['args'] = $trace[$i - 1]['args'];
 961
 962							// Remove the args
 963							unset($trace[$i - 1]['args']);
 964						}
 965					}
 966				}
 967			}
 968
 969			if ( ! headers_sent())
 970			{
 971				// Make sure the proper content type is sent with a 500 status
 972				header('Content-Type: text/html; charset='.Kohana::$charset, TRUE, 500);
 973			}
 974
 975			// Start an output buffer
 976			ob_start();
 977
 978			// Include the exception HTML
 979			include Kohana::find_file('views', 'kohana/error');
 980
 981			// Display the contents of the output buffer
 982			echo ob_get_clean();
 983
 984			return TRUE;
 985		}
 986		catch (Exception $e)
 987		{
 988			// Clean the output buffer if one exists
 989			ob_get_level() and ob_clean();
 990
 991			// Display the exception text
 992			echo Kohana::exception_text($e), "\n";
 993
 994			// Exit with an error status
 995			exit(1);
 996		}
 997	}
 998
 999	/**
1000	 * Catches errors that are not caught by the error handler, such as E_PARSE.
1001	 *
1002	 * @uses    Kohana::exception_handler
1003	 * @return  void
1004	 */
1005	public static function shutdown_handler()
1006	{
1007		if ( ! Kohana::$_init)
1008		{
1009			// Do not execute when not active
1010			return;
1011		}
1012
1013		try
1014		{
1015			if (Kohana::$caching === TRUE AND Kohana::$_files_changed === TRUE)
1016			{
1017				// Write the file path cache
1018				Kohana::cache('Kohana::find_file()', Kohana::$_files);
1019			}
1020		}
1021		catch (Exception $e)
1022		{
1023			// Pass the exception to the handler
1024			Kohana::exception_handler($e);
1025		}
1026
1027		if (Kohana::$errors AND $error = error_get_last() AND in_array($error['type'], Kohana::$shutdown_errors))
1028		{
1029			// Clean the output buffer
1030			ob_get_level() and ob_clean();
1031
1032			// Fake an exception for nice debugging
1033			Kohana::exception_handler(new ErrorException($error['message'], $error['type'], 0, $error['file'], $error['line']));
1034
1035			// Shutdown now to avoid a "death loop"
1036			exit(1);
1037		}
1038	}
1039
1040	/**
1041	 * Get a single line of text representing the exception:
1042	 *
1043	 * Error [ Code ]: Message ~ File [ Line ]
1044	 *
1045	 * @param   object  Exception
1046	 * @return  string
1047	 */
1048	public static function exception_text(Exception $e)
1049	{
1050		return sprintf('%s [ %s ]: %s ~ %s [ %d ]',
1051			get_class($e), $e->getCode(), strip_tags($e->getMessage()), Kohana::debug_path($e->getFile()), $e->getLine());
1052	}
1053
1054	/**
1055	 * Returns an HTML string of debugging information about any number of
1056	 * variables, each wrapped in a "pre" tag:
1057	 *
1058	 *     // Displays the type and value of each variable
1059	 *     echo Kohana::debug($foo, $bar, $baz);
1060	 *
1061	 * @param   mixed   variable to debug
1062	 * @param   ...
1063	 * @return  string
1064	 */
1065	public static function debug()
1066	{
1067		if (func_num_args() === 0)
1068			return;
1069
1070		// Get all passed variables
1071		$variables = func_get_args();
1072
1073		$output = array();
1074		foreach ($variables as $var)
1075		{
1076			$output[] = Kohana::_dump($var, 1024);
1077		}
1078
1079		return '<pre class="debug">'.implode("\n", $output).'</pre>';
1080	}
1081
1082	/**
1083	 * Returns an HTML string of information about a single variable.
1084	 *
1085	 * Borrows heavily on concepts from the Debug class of [Nette](http://nettephp.com/).
1086	 *
1087	 * @param   mixed    variable to dump
1088	 * @param   integer  maximum length of strings
1089	 * @return  string
1090	 */
1091	public static function dump($value, $length = 128)
1092	{
1093		return Kohana::_dump($value, $length);
1094	}
1095
1096	/**
1097	 * Helper for Kohana::dump(), handles recursion in arrays and objects.
1098	 *
1099	 * @param   mixed    variable to dump
1100	 * @param   integer  maximum length of strings
1101	 * @param   integer  recursion level (internal)
1102	 * @return  string
1103	 */
1104	protected static function _dump( & $var, $length = 128, $level = 0)
1105	{
1106		if ($var === NULL)
1107		{
1108			return '<small>NULL</small>';
1109		}
1110		elseif (is_bool($var))
1111		{
1112			return '<small>bool</small> '.($var ? 'TRUE' : 'FALSE');
1113		}
1114		elseif (is_float($var))
1115		{
1116			return '<small>float</small> '.$var;
1117		}
1118		elseif (is_resource($var))
1119		{
1120			if (($type = get_resource_type($var)) === 'stream' AND $meta = stream_get_meta_data($var))
1121			{
1122				$meta = stream_get_meta_data($var);
1123
1124				if (isset($meta['uri']))
1125				{
1126					$file = $meta['uri'];
1127
1128					if (function_exists('stream_is_local'))
1129					{
1130						// Only exists on PHP >= 5.2.4
1131						if (stream_is_local($file))
1132						{
1133							$file = Kohana::debug_path($file);
1134						}
1135					}
1136
1137					return '<small>resource</small><span>('.$type.')</span> '.htmlspecialchars($file, ENT_NOQUOTES, Kohana::$charset);
1138				}
1139			}
1140			else
1141			{
1142				return '<small>resource</small><span>('.$type.')</span>';
1143			}
1144		}
1145		elseif (is_string($var))
1146		{
1147			if (UTF8::strlen($var) > $length)
1148			{
1149				// Encode the truncated string
1150				$str = htmlspecialchars(UTF8::substr($var, 0, $length), ENT_NOQUOTES, Kohana::$charset).'&nbsp;&hellip;';
1151			}
1152			else
1153			{
1154				// Encode the string
1155				$str = htmlspecialchars($var, ENT_NOQUOTES, Kohana::$charset);
1156			}
1157
1158			return '<small>string</small><span>('.strlen($var).')</span> "'.$str.'"';
1159		}
1160		elseif (is_array($var))
1161		{
1162			$output = array();
1163
1164			// Indentation for this variable
1165			$space = str_repeat($s = '    ', $level);
1166
1167			static $marker;
1168
1169			if ($marker === NULL)
1170			{
1171				// Make a unique marker
1172				$marker = uniqid("\x00");
1173			}
1174
1175			if (empty($var))
1176			{
1177				// Do nothing
1178			}
1179			elseif (isset($var[$marker]))
1180			{
1181				$output[] = "(\n$space$s*RECURSION*\n$space)";
1182			}
1183			elseif ($level < 5)
1184			{
1185				$output[] = "<span>(";
1186
1187				$var[$marker] = TRUE;
1188				foreach ($var as $key => & $val)
1189				{
1190					if ($key === $marker) continue;
1191					if ( ! is_int($key))
1192					{
1193						$key = '"'.htmlspecialchars($key, ENT_NOQUOTES, self::$charset).'"';
1194					}
1195
1196					$output[] = "$space$s$key => ".Kohana::_dump($val, $length, $level + 1);
1197				}
1198				unset($var[$marker]);
1199
1200				$output[] = "$space)</span>";
1201			}
1202			else
1203			{
1204				// Depth too great
1205				$output[] = "(\n$space$s...\n$space)";
1206			}
1207
1208			return '<small>array</small><span>('.count($var).')</span> '.implode("\n", $output);
1209		}
1210		elseif (is_object($var))
1211		{
1212			// Copy the object as an array
1213			$array = (array) $var;
1214
1215			$output = array();
1216
1217			// Indentation for this variable
1218			$space = str_repeat($s = '    ', $level);
1219
1220			$hash = spl_object_hash($var);
1221
1222			// Objects that are being dumped
1223			static $objects = array();
1224
1225			if (empty($var))
1226			{
1227				// Do nothing
1228			}
1229			elseif (isset($objects[$hash]))
1230			{
1231				$output[] = "{\n$space$s*RECURSION*\n$space}";
1232			}
1233			elseif ($level < 10)
1234			{
1235				$output[] = "<code>{";
1236
1237				$objects[$hash] = TRUE;
1238				foreach ($array as $key => & $val)
1239				{
1240					if ($key[0] === "\x00")
1241					{
1242						// Determine if the access is protected or protected
1243						$access = '<small>'.($key[1] === '*' ? 'protected' : 'private').'</small>';
1244
1245						// Remove the access level from the variable name
1246						$key = substr($key, strrpos($key, "\x00") + 1);
1247					}
1248					else
1249					{
1250						$access = '<small>public</small>';
1251					}
1252
1253					$output[] = "$space$s$access $key => ".Kohana::_dump($val, $length, $level + 1);
1254				}
1255				unset($objects[$hash]);
1256
1257				$output[] = "$space}</code>";
1258			}
1259			else
1260			{
1261				// Depth too great
1262				$output[] = "{\n$space$s...\n$space}";
1263			}
1264
1265			return '<small>object</small> <span>'.get_class($var).'('.count($array).')</span> '.implode("\n", $output);
1266		}
1267		else
1268		{
1269			return '<small>'.gettype($var).'</small> '.htmlspecialchars(print_r($var, TRUE), ENT_NOQUOTES, Kohana::$charset);
1270		}
1271	}
1272
1273	/**
1274	 * Removes application, system, modpath, or docroot from a filename,
1275	 * replacing them with the plain text equivalents. Useful for debugging
1276	 * when you want to display a shorter path.
1277	 *
1278	 *     // Displays SYSPATH/classes/kohana.php
1279	 *     echo Kohana::debug_path(Kohana::find_file('classes', 'kohana'));
1280	 *
1281	 * @param   string  path to debug
1282	 * @return  string
1283	 */
1284	public static function debug_path($file)
1285	{
1286		if (strpos($file, APPPATH) === 0)
1287		{
1288			$file = 'APPPATH/'.substr($file, strlen(APPPATH));
1289		}
1290		elseif (strpos($file, SYSPATH) === 0)
1291		{
1292			$file = 'SYSPATH/'.substr($file, strlen(SYSPATH));
1293		}
1294		elseif (strpos($file, MODPATH) === 0)
1295		{
1296			$file = 'MODPATH/'.substr($file, strlen(MODPATH));
1297		}
1298		elseif (strpos($file, DOCROOT) === 0)
1299		{
1300			$file = 'DOCROOT/'.substr($file, strlen(DOCROOT));
1301		}
1302
1303		return $file;
1304	}
1305
1306	/**
1307	 * Returns an HTML string, highlighting a specific line of a file, with some
1308	 * number of lines padded above and below.
1309	 *
1310	 *     // Highlights the current line of the current file
1311	 *     echo Kohana::debug_source(__FILE__, __LINE__);
1312	 *
1313	 * @param   string   file to open
1314	 * @param   integer  line number to highlight
1315	 * @param   integer  number of padding lines
1316	 * @return  string   source of file
1317	 * @return  FALSE    file is unreadable
1318	 */
1319	public static function debug_source($file, $line_number, $padding = 5)
1320	{
1321		if ( ! $file OR ! is_readable($file))
1322		{
1323			// Continuing will cause errors
1324			return FALSE;
1325		}
1326
1327		// Open the file and set the line position
1328		$file = fopen($file, 'r');
1329		$line = 0;
1330
1331		// Set the reading range
1332		$range = array('start' => $line_number - $padding, 'end' => $line_number + $padding);
1333
1334		// Set the zero-padding amount for line numbers
1335		$format = '% '.strlen($range['end']).'d';
1336
1337		$source = '';
1338		while (($row = fgets($file)) !== FALSE)
1339		{
1340			// Increment the line number
1341			if (++$line > $range['end'])
1342				break;
1343
1344			if ($line >= $range['start'])
1345			{
1346				// Make the row safe for output
1347				$row = htmlspecialchars($row, ENT_NOQUOTES, Kohana::$charset);
1348
1349				// Trim whitespace and sanitize the row
1350				$row = '<span class="number">'.sprintf($format, $line).'</span> '.$row;
1351
1352				if ($line === $line_number)
1353				{
1354					// Apply highlighting to this row
1355					$row = '<span class="line highlight">'.$row.'</span>';
1356				}
1357				else
1358				{
1359					$row = '<span class="line">'.$row.'</span>';
1360				}
1361
1362				// Add to the captured source
1363				$source .= $row;
1364			}
1365		}
1366
1367		// Close the file
1368		fclose($file);
1369
1370		return '<pre class="source"><code>'.$source.'</code></pre>';
1371	}
1372
1373	/**
1374	 * Returns an array of HTML strings that represent each step in the backtrace.
1375	 *
1376	 *     // Displays the entire current backtrace
1377	 *     echo implode('<br/>', Kohana::trace());
1378	 *
1379	 * @param   string  path to debug
1380	 * @return  string
1381	 */
1382	public static function trace(array $trace = NULL)
1383	{
1384		if ($trace === NULL)
1385		{
1386			// Start a new trace
1387			$trace = debug_backtrace();
1388		}
1389
1390		// Non-standard function calls
1391		$statements = array('include', 'include_once', 'require', 'require_once');
1392
1393		$output = array();
1394		foreach ($trace as $step)
1395		{
1396			if ( ! isset($step['function']))
1397			{
1398				// Invalid trace step
1399				continue;
1400			}
1401
1402			if (isset($step['file']) AND isset($step['line']))
1403			{
1404				// Include the source of this step
1405				$source = Kohana::debug_source($step['file'], $step['line']);
1406			}
1407
1408			if (isset($step['file']))
1409			{
1410				$file = $step['file'];
1411
1412				if (isset($step['line']))
1413				{
1414					$line = $step['line'];
1415				}
1416			}
1417
1418			// function()
1419			$function = $step['function'];
1420
1421			if (in_array($step['function'], $statements))
1422			{
1423				if (empty($step['args']))
1424				{
1425					// No arguments
1426					$args = array();
1427				}
1428				else
1429				{
1430					// Sanitize the file path
1431					$args = array($step['args'][0]);
1432				}
1433			}
1434			elseif (isset($step['args']))
1435			{
1436				if (strpos($step['function'], '{closure}') !== FALSE)
1437				{
1438					// Introspection on closures in a stack trace is impossible
1439					$params = NULL;
1440				}
1441				else
1442				{
1443					if (isset($step['class']))
1444					{
1445						if (method_exists($step['class'], $step['function']))
1446						{
1447							$reflection = new ReflectionMethod($step['class'], $step['function']);
1448						}
1449						else
1450						{
1451							$reflection = new ReflectionMethod($step['class'], '__call');
1452						}
1453					}
1454					else
1455					{
1456						$reflection = new ReflectionFunction($step['function']);
1457					}
1458
1459					// Get the function parameters
1460					$params = $reflection->getParameters();
1461				}
1462
1463				$args = array();
1464
1465				foreach ($step['args'] as $i => $arg)
1466				{
1467					if (isset($params[$i]))
1468					{
1469						// Assign the argument by the parameter name
1470						$args[$params[$i]->name] = $arg;
1471					}
1472					else
1473					{
1474						// Assign the argument by number
1475						$args[$i] = $arg;
1476					}
1477				}
1478			}
1479
1480			if (isset($step['class']))
1481			{
1482				// Class->method() or Class::method()
1483				$function = $step['class'].$step['type'].$step['function'];
1484			}
1485
1486			$output[] = array(
1487				'function' => $function,
1488				'args'     => isset($args)   ? $args : NULL,
1489				'file'     => isset($file)   ? $file : NULL,
1490				'line'     => isset($line)   ? $line : NULL,
1491				'source'   => isset($source) ? $source : NULL,
1492			);
1493
1494			unset($function, $args, $file, $line, $source);
1495		}
1496
1497		return $output;
1498	}
1499
1500	private function __construct()
1501	{
1502		// This is a static class
1503	}
1504
1505} // End Kohana