<?php
/*
* Copyright (C) 2017 Karmabunny Pty Ltd.
*
* This file is a part of SproutCMS.
*
* SproutCMS is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Foundation, either
* version 2 of the License, or (at your option) any later version.
*
* For more information, visit <http://getsproutcms.com>.
*/
use karmabunny\pdb\Exceptions\QueryException;
use karmabunny\pdb\Exceptions\RowMissingException;
use Sprout\Controllers\BaseController;
use Sprout\Helpers\Enc;
use Sprout\Helpers\Inflector;
use Sprout\Helpers\Pdb;
use Sprout\Helpers\Register;
use Sprout\Helpers\Router;
use Sprout\Helpers\Sprout;
use Sprout\Helpers\SubsiteSelector;
use Sprout\Helpers\View;
/**
* Provides Kohana-specific helper functions. This is where the magic happens!
*
* $Id: Kohana.php 4372 2009-05-28 17:00:34Z ixmatus $
*
* @package Core
* @author Kohana Team
* @copyright (c) 2007-2008 Kohana Team
* @license http://kohanaphp.com/license.html
*/
final class Kohana {
// The singleton instance of the controller
public static $instance;
// Output buffering level
private static $buffer_level;
// Will be set to TRUE when an exception is caught
public static $has_error = FALSE;
// The final output that will displayed by Kohana
public static $output = '';
// The current user agent
public static $user_agent;
// The current locale
public static $locale;
// Configuration
private static $configuration;
// Include paths
private static $include_paths;
// Cache lifetime
private static $cache_lifetime;
// Internal caches and write status
private static
$internal_cache = array(); private static $write_cache;
private static $internal_cache_path;
/**
* Sets up the PHP environment. Adds error/exception handling, output
* buffering, and adds an auto-loading method for loading classes.
*
* For security, this function also destroys the $_REQUEST global variable.
* Using the proper global (GET, POST, COOKIE, etc) is inherently more secure.
* @see http://www.php.net/globals
*
* @return void
*/
public static function setup()
{
static $run;
// This function can only be run once
if ($run === TRUE)
return;
// Define Kohana error constant
// Define 404 error constant
define('E_PAGE_NOT_FOUND', 43);
// Define database error constant
define('E_DATABASE_ERROR', 44);
// Set the directory to be used for the internal cache
self::$internal_cache_path = APPPATH.'cache/';
// How long to save the internal cache, in seconds
self::$cache_lifetime = 60;
// Load cached configuration and file paths
//self::$internal_cache['configuration'] = self::cache('configuration');
self::$internal_cache['find_file_paths'] = self::cache('find_file_paths');
// Enable cache saving
Event
::add('system.shutdown', array(__CLASS__, 'internalCacheSave'));
// Set the user agent
self::$user_agent = trim(@$_SERVER['HTTP_USER_AGENT']);
// Start output buffering
// Save buffering level
// Auto-convert errors into exceptions
// Set exception handler
set_exception_handler
(array('Kohana', 'exceptionHandler'));
// Send default text/html UTF-8 header
header('Content-Type: text/html; charset=UTF-8');
// Check the CLI domain has been set
if (! Kohana::config('config.cli_domain'))
{
throw new Exception('Sprout config parameter "config.cli_domain" has not been set. See the sprout development documentation for more info.');
}
// Set HTTP_HOST for CLI scripts
if (! isset($_SERVER['HTTP_HOST'])) {
if (!empty($_SERVER['PHP_S_HTTP_HOST'])) {
$_SERVER['HTTP_HOST'] = $_SERVER['PHP_S_HTTP_HOST'];
}
else
{
$_SERVER['HTTP_HOST'] = Kohana::config('config.cli_domain');
}
}
// Set SERVER_NAME if it's not set
if (! isset($_SERVER['SERVER_NAME'])) {
$_SERVER['SERVER_NAME'] = $_SERVER['HTTP_HOST'];
}
// Load locales
$locales = self::config('locale.language');
// Make first locale UTF-8
$locales[0] .= '.UTF-8';
// Set locale information
// Enable Kohana 404 pages
Event
::add('system.404', array('Kohana', 'show404'));
// Enable Kohana output handling
Event
::add('system.shutdown', array('Kohana', 'shutdown'));
Event
::add('system.display', array('Sprout\\Helpers\\Needs', 'replacePlaceholders')); Event
::add('system.display', array('Sprout\\Helpers\\SessionStats', 'trackPageView'));
// Setup is complete, prevent it from being run again
$run = TRUE;
}
/**
* Loads the controller and initializes it. Runs the pre_controller,
* post_controller_constructor, and post_controller events. Triggers
* a system.404 event when the route cannot be mapped to a controller.
*
* @return object instance of controller
*/
public static function & instance()
{
if (self::$instance === NULL)
{
try {
// Start validation of the controller
$class = new ReflectionClass(Router::$controller);
} catch (ReflectionException $e) {
// Controller does not exist
Event::run('system.404');
}
if ($class->isAbstract() OR (IN_PRODUCTION AND $class->getConstant('ALLOW_PRODUCTION') == FALSE))
{
// Controller is not allowed to run in production
Event::run('system.404');
}
// Initialise Sprout modules, if required
$modules = Register::getModuleDirs();
foreach ($modules as $mod) {
require $mod . '/sprout_load.php';
}
}
// Initialise any custom non-module code
require DOCROOT . '/skin/sprout_load.php';
}
// Run system.pre_controller
Event::run('system.pre_controller');
// Create a new controller instance
$controller = $class->newInstance();
if (!($controller instanceof BaseController)) {
throw new Exception
("Class doesn't extend BaseController: " . get_class($controller)); }
// Controller constructor has been executed
Event::run('system.post_controller_constructor');
try
{
// Load the controller method
$method = $class->getMethod(Router::$method);
// Method exists
if (Router::$method[0] === '_')
{
// Do not allow access to hidden methods
Event::run('system.404');
}
if ($method->isProtected() or $method->isPrivate())
{
// Do not attempt to invoke protected methods
throw new ReflectionException('protected controller method');
}
}
catch (ReflectionException $e)
{
}
$controller->_run(Router::$method, Router::$arguments);
// Controller method has been executed
Event::run('system.post_controller');
}
return self::$instance;
}
/**
* Get all include paths. APPPATH is the first path, followed by module
* paths in the order they are configured.
*
* @param boolean re-process the include paths
* @return array
*/
public static function includePaths($process = FALSE)
{
if ($process === TRUE)
{
self::$include_paths = array();
// Sprout modules first
foreach (Register::getModuleDirs() as $path)
{
{
// Add a valid path
self::$include_paths[] = $path.'/';
}
}
// Add Sprout core next
self::$include_paths[] = APPPATH;
}
return self::$include_paths;
}
/**
* Get a config item or group.
*
* @param string item name
* @param boolean force a forward slash (/) at the end of the item
* @param boolean is the item required?
* @return mixed
*/
public static function config($key, $slash = FALSE, $required = TRUE)
{
if (self::$configuration === NULL)
{
// Load core configuration
self::$configuration['core'] = self::configLoad('core');
// Re-parse the include paths
self::includePaths(TRUE);
}
// Get the group name from the key
$group = $group[0];
if ( ! isset(self::$configuration[$group])) {
// Load the configuration group
self::$configuration[$group] = self::configLoad($group, $required);
}
// Get the value of the key string
$value = self::keyString(self::$configuration, $key);
if ($slash === TRUE AND
is_string($value) AND
$value !== '') {
// Force the value to end with "/"
$value = rtrim($value, '/').'/'; }
return $value;
}
/**
* Sets a configuration item, if allowed.
*
* @param string config key string
* @param string config value
* @return boolean
*/
public static function configSet($key, $value)
{
// Do this to make sure that the config array is already loaded
self::config($key);
if (substr($key, 0, 7) === 'routes.') {
// Routes cannot contain sub keys due to possible dots in regex
}
else
{
// Convert dot-noted key string to an array
}
// Used for recursion
$conf =& self::$configuration;
$last = count($keys) - 1;
foreach ($keys as $i => $k)
{
if ($i === $last)
{
$conf[$k] = $value;
}
else
{
$conf =& $conf[$k];
}
}
return TRUE;
}
/**
* Load a config file.
*
* @param string config filename, without extension
* @param boolean is the file required?
* @return array
*/
public static function configLoad($name, $required = TRUE)
{
if ($name === 'core')
{
// Load the application configuration file
require APPPATH.'config/config.php';
if ( ! isset($config['site_domain'])) {
// Invalid config file
die('Your Kohana application configuration file is not valid.'); }
return $config;
}
if (isset(self::$internal_cache['configuration'][$name])) return self::$internal_cache['configuration'][$name];
// Load matching configs
$configuration = array();
if ($files = self::findFile('config', $name, $required))
{
foreach ($files as $file)
{
require $file;
{
// Merge in configuration
}
}
}
if ( ! isset(self::$write_cache['configuration'])) {
// Cache has changed
self::$write_cache['configuration'] = TRUE;
}
return self::$internal_cache['configuration'][$name] = $configuration;
}
/**
* Clears a config group from the cached configuration.
*
* @param string config group
* @return void
*/
public static function configClear($group)
{
// Remove the group from config
unset(self::$configuration[$group], self::$internal_cache['configuration'][$group]);
if ( ! isset(self::$write_cache['configuration'])) {
// Cache has changed
self::$write_cache['configuration'] = TRUE;
}
}
/**
* Deprecated.
*
* This function has been removed, and it's signature has
* been left for compatibilty only.
*/
public static
function log($type, $message) {
}
/**
* Disable the find-files and configuration caches
* This may be required if the caching is causing problems
*/
public static function disableCache()
{
self::$cache_lifetime = 0;
self::$internal_cache = [];
}
/**
* Load data from a simple cache file. This should only be used internally,
* and is NOT a replacement for the Cache library.
*
* @param string unique name of cache
* @return mixed
*/
public static function cache($name)
{
if (self::$cache_lifetime > 0)
{
$path = self::$internal_cache_path.'kohana_'.$name;
{
// Check the file modification time
{
}
else
{
// Cache is invalid, delete it
}
}
}
// No cache found
return NULL;
}
/**
* Save data to a simple cache file. This should only be used internally, and
* is NOT a replacement for the Cache library.
*
* @param string cache name
* @param mixed data to cache
* @param integer expiration in seconds
* @return boolean
*/
public static function cacheSave($name, $data)
{
$path = self::$internal_cache_path.'kohana_'.$name;
if ($data === NULL)
{
// Delete cache
}
else
{
}
}
/**
* Kohana output handler. Called during ob_clean, ob_flush, and their variants.
*
* @param string current output buffer
* @return string
*/
public static function outputBuffer($output)
{
// Could be flushing, so send headers first
if ( ! Event::hasRun('system.send_headers'))
{
// Run the send_headers event
Event::run('system.send_headers');
}
self::$output = $output;
// Set and return the final output
return self::$output;
}
/**
* Closes all open output buffers, either by flushing or cleaning, and stores the Kohana
* output buffer for display during shutdown.
*
* @param boolean disable to clear buffers, rather than flushing
* @return void
*/
public static function closeBuffers($flush = TRUE)
{
{
// Set the close function
$close = ($flush === TRUE) ? 'ob_end_flush' : 'Kohana::_obEndClean';
{
// Flush or clean the buffer
$close();
}
// Store the Kohana output buffer
Kohana::_obEndClean();
}
}
/**
* Triggers the shutdown of Kohana by closing the output buffer, runs the system.display event.
*
* @return void
*/
public static function shutdown()
{
// Close output buffers
self::closeBuffers(TRUE);
// Run the output event
Event::run('system.display', self::$output);
// Render the final output
self::render(self::$output);
}
/**
* Inserts global Kohana variables into the generated output and prints it.
*
* @param string final output that will displayed
* @return void
*/
public static function render($output)
{
if ($level = self::config('core.output_compression') AND
ini_get('output_handler') !== 'ob_gzhandler' AND
(int
) ini_get('zlib.output_compression') === 0) {
if ($level < 1 OR $level > 9)
{
// Normalize the level to be an integer between 1 and 9. This
// step must be done to prevent gzencode from triggering an error
$level = max(1, min($level, 9)); }
if (stripos(@$_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE) {
$compress = 'gzip';
}
elseif (stripos(@$_SERVER['HTTP_ACCEPT_ENCODING'], 'deflate') !== FALSE) {
$compress = 'deflate';
}
}
if (isset($compress) AND
$level > 0) {
switch ($compress)
{
case 'gzip':
// Compress output using gzip
break;
case 'deflate':
// Compress output using zlib (HTTP deflate)
break;
}
// This header must be sent with compressed content to prevent
// browser caches from breaking
header('Vary: Accept-Encoding');
// Send the content encoding header
header('Content-Encoding: '.$compress);
// Sending Content-Length in CGI can result in unexpected behavior
if (stripos(PHP_SAPI
, 'cgi') === FALSE) {
}
}
echo $output;
}
/**
* Displays a 404 page.
*
* @throws Kohana_404_Exception
* @param string URI of page
* @param string custom template
* @return void
*/
public static function show404($page = FALSE)
{
throw new Kohana_404_Exception($page);
}
/**
* Converts PHP errors into {@see ErrorException}s
*
* @param int $errno Error code
* @param string $errmsg Error message
* @param string $file
* @param int $line
* @throws ErrorException
* @return void
*/
public static function errorHandler($errno, $errmsg, $file, $line)
{
// Ignore statments prepended by @
throw new ErrorException($errmsg, 0, $errno, $file, $line);
}
/**
* Log exceptions in the database
*
* @param (\Exception|\Throwable) Exception or error to log
* @return int Record ID
*/
public static function logException($exception)
{
static $insert; // PDOStatement
static $delete; // PDOStatement
$conn = Pdb::getConnection();
if (!$insert) {
// This table is MyISAM so this shouldn't affect transactions
$table = Pdb::prefix() . 'exception_log';
$insert_q = "INSERT INTO {$table}
(date_generated, class_name, message, exception_object, exception_trace, server, get_data, session)
VALUES
(:date, :class, :message, :exception, :trace, :server, :get, :session)";
$insert = $conn->prepare($insert_q);
$delete_q = "DELETE FROM {$table} WHERE date_generated < DATE_SUB(?, INTERVAL 10 DAY)";
$delete = $conn->prepare($delete_q);
}
// Extract private attributes from Exception which serialize() would provide
$reflect = new ReflectionClass($exception);
$ex_data = [];
$ignore_props = ['message', 'trace', 'string', 'previous'];
$props = $reflect->getProperties();
foreach ($props as $prop) {
$prop_name = $prop->name;
if (in_array($prop_name, $ignore_props)) continue;
$prop->setAccessible(true);
$ex_data[$prop_name] = $prop->getValue($exception);
}
$insert->execute([
'date' => Pdb::now(),
'message' => $exception->getMessage(),
'trace' => json_encode($exception->getTraceAsString()), ]);
$log_id = $conn->lastInsertId();
$insert->closeCursor();
$delete->execute([Pdb::now()]);
$delete->closeCursor();
return $log_id;
}
/**
* Exception handler.
*
* @param object exception object
* @return void
*/
public static function exceptionHandler($exception)
{
if (!$exception instanceof \Exception and !$exception instanceof \Throwable) {
throw new Exception
('PHP7 - Exception handler was invoked with an invalid exception object type: ' . get_class($exception)); }
try {
$log_id = self::logException($exception);
} catch (Exception $junk) {
$log_id = 0;
}
try
{
// This is useful for hooks to determine if a page has an error
self::$has_error = TRUE;
$code = $exception->getCode();
$message = $exception->getMessage();
$file = $exception->getFile();
$line = $exception->getLine();
{
$codes = self::lang('errors');
if ( ! empty($codes[$code])) {
list($level, $error, $description) = $codes[$code]; }
else
{
$level = 1;
$description = '';
}
}
else
{
// Custom error message, this will never be logged
$level = 5;
$error = $code;
$description = '';
}
// For PHP errors, look up name and description from the i18n system
if ($exception instanceof ErrorException) {
$severify_info = self::lang('errors.' . $exception->getSeverity());
$error = $severify_info[1];
$description = $severify_info[2];
}
}
// Remove the DOCROOT from the path, as a security precaution
if (IN_PRODUCTION or @$_SERVER['SERVER_ADDR'] != @$_SERVER['REMOTE_ADDR']) {
}
{
// Send the headers if they have not already been sent
$exception->sendHeaders();
}
// Scrub details for database errors on production sites.
if (IN_PRODUCTION and $exception instanceof QueryException) {
$line = $file = $message = '';
$description = 'A database error occurred while performing the requested procedure.';
}
// Close all output buffers except for Kohana
{
}
// 404 errors are a special case - we load content from the database and put into a nice skin
if ($exception instanceof Kohana_404_Exception
or
($exception instanceof RowMissingException and IN_PRODUCTION)
) {
header("HTTP/1.0 404 File Not Found");
if ($exception instanceof RowMissingException) {
$message = 'One of the database records for the page you requested could not be found.';
}
$page = new View('sprout/404_error');
$page->message = $message;
$page = $page->render();
$view = new View('skin/inner');
$view->page_title = '404 File Not Found';
$view->main_content = $page;
$view->controller = '404-error';
echo $view->render();
}
else
{
if (PHP_SAPI != 'cli') {
header("HTTP/1.0 500 Internal Server Error"); }
if ( ! IN_PRODUCTION )
{
$trace = $exception->getTrace();
$trace = Sprout::simpleBacktrace($trace);
$trace = self::backtrace($trace);
}
// Load the error
if (PHP_SAPI == 'cli') {
require APPPATH . 'views/system/error_cli.php';
} elseif (!IN_PRODUCTION) {
require APPPATH . 'views/system/error_development.php';
} else {
// No skin defined yet? Use the default one
if (! SubsiteSelector::$subsite_code) {
SubsiteSelector::$subsite_code = 'default';
}
// Use the skin template or fall back to a sensible default
$file = DOCROOT . 'skin/' . SubsiteSelector::$subsite_code . '/exception.php';
$file = APPPATH . 'views/system/error_production.php';
}
require $file;
}
}
if ( ! Event::hasRun('system.shutdown'))
{
// Run the shutdown even to ensure a clean exit
Event::run('system.shutdown');
}
// Turn off error reporting
}
catch (Exception $e)
{
if (IN_PRODUCTION) {
} else {
echo "<pre>";
echo "<b>Failed to handle ", get_class($e), "</b> in "; echo $e->getFile(), ' on line ', $e->getLine(), ":\n";
echo $e->getMessage(), "\n\n";
echo "<b>Route:</b> ", Router::$controller, '::';
echo Router::$method;
if (!empty(Router
::$arguments)) { echo '(', implode(', ', Router
::$arguments), ')'; }
echo "\n\n";
echo "<b>Trace:</b>\n";
$trace = print_r($e->getTrace(), true); }
}
}
/**
* Find a resource file in a given directory. Files will be located according
* to the order of the include paths. Configuration and i18n files will be
* returned in reverse order.
*
* @throws Kohana_Exception if file is required and not found
* @param string directory to search in
* @param string filename to look for (without extension)
* @param boolean file required
* @param string file extension
* @return array if the type is config, i18n or l10n
* @return string if the file is found
* @return FALSE if the file is not found
*/
public static function findFile($directory, $filename, $required = FALSE, $ext = FALSE)
{
// NOTE: This test MUST be not be a strict comparison (===), or empty
// extensions will be allowed!
if ($ext == '')
{
// Use the default extension
$ext = '.php';
}
else
{
// Add a period before the extension
$ext = '.'.$ext;
}
// Search path
$search = $directory.'/'.$filename.$ext;
if (isset(self::$internal_cache['find_file_paths'][$search])) return self::$internal_cache['find_file_paths'][$search];
// Load include paths
$paths = self::$include_paths;
// Nothing found, yet
$found = NULL;
if ($directory === 'config')
{
array_unshift($paths, DOCROOT
. 'skin/' . SubsiteSelector
::$subsite_code . '/'); }
else if ($directory === 'views')
{
array_unshift($paths, DOCROOT
. 'skin/' . SubsiteSelector
::$subsite_code . '/'); }
if ($directory === 'config' OR $directory === 'i18n')
{
// Search in reverse, for merging
foreach ($paths as $path)
{
{
// A matching file has been found
$found[] = $path.$search;
}
}
}
else
{
foreach ($paths as $path)
{
{
// A matching file has been found
$found = $path.$search;
// Stop searching
break;
}
}
}
if ($found === NULL)
{
if ($required === TRUE)
{
// Directory i18n key
$directory = 'core.'.Inflector::singular($directory);
// If the file is required, throw an exception
throw new Kohana_Exception('core.resource_not_found', self::lang($directory), $filename);
}
else
{
// Nothing was found, return FALSE
$found = FALSE;
}
}
if ( ! isset(self::$write_cache['find_file_paths'])) {
// Write cache at shutdown
self::$write_cache['find_file_paths'] = TRUE;
}
return self::$internal_cache['find_file_paths'][$search] = $found;
}
/**
* Lists all files and directories in a resource path.
*
* @param string directory to search
* @param boolean list all files to the maximum depth?
* @param string full path to search (used for recursion, *never* set this manually)
* @return array filenames and directories
*/
public static function listFiles($directory, $recursive = FALSE, $path = FALSE)
{
if ($path === FALSE)
{
foreach ($paths as $path)
{
// Recursively get and merge all files
$files = array_merge($files, self::listFiles($directory, $recursive, $path.$directory)); }
}
else
{
$path = rtrim($path, '/').'/';
{
{
foreach ($items as $index => $item)
{
// Handle recursion
if (is_dir($item) AND
$recursive == TRUE) {
// Filename should only be the basename
$item = pathinfo($item, PATHINFO_BASENAME
);
// Append sub-directory search
$files = array_merge($files, self::listFiles($directory, TRUE, $path.$item)); }
}
}
}
}
return $files;
}
/**
* Fetch an i18n language item.
*
* @param string language key to fetch
* @param array additional information to insert into the line
* @return string i18n language string, or the requested key if the i18n item is not found
*/
public static
function lang
($key, $args = array()) {
// Extract the main group from the key
$group = $group[0];
// Get locale name
$locale = self::config('locale.language.0');
if ( ! isset(self::$internal_cache['language'][$locale][$group])) {
// Messages for this group
include APPPATH . "i18n/{$locale}/{$group}.php";
// Merge in configuration
foreach ($lang as $k => $v) {
$messages[$k] = $v;
}
}
if ( ! isset(self::$write_cache['language'])) {
// Write language cache
self::$write_cache['language'] = TRUE;
}
self::$internal_cache['language'][$locale][$group] = $messages;
}
// Get the line from cache
$line = self::keyString(self::$internal_cache['language'][$locale], $key);
if ($line === NULL)
{
// Return the key string as fallback
return $key;
}
{
// Add the arguments into the line
}
return $line;
}
/**
* Returns the value of a key, defined by a 'dot-noted' string, from an array.
*
* @param array array to search
* @param string dot-noted string: foo.bar.baz
* @return string if the key is found
* @return void if the key is not found
*/
public static function keyString($array, $keys)
{
return NULL;
// Prepare for loop
{
return @$array[$keys[0]][$keys[1]];
}
do
{
// Get the next key
{
{
// Dig down to prepare the next loop
$array = $array[$key];
}
else
{
// Requested key was found
return $array[$key];
}
}
else
{
// Requested key is not set
break;
}
}
return NULL;
}
/**
* Sets values in an array by using a 'dot-noted' string.
*
* @param array array to set keys in (reference)
* @param string dot-noted string: foo.bar.baz
* @return mixed fill value for the key
* @return void
*/
public static function keyStringSet( & $array, $keys, $fill = NULL)
{
if (is_object($array) AND
($array instanceof ArrayObject
)) {
// Copy the array
$array_copy = $array->getArrayCopy();
// Is an object
$array_object = TRUE;
}
else
{
{
// Must always be an array
}
// Copy is a reference to the array
$array_copy =& $array;
}
return $array;
// Create keys
// Create reference to the array
$row =& $array_copy;
for ($i = 0, $end = count($keys) - 1; $i <= $end; $i++) {
// Get the current key
$key = $keys[$i];
if ( ! isset($row[$key])) {
if (isset($keys[$i + 1])) {
// Make the value an array
}
else
{
// Add the fill key
$row[$key] = $fill;
}
}
elseif (isset($keys[$i + 1])) {
// Make the value an array
$row[$key] = (array) $row[$key]; }
// Go down a level, creating a new row reference
$row =& $row[$key];
}
if (isset($array_object)) {
// Swap the array back in
$array->exchangeArray($array_copy);
}
}
/**
* Retrieves current user agent information:
* keys: browser, version, platform, mobile, robot, referrer, languages, charsets
* tests: is_browser, is_mobile, is_robot, accept_lang, accept_charset
*
* @param string key or test name
* @param string used with "accept" tests: userAgent(accept_lang, en)
* @return array languages and charsets
* @return string all other keys
* @return boolean all tests
*/
public static function userAgent($key = 'agent', $compare = NULL)
{
static $info;
// Return the raw string
if ($key === 'agent')
return self::$user_agent;
if ($info === NULL)
{
// Parse the user agent and extract basic information
$agents = self::config('user_agents');
foreach ($agents as $type => $data)
{
foreach ($data as $agent => $name)
{
if (stripos(self::$user_agent, $agent) !== FALSE) {
if ($type === 'browser' AND
preg_match('|'.preg_quote($agent).'[^0-9.]*+([0-9.][0-9.a-z]*)|i', self::$user_agent, $match)) {
// Set the browser version
$info['version'] = $match[1];
}
// Set the agent name
$info[$type] = $name;
break;
}
}
}
}
{
switch ($key)
{
case 'is_robot':
case 'is_browser':
case 'is_mobile':
// A boolean result
break;
case 'languages':
if ( ! empty($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
{
// Found a result
$return = $matches[0];
}
}
break;
case 'charsets':
if ( ! empty($_SERVER['HTTP_ACCEPT_CHARSET'])) {
{
// Found a result
$return = $matches[0];
}
}
break;
case 'referrer':
if ( ! empty($_SERVER['HTTP_REFERER'])) {
// Found a result
$return = trim($_SERVER['HTTP_REFERER']); }
break;
}
// Cache the return value
isset($return) and
$info[$key] = $return; }
{
// The comparison must always be lowercase
switch ($key)
{
case 'accept_lang':
// Check if the lange is accepted
return in_array($compare, self::userAgent('languages')); break;
case 'accept_charset':
// Check if the charset is accepted
return in_array($compare, self::userAgent('charsets')); break;
default:
// Invalid comparison
return FALSE;
break;
}
}
// Return the key, if set
return isset($info[$key]) ?
$info[$key] : NULL; }
/**
* Quick debugging of any variable. Any number of parameters can be set.
*
* @return string
*/
public static function debug()
{
return;
// Get params
foreach ($params as $var)
{
$output[] = '<pre>('.gettype($var).') '.Enc
::html(print_r($var, TRUE)).'</pre>'; }
}
/**
* Displays nice backtrace information.
* @see http://php.net/debug_backtrace
*
* @param array backtrace generated by an exception or debug_backtrace
* @return string
*/
public static function backtrace($trace)
{
return;
// Final output
foreach ($trace as $entry)
{
$temp = '<li>';
if (isset($entry['file'])) { if (IN_PRODUCTION or @$_SERVER['SERVER_ADDR'] != @$_SERVER['REMOTE_ADDR']) {
}
$temp .= self::lang('core.error_file_line', $entry['file'], $entry['line']);
}
$temp .= '<pre>';
if (isset($entry['class'])) {
// Add class and call type
$temp .= $entry['class'].$entry['type'];
}
// Add function
$temp .= $entry['function'].'( ';
// Add function args
{
// Separator starts as nothing
$sep = '';
{
{
$arg = Enc::cleanfunky($arg);
// Remove docroot from filename
{
}
}
$temp .= $sep.'<span>'.Enc
::html(print_r($arg, TRUE)).'</span>';
// Change separator to a comma
$sep = ', ';
}
}
$temp .= ' )</pre></li>';
$output[] = $temp;
}
return '<ul class="backtrace">'.implode("\n", $output).'</ul>'; }
/**
* Saves the internal caches: configuration, include paths, etc.
*
* @return boolean
*/
public static function internalCacheSave()
{
return FALSE;
// Get internal cache names
// Nothing written
$written = FALSE;
// The 'sprout' config is read from different skins based on the subsite
unset(self::$internal_cache['find_file_paths']['config/sprout.php']);
foreach ($caches as $cache)
{
if (isset(self::$internal_cache[$cache])) {
// Write the cache file
self::cacheSave($cache, self::$internal_cache[$cache]);
// A cache has been written
$written = TRUE;
}
}
return $written;
}
/**
* Ends the current output buffer with callback in mind
* PHP doesn't pass the output to the callback defined in ob_start() since 5.4
*
* @param callback $callback
* @return boolean
*/
protected static function _obEndClean($callback = NULL)
{
// Pre-5.4 ob_end_clean() will pass the buffer to the callback anyways
if ($callback === NULL)
{
}
}
} // End Kohana
/**
* Creates a generic i18n exception.
*/
class Kohana_Exception extends Exception
{
// Header
protected $header = FALSE;
// Error code
protected $code = E_KOHANA;
/**
* Set exception message.
*
* @param string i18n language key for the message
* @param array addition line parameters
*/
public function __construct($error)
{
// Fetch the error message
$message = Kohana::lang($error, $args);
if ($message === $error OR
empty($message)) {
// Unable to locate the message for the error
$message = 'Unknown Exception: '.$error;
}
// Sets $this->message the proper way
parent::__construct($message);
}
/**
* Magic method for converting an object to a string.
*
* @return string i18n message
*/
public function __toString()
{
return (string) $this->message;
}
/**
* Fetch the template name.
*
* @return string
*/
public function getTemplate()
{
return $this->template;
}
/**
* Sends an Internal Server Error header.
*
* @return void
*/
public function sendHeaders()
{
// Send the 500 header
header('HTTP/1.1 500 Internal Server Error'); }
} // End Kohana Exception
/**
* Creates a custom exception.
*/
class Kohana_User_Exception extends Kohana_Exception
{
/**
* Set exception title and message.
*
* @param string exception title string
* @param string exception message string
* @param string custom error template
*/
public function __construct($title, $message)
{
Exception::__construct($message);
$this->code = $title;
}
} // End Kohana PHP Exception
/**
* Creates a Page Not Found exception.
*/
class Kohana_404_Exception extends Kohana_Exception
{
protected $code = E_PAGE_NOT_FOUND;
/**
* Set internal properties.
*
* @param string URL of page
* @param string custom error template
*/
public function __construct($page = FALSE)
{
if ($page === FALSE)
{
// Construct the page URI using Router properties
$page = Router::$current_uri.Router::$url_suffix.Router::$query_string;
}
Exception::__construct(Kohana::lang('core.page_not_found', $page));
}
/**
* Sends "File Not Found" headers, to emulate server behavior.
*
* @return void
*/
public function sendHeaders()
{
// Send the 404 header
header('HTTP/1.1 404 File Not Found'); }
} // End Kohana 404 Exception
/**
* Recursive version of array_map function
* I would imagine would be much slower than the original. Only use if necessary
*
* @param mixed $callback Callback function, can be string, array or in PHP 5.3, a function
* @param array $array The array to process
**/
function arrayMapRecursive($callback, $array) {
foreach ($keys as $k)
{
$array[$k] = is_array($array[$k]) ? arrayMapRecursive
($callback, $array[$k]) : call_user_func($callback, $array[$k]); }
return $array;
}