PageRenderTime 41ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/system/core/Kohana.php

https://github.com/lmorchard/friendfeedarchiver
PHP | 1111 lines | 704 code | 141 blank | 266 comment | 55 complexity | a17fb053c326f3022ae7355c5dbba2b6 MD5 | raw file
  1. <?php defined('SYSPATH') or die('No direct script access.');
  2. /**
  3. * Provides Kohana-specific helper functions. This is where the magic happens!
  4. *
  5. * $Id: Kohana.php 1899 2008-02-02 00:29:35Z Shadowhand $
  6. *
  7. * @package Core
  8. * @author Kohana Team
  9. * @copyright (c) 2007-2008 Kohana Team
  10. * @license http://kohanaphp.com/license.html
  11. */
  12. class Kohana {
  13. // The singleton instance of the controller
  14. public static $instance;
  15. // Output buffering level
  16. private static $buffer_level = 0;
  17. // Will be set to TRUE when an exception is caught
  18. public static $has_error = FALSE;
  19. // The final output that will displayed by Kohana
  20. public static $output = '';
  21. // The current user agent
  22. public static $user_agent = '';
  23. /**
  24. * Sets up the PHP environment. Adds error/exception handling, output
  25. * buffering, and adds an auto-loading method for loading classes.
  26. *
  27. * This method is run immediately when this file is loaded, and is
  28. * benchmarked as environment_setup.
  29. *
  30. * For security, this function also destroys the $_REQUEST global variable.
  31. * Using the proper global (GET, POST, COOKIE, etc) is inherently more secure.
  32. * The recommended way to fetch a global variable is using the Input library.
  33. * @see http://www.php.net/globals
  34. *
  35. * @return void
  36. */
  37. final public static function setup()
  38. {
  39. static $run;
  40. // This function can only be run once
  41. if ($run === TRUE)
  42. return;
  43. // Start the environment setup benchmark
  44. Benchmark::start(SYSTEM_BENCHMARK.'_environment_setup');
  45. // Disable error reporting
  46. $ER = error_reporting(0);
  47. // Set the user agent
  48. self::$user_agent = trim($_SERVER['HTTP_USER_AGENT']);
  49. if (function_exists('date_default_timezone_set'))
  50. {
  51. $timezone = Config::item('locale.timezone');
  52. // Set default timezone, due to increased validation of date settings
  53. // which cause massive amounts of E_NOTICEs to be generated in PHP 5.2+
  54. date_default_timezone_set(empty($timezone) ? date_default_timezone_get() : $timezone);
  55. }
  56. // Restore error reporting
  57. error_reporting($ER);
  58. // Start output buffering
  59. ob_start(array('Kohana', 'output_buffer'));
  60. // Save buffering level
  61. self::$buffer_level = ob_get_level();
  62. // Set autoloader
  63. spl_autoload_register(array('Kohana', 'auto_load'));
  64. // Set error handler
  65. set_error_handler(array('Kohana', 'exception_handler'));
  66. // Set exception handler
  67. set_exception_handler(array('Kohana', 'exception_handler'));
  68. // Disable magic_quotes_runtime. The Input library takes care of
  69. // magic_quotes_gpc later.
  70. set_magic_quotes_runtime(0);
  71. // Send default text/html UTF-8 header
  72. header('Content-type: text/html; charset=UTF-8');
  73. // Set locale information
  74. setlocale(LC_ALL, Config::item('locale.language').'.UTF-8');
  75. if (Config::item('log.threshold') > 0)
  76. {
  77. // Get the configured log directory
  78. $log_dir = Config::item('log.directory');
  79. // Two possible locations
  80. $app_log = APPPATH.$log_dir;
  81. $log_dir = realpath($log_dir);
  82. // If the log directory does not exist, log inside of application/
  83. is_dir($log_dir) or $log_dir = $app_log;
  84. // Log directory must be writable
  85. if ( ! is_dir($log_dir) OR ! is_writable($log_dir))
  86. throw new Kohana_Exception('core.cannot_write_log');
  87. // Set the log directory
  88. Log::directory($log_dir);
  89. // Enable log writing if the log threshold is above 0
  90. register_shutdown_function(array('Log', 'write'));
  91. }
  92. // Enable Kohana routing
  93. Event::add('system.routing', array('Router', 'find_uri'));
  94. Event::add('system.routing', array('Router', 'setup'));
  95. // Enable Kohana controller initialization
  96. Event::add('system.execute', array('Kohana', 'instance'));
  97. // Enable Kohana 404 pages
  98. Event::add('system.404', array('Kohana', 'show_404'));
  99. // Enable Kohana output handling
  100. Event::add('system.shutdown', array('Kohana', 'shutdown'));
  101. if ($config = Config::item('hooks.enable'))
  102. {
  103. $hooks = array();
  104. if ( ! is_array($config))
  105. {
  106. // All of the hooks are enabled, so we use list_files
  107. $hooks = Kohana::list_files('hooks', TRUE);
  108. }
  109. else
  110. {
  111. // Individual hooks need to be found
  112. foreach($config as $name)
  113. {
  114. if ($hook = Kohana::find_file('hooks', $name, FALSE))
  115. {
  116. // Hook was found, add it to loaded hooks
  117. $hooks[] = $hook;
  118. }
  119. else
  120. {
  121. // This should never happen
  122. Log::add('error', 'Hook not found: '.$name);
  123. }
  124. }
  125. }
  126. // To validate the filename extension
  127. $ext = -(strlen(EXT));
  128. foreach($hooks as $hook)
  129. {
  130. if (substr($hook, $ext) === EXT)
  131. {
  132. // Hook was found, include it
  133. include_once $hook;
  134. }
  135. else
  136. {
  137. // This should never happen
  138. Log::add('error', 'Hook not found: '.$hook);
  139. }
  140. }
  141. }
  142. // Setup is complete, prevent it from being run again
  143. $run = TRUE;
  144. // Stop the environment setup routine
  145. Benchmark::stop(SYSTEM_BENCHMARK.'_environment_setup');
  146. }
  147. /**
  148. * Loads the controller and initializes it. Runs the pre_controller,
  149. * post_controller_constructor, and post_controller events. Triggers
  150. * a system.404 event when the route cannot be mapped to a controller.
  151. *
  152. * This method is benchmarked as controller_setup and controller_execution.
  153. *
  154. * @return object instance of controller
  155. */
  156. final public static function & instance()
  157. {
  158. if (self::$instance === NULL)
  159. {
  160. Benchmark::start(SYSTEM_BENCHMARK.'_controller_setup');
  161. // Include the Controller file
  162. require Router::$directory.Router::$controller.EXT;
  163. // Run system.pre_controller
  164. Event::run('system.pre_controller');
  165. // Set controller class name
  166. $controller = ucfirst(Router::$controller).'_Controller';
  167. // Make sure the controller class exists
  168. class_exists($controller, FALSE) or Event::run('system.404');
  169. // Find the unique controller methods
  170. $methods = array_diff(get_class_methods($controller), get_class_methods('Controller_Core'));
  171. // If there are no methods in the controller, it's invalid
  172. empty($methods) and Event::run('system.404');
  173. // Combine the methods
  174. $methods = array_combine($methods, $methods);
  175. if (isset($methods['_remap']))
  176. {
  177. // Change arguments to be $method, $arguments.
  178. // This makes _remap capable of being a much more effecient dispatcher
  179. Router::$arguments = array(Router::$method, Router::$arguments);
  180. // Set the method to _remap
  181. Router::$method = '_remap';
  182. }
  183. elseif (isset($methods[Router::$method]) AND substr(Router::$method, 0, 1) != '_')
  184. {
  185. // A valid route has been found, and nothing needs to be done.
  186. // Amazing that having nothing inside the statement still works.
  187. }
  188. elseif (method_exists($controller, '_default'))
  189. {
  190. // Change arguments to be $method, $arguments.
  191. // This makes _default a much more effecient 404 handler
  192. Router::$arguments = array(Router::$method, Router::$arguments);
  193. // Set the method to _default
  194. Router::$method = '_default';
  195. }
  196. else
  197. {
  198. // Method was not found, run the system.404 event
  199. Event::run('system.404');
  200. }
  201. // Initialize the controller
  202. $controller = new $controller;
  203. // Run system.post_controller_constructor
  204. Event::run('system.post_controller_constructor');
  205. // Stop the controller setup benchmark
  206. Benchmark::stop(SYSTEM_BENCHMARK.'_controller_setup');
  207. // Start the controller execution benchmark
  208. Benchmark::start(SYSTEM_BENCHMARK.'_controller_execution');
  209. // Controller method name, used for calling
  210. $method = Router::$method;
  211. if (empty(Router::$arguments))
  212. {
  213. // Call the controller method with no arguments
  214. $controller->$method();
  215. }
  216. else
  217. {
  218. // Manually call the controller for up to 4 arguments. Why? Because
  219. // call_user_func_array is ~3 times slower than direct method calls.
  220. switch(count(Router::$arguments))
  221. {
  222. case 1:
  223. $controller->$method(Router::$arguments[0]);
  224. break;
  225. case 2:
  226. $controller->$method(Router::$arguments[0], Router::$arguments[1]);
  227. break;
  228. case 3:
  229. $controller->$method(Router::$arguments[0], Router::$arguments[1], Router::$arguments[2]);
  230. break;
  231. case 4:
  232. $controller->$method(Router::$arguments[0], Router::$arguments[1], Router::$arguments[2], Router::$arguments[3]);
  233. break;
  234. default:
  235. // Resort to using call_user_func_array for many segments
  236. call_user_func_array(array($controller, $method), Router::$arguments);
  237. break;
  238. }
  239. }
  240. // Run system.post_controller
  241. Event::run('system.post_controller');
  242. // Stop the controller execution benchmark
  243. Benchmark::stop(SYSTEM_BENCHMARK.'_controller_execution');
  244. }
  245. return self::$instance;
  246. }
  247. /**
  248. * Kohana output handler.
  249. *
  250. * @param string current output buffer
  251. * @return string
  252. */
  253. final public static function output_buffer($output)
  254. {
  255. // Run the send_headers event, specifically for cookies being set
  256. Event::has_run('system.send_headers') or Event::run('system.send_headers');
  257. // Set final output
  258. self::$output = $output;
  259. // Set and return the final output
  260. return $output;
  261. }
  262. /**
  263. * Triggers the shutdown of Kohana by closing the output buffer, runs the system.display event.
  264. *
  265. * @return void
  266. */
  267. public static function shutdown()
  268. {
  269. while (ob_get_level() > self::$buffer_level)
  270. {
  271. // Flush all open output buffers above the internal buffer
  272. ob_end_flush();
  273. }
  274. // This will flush the Kohana buffer, which sets self::$output
  275. (ob_get_level() === self::$buffer_level) and ob_end_clean();
  276. // Run the output event
  277. Event::run('system.display', self::$output);
  278. // Render the final output
  279. self::render(self::$output);
  280. }
  281. /**
  282. * Inserts global Kohana variables into the generated output and prints it.
  283. *
  284. * @param string final output that will displayed
  285. * @return void
  286. */
  287. public static function render($output)
  288. {
  289. // Fetch memory usage in MB
  290. $memory = function_exists('memory_get_usage') ? (memory_get_usage() / 1024 / 1024) : 0;
  291. // Fetch benchmark for page execution time
  292. $benchmark = Benchmark::get(SYSTEM_BENCHMARK.'_total_execution');
  293. // Replace the global template variables
  294. $output = str_replace(
  295. array
  296. (
  297. '{kohana_version}',
  298. '{kohana_codename}',
  299. '{execution_time}',
  300. '{memory_usage}'
  301. ),
  302. array
  303. (
  304. KOHANA_VERSION,
  305. KOHANA_CODENAME,
  306. $benchmark['time'],
  307. number_format($memory, 2).'MB'
  308. ),
  309. $output
  310. );
  311. if (ini_get('output_handler') != 'ob_gzhandler' AND ini_get('zlib.output_compression') == 0 AND $level = Config::item('core.output_compression'))
  312. {
  313. if ($level < 1 OR $level > 9)
  314. {
  315. // Normalize the level to be an integer between 1 and 9. This
  316. // step must be done to prevent gzencode from triggering an error
  317. $level = max(1, min($level, 9));
  318. }
  319. if (stripos(@$_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE)
  320. {
  321. $compress = 'gzip';
  322. }
  323. elseif (stripos(@$_SERVER['HTTP_ACCEPT_ENCODING'], 'deflate') !== FALSE)
  324. {
  325. $compress = 'deflate';
  326. }
  327. }
  328. if (isset($compress) AND $level > 0)
  329. {
  330. switch($compress)
  331. {
  332. case 'gzip':
  333. // Compress output using gzip
  334. $output = gzencode($output, $level);
  335. break;
  336. case 'deflate':
  337. // Compress output using zlib (HTTP deflate)
  338. $output = gzdeflate($output, $level);
  339. break;
  340. }
  341. // This header must be sent with compressed content to prevent
  342. // browser caches from breaking
  343. header('Vary: Accept-Encoding');
  344. // Send the content encoding header
  345. header('Content-Encoding: '.$compress);
  346. // Sending Content-Length in CGI can result in unexpected behavior
  347. if (stripos(PHP_SAPI, 'cgi') === FALSE)
  348. {
  349. header('Content-Length: '.strlen($output));
  350. }
  351. }
  352. echo $output;
  353. }
  354. /**
  355. * Dual-purpose PHP error and exception handler. Uses the kohana_error_page
  356. * view to display the message.
  357. *
  358. * @param integer|object exception object or error code
  359. * @param string error message
  360. * @param string filename
  361. * @param integer line number
  362. * @return void
  363. */
  364. public static function exception_handler($exception, $message = NULL, $file = NULL, $line = NULL)
  365. {
  366. // PHP errors have 5 args, always
  367. $PHP_ERROR = (func_num_args() === 5);
  368. // Test to see if errors should be displayed
  369. if ($PHP_ERROR AND (error_reporting() & $exception) === 0)
  370. return;
  371. // This is useful for hooks to determine if a page has an error
  372. self::$has_error = TRUE;
  373. // Error handling will use exactly 5 args, every time
  374. if ($PHP_ERROR)
  375. {
  376. $code = $exception;
  377. $type = 'PHP Error';
  378. $template = 'kohana_error_page';
  379. }
  380. else
  381. {
  382. $code = $exception->getCode();
  383. $type = get_class($exception);
  384. $message = $exception->getMessage();
  385. $file = $exception->getFile();
  386. $line = $exception->getLine();
  387. $template = ($exception instanceof Kohana_Exception) ? $exception->getTemplate() : 'kohana_error_page';
  388. }
  389. if (is_numeric($code))
  390. {
  391. $codes = Kohana::lang('errors');
  392. if ( ! empty($codes[$code]))
  393. {
  394. list($level, $error, $description) = $codes[$code];
  395. }
  396. else
  397. {
  398. $level = 1;
  399. $error = $PHP_ERROR ? 'Unknown Error' : get_class($exception);
  400. $description = '';
  401. }
  402. }
  403. else
  404. {
  405. // Custom error message, this will never be logged
  406. $level = 5;
  407. $error = $code;
  408. $description = '';
  409. }
  410. // Remove the DOCROOT from the path, as a security precaution
  411. $file = str_replace('\\', '/', realpath($file));
  412. $file = preg_replace('|^'.preg_quote(DOCROOT).'|', '', $file);
  413. if (Config::item('log.threshold') >= $level)
  414. {
  415. // Log the error
  416. Log::add('error', Kohana::lang('core.uncaught_exception', $type, $message, $file, $line));
  417. }
  418. if ($PHP_ERROR)
  419. {
  420. $description = Kohana::lang('errors.'.E_RECOVERABLE_ERROR);
  421. $description = $description[2];
  422. }
  423. else
  424. {
  425. if (method_exists($exception, 'sendHeaders'))
  426. {
  427. // Send the headers if they have not already been sent
  428. headers_sent() or $exception->sendHeaders();
  429. }
  430. }
  431. while (ob_get_level() > self::$buffer_level)
  432. {
  433. // Clean all active output buffers
  434. ob_end_clean();
  435. }
  436. // Clear the current buffer
  437. (ob_get_level() === self::$buffer_level) and ob_clean();
  438. // Test if display_errors is on
  439. if (Config::item('core.display_errors'))
  440. {
  441. if ($line != FALSE)
  442. {
  443. // Remove the first entry of debug_backtrace(), it is the exception_handler call
  444. $trace = $PHP_ERROR ? array_slice(debug_backtrace(), 1) : $exception->getTrace();
  445. // Beautify backtrace
  446. $trace = self::backtrace($trace);
  447. }
  448. // Load the error
  449. include self::find_file('views', empty($template) ? 'kohana_error_page' : $template);
  450. }
  451. else
  452. {
  453. // Get the i18n messages
  454. $error = Kohana::lang('core.generic_error');
  455. $message = sprintf(Kohana::lang('core.errors_disabled'), url::site(''), url::site(Router::$current_uri));
  456. // Load the errors_disabled view
  457. include self::find_file('views', 'kohana_error_disabled');
  458. }
  459. // Run the system.shutdown event
  460. Event::has_run('system.shutdown') or Event::run('system.shutdown');
  461. // Turn off error reporting
  462. error_reporting(0);
  463. exit;
  464. }
  465. /**
  466. * Displays a 404 page.
  467. *
  468. * @throws Kohana_404_Exception
  469. * @param string URI of page
  470. * @param string custom template
  471. * @return void
  472. */
  473. public static function show_404($page = FALSE, $template = FALSE)
  474. {
  475. throw new Kohana_404_Exception($page, $template);
  476. }
  477. /**
  478. * Show a custom error message.
  479. *
  480. * @throws Kohana_User_Exception
  481. * @param string error title
  482. * @param string error message
  483. * @param string custom template
  484. * @return void
  485. */
  486. public static function show_error($title, $message, $template = FALSE)
  487. {
  488. throw new Kohana_User_Exception($title, $message, $template);
  489. }
  490. /**
  491. * Provides class auto-loading.
  492. *
  493. * @throws Kohana_Exception
  494. * @param string name of class
  495. * @return bool
  496. */
  497. public static function auto_load($class)
  498. {
  499. static $prefix;
  500. // Set the extension prefix
  501. empty($prefix) and $prefix = Config::item('core.extension_prefix');
  502. if (class_exists($class, FALSE))
  503. return TRUE;
  504. if (($type = strrpos($class, '_')) !== FALSE)
  505. {
  506. // Find the class suffix
  507. $type = substr($class, $type + 1);
  508. }
  509. switch($type)
  510. {
  511. case 'Core':
  512. $type = 'libraries';
  513. $file = substr($class, 0, -5);
  514. break;
  515. case 'Controller':
  516. $type = 'controllers';
  517. // Lowercase filename
  518. $file = strtolower(substr($class, 0, -11));
  519. break;
  520. case 'Model':
  521. $type = 'models';
  522. // Lowercase filename
  523. $file = strtolower(substr($class, 0, -6));
  524. break;
  525. case 'Driver':
  526. $type = 'libraries/drivers';
  527. $file = str_replace('_', '/', substr($class, 0, -7));
  528. break;
  529. default:
  530. // This can mean either a library or a helper, but libraries must
  531. // always be capitalized, so we check if the first character is
  532. // lowercase. If it is, we are loading a helper, not a library.
  533. $type = (ord($class[0]) > 96) ? 'helpers' : 'libraries';
  534. $file = $class;
  535. break;
  536. }
  537. // If the file doesn't exist, just return
  538. if (($filepath = self::find_file($type, $file)) === FALSE)
  539. return FALSE;
  540. // Load the requested file
  541. require_once $filepath;
  542. if ($type === 'libraries' OR $type === 'helpers')
  543. {
  544. if ($extension = self::find_file($type, $prefix.$class))
  545. {
  546. // Load the class extension
  547. require_once $extension;
  548. }
  549. elseif (substr($class, -5) !== '_Core' AND class_exists($class.'_Core', FALSE))
  550. {
  551. // Transparent class extensions are handled using eval. This is
  552. // a disgusting hack, but it works very well.
  553. eval('class '.$class.' extends '.$class.'_Core { }');
  554. }
  555. }
  556. return class_exists($class, FALSE);
  557. }
  558. /**
  559. * Find a resource file in a given directory.
  560. *
  561. * @throws Kohana_Exception if file is required and not found
  562. * @param string directory to search in
  563. * @param string filename to look for (including extension only if 4th parameter is TRUE)
  564. * @param boolean is the file required?
  565. * @param boolean use custom file extension?
  566. * @return array if the type is i18n or config
  567. * @return string if the file is found
  568. * @return FALSE if the file is not found
  569. */
  570. public static function find_file($directory, $filename, $required = FALSE, $ext = FALSE)
  571. {
  572. static $found = array();
  573. $search = $directory.'/'.$filename;
  574. $hash = sha1($search);
  575. if (isset($found[$hash]))
  576. return $found[$hash];
  577. if ($directory == 'config' OR $directory == 'i18n')
  578. {
  579. $fnd = array();
  580. // Search from SYSPATH up
  581. foreach(array_reverse(Config::include_paths()) as $path)
  582. {
  583. if (is_file($path.$search.EXT)) $fnd[] = $path.$search.EXT;
  584. }
  585. // If required and nothing was found, throw an exception
  586. if ($required == TRUE AND $fnd === array())
  587. throw new Kohana_Exception('core.resource_not_found', Kohana::lang('core.'.inflector::singular($directory)), $filename);
  588. return $found[$hash] = $fnd;
  589. }
  590. else
  591. {
  592. // Users can define their own extensions, .css, etc
  593. $ext = ($ext == FALSE) ? EXT : '';
  594. // Find the file and return its filename
  595. foreach (Config::include_paths() as $path)
  596. {
  597. if (is_file($path.$search.$ext))
  598. return $found[$hash] = $path.$search.$ext;
  599. }
  600. // If the file is required, throw an exception
  601. if ($required == TRUE)
  602. throw new Kohana_Exception('core.resource_not_found', Kohana::lang('core.'.inflector::singular($directory)), $filename);
  603. return $found[$hash] = FALSE;
  604. }
  605. }
  606. /**
  607. * Lists all files and directories in a resource path.
  608. *
  609. * @param string directory to search
  610. * @param boolean list all files to the maximum depth?
  611. * @param string full path to search (used for recursion, *never* set this manually)
  612. * @return array filenames and directories
  613. */
  614. public static function list_files($directory, $recursive = FALSE, $path = FALSE)
  615. {
  616. $files = array();
  617. if ($path === FALSE)
  618. {
  619. foreach(Config::include_paths() as $path)
  620. {
  621. $files = array_merge($files, self::list_files($directory, $recursive, $path.$directory));
  622. }
  623. }
  624. else
  625. {
  626. $path = rtrim($path, '/').'/';
  627. if (is_readable($path))
  628. {
  629. foreach(glob($path.'*') as $index => $item)
  630. {
  631. $files[] = $item = str_replace('\\', '/', $item);
  632. // Handle recursion
  633. if (is_dir($item) AND $recursive == TRUE)
  634. {
  635. // Filename should only be the basename
  636. $item = pathinfo($item, PATHINFO_BASENAME);
  637. // Append sub-directory search
  638. $files = array_merge($files, self::list_files($directory, TRUE, $path.$item));
  639. }
  640. }
  641. }
  642. }
  643. return $files;
  644. }
  645. /**
  646. * Fetch an i18n language item.
  647. *
  648. * @param string language key to fetch
  649. * @param array additional information to insert into the line
  650. * @return string i18n language string, or the requested key if the i18n item is not found
  651. */
  652. public static function lang($key, $args = array())
  653. {
  654. static $language = array();
  655. $group = current(explode('.', $key));
  656. if ( ! isset($language[$group]))
  657. {
  658. // Messages from this file
  659. $messages = array();
  660. // The name of the file to search for
  661. $filename = Config::item('locale.language').'/'.$group;
  662. // Loop through the files and include each one, so SYSPATH files
  663. // can be overloaded by more localized files
  664. foreach(self::find_file('i18n', $filename) as $filename)
  665. {
  666. include $filename;
  667. // Merge in configuration
  668. if ( ! empty($lang) AND is_array($lang))
  669. {
  670. foreach($lang as $k => $v)
  671. {
  672. $messages[$k] = $v;
  673. }
  674. }
  675. }
  676. // Cache the type
  677. $language[$group] = $messages;
  678. }
  679. $line = self::key_string($key, $language);
  680. // Return the key string as fallback
  681. if ($line === NULL)
  682. {
  683. Log::add('debug', 'Missing i18n entry '.$key.' for language '.Config::item('locale.language'));
  684. return $key;
  685. }
  686. if (is_string($line) AND func_num_args() > 1)
  687. {
  688. $args = array_slice(func_get_args(), 1);
  689. // Add the arguments into the line
  690. $line = vsprintf($line, is_array($args[0]) ? $args[0] : $args);
  691. }
  692. return $line;
  693. }
  694. /**
  695. * Returns the value of a key, defined by a 'dot-noted' string, from an array.
  696. *
  697. * @param string dot-noted string: foo.bar.baz
  698. * @param array array to search
  699. * @return string if the key is found
  700. * @return void if the key is not found
  701. */
  702. public static function key_string($keys, $array)
  703. {
  704. // No array to search
  705. if ((empty($keys) AND is_string($keys)) OR (empty($array) AND is_array($array)))
  706. return;
  707. // Prepare for loop
  708. $keys = explode('.', $keys);
  709. // Loop down and find the key
  710. do
  711. {
  712. // Get the current key
  713. $key = array_shift($keys);
  714. // Value is set, dig deeper or return
  715. if (isset($array[$key]))
  716. {
  717. // If the key is an array, and we haven't hit bottom, prepare
  718. // for the next loop by re-referencing to the next child
  719. if (is_array($array[$key]) AND ! empty($keys))
  720. {
  721. $array =& $array[$key];
  722. }
  723. else
  724. {
  725. // Requested key was found
  726. return $array[$key];
  727. }
  728. }
  729. else
  730. {
  731. // Requested key is not set
  732. break;
  733. }
  734. }
  735. while ( ! empty($keys));
  736. // We return NULL, because it's less common than FALSE
  737. return;
  738. }
  739. /**
  740. * Quick debugging of any variable. Any number of parameters can be set.
  741. *
  742. * @return string
  743. */
  744. public static function debug()
  745. {
  746. if (func_num_args() === 0)
  747. return;
  748. // Get params
  749. $params = func_get_args();
  750. $output = array();
  751. foreach($params as $var)
  752. {
  753. $output[] = '<pre>'.html::specialchars(print_r($var, TRUE)).'</pre>';
  754. }
  755. return implode("\n", $output);
  756. }
  757. /**
  758. * Displays nice backtrace information.
  759. * @see http://php.net/debug_backtrace
  760. *
  761. * @param array backtrace generated by an exception or debug_backtrace
  762. * @return string
  763. */
  764. public static function backtrace($trace)
  765. {
  766. if ( ! is_array($trace))
  767. return;
  768. // Final output
  769. $output = array();
  770. foreach($trace as $entry)
  771. {
  772. $temp = '<li>';
  773. if (isset($entry['file']))
  774. {
  775. // Add file (without docroot)
  776. $temp .= '<strong>'.preg_replace('!^'.preg_quote(DOCROOT).'!', '', $entry['file']);
  777. // Add line
  778. $temp .= ' ['.$entry['line'].']:</strong>';
  779. }
  780. $temp .= '<pre>';
  781. if (isset($entry['class']))
  782. {
  783. // Add class and call type
  784. $temp .= $entry['class'].$entry['type'];
  785. }
  786. // Add function
  787. $temp .= $entry['function'].'( ';
  788. // Add function args
  789. if (isset($entry['args']) AND is_array($entry['args']))
  790. {
  791. // Separator starts as nothing
  792. $sep = '';
  793. while ($arg = array_shift($entry['args']))
  794. {
  795. if (is_string($arg) AND is_file($arg))
  796. {
  797. // Remove docroot from filename
  798. $arg = preg_replace('!^'.preg_quote(DOCROOT).'!', '', $arg);
  799. }
  800. $temp .= $sep.print_r($arg, TRUE);
  801. // Change separator to a comma
  802. $sep = ', ';
  803. }
  804. }
  805. $temp .= ' )</pre></li>';
  806. $output[] = $temp;
  807. }
  808. return '<ul class="backtrace">'.implode("\n", $output).'</ul>';
  809. }
  810. } // End Kohana
  811. /**
  812. * Creates a generic i18n exception.
  813. */
  814. class Kohana_Exception extends Exception {
  815. // Template file
  816. protected $template = 'kohana_error_page';
  817. // Message
  818. protected $message = 'Unknown Exception: ';
  819. // Header
  820. protected $header = FALSE;
  821. // Error code, filename, line number
  822. protected $code = E_KOHANA;
  823. protected $file = FALSE;
  824. protected $line = FALSE;
  825. /**
  826. * Set exception message.
  827. *
  828. * @param string i18n language key for the message
  829. * @param array addition line parameters
  830. */
  831. function __construct($error)
  832. {
  833. $args = array_slice(func_get_args(), 1);
  834. // Fetch the error message
  835. $message = Kohana::lang($error, $args);
  836. // Handle error messages that are not set
  837. if ($message == $error)
  838. {
  839. $this->message .= $error;
  840. }
  841. else
  842. {
  843. $this->message = $message;
  844. }
  845. }
  846. /**
  847. * Magic method for converting an object to a string.
  848. *
  849. * @return string i18n message
  850. */
  851. public function __toString()
  852. {
  853. return (string) $this->message;
  854. }
  855. /**
  856. * Fetch the template name.
  857. *
  858. * @return string
  859. */
  860. public function getTemplate()
  861. {
  862. return $this->template;
  863. }
  864. /**
  865. * Sends an Internal Server Error header.
  866. *
  867. * @return void
  868. */
  869. public function sendHeaders()
  870. {
  871. // Send the 500 header
  872. header('HTTP/1.1 500 Internal Server Error');
  873. }
  874. } // End Kohana Exception
  875. /**
  876. * Creates a custom exception.
  877. */
  878. class Kohana_User_Exception extends Kohana_Exception {
  879. /**
  880. * Set exception title and message.
  881. *
  882. * @param string exception title string
  883. * @param string exception message string
  884. * @param string custom error template
  885. */
  886. public function __construct($title, $message, $template = FALSE)
  887. {
  888. $this->code = $title;
  889. $this->message = $message;
  890. if ($template != FALSE)
  891. {
  892. $this->template = $template;
  893. }
  894. }
  895. } // End Kohana PHP Exception
  896. /**
  897. * Creates a Page Not Found exception.
  898. */
  899. class Kohana_404_Exception extends Kohana_Exception {
  900. protected $code = E_PAGE_NOT_FOUND;
  901. /**
  902. * Set internal properties.
  903. *
  904. * @param string URL of page
  905. * @param string custom error template
  906. */
  907. public function __construct($page = FALSE, $template = FALSE)
  908. {
  909. if ($page === FALSE)
  910. {
  911. // Construct the page URI using Router properties
  912. $page = Router::$current_uri.Router::$url_suffix.Router::$query_string;
  913. }
  914. $this->message = Kohana::lang('core.page_not_found', $page);
  915. $this->file = FALSE;
  916. $this->line = FALSE;
  917. $this->template = $template;
  918. }
  919. /**
  920. * Sends "File Not Found" headers, to emulate server behavior.
  921. *
  922. * @return void
  923. */
  924. public function sendHeaders()
  925. {
  926. // Send the 404 header
  927. header('HTTP/1.1 404 File Not Found');
  928. }
  929. } // End Kohana 404 Exception
  930. /**
  931. * Run Kohana setup to prepare the environment.
  932. */
  933. Kohana::setup();