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 */
16 class 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