Updated to KH 3.3 and improved

This commit is contained in:
Deon George
2013-04-13 16:17:56 +10:00
parent 6f50463ec7
commit 6f7913d363
1551 changed files with 96188 additions and 29813 deletions

View File

@@ -0,0 +1,620 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* Array helper.
*
* @package Kohana
* @category Helpers
* @author Kohana Team
* @copyright (c) 2007-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
class Kohana_Arr {
/**
* @var string default delimiter for path()
*/
public static $delimiter = '.';
/**
* Tests if an array is associative or not.
*
* // Returns TRUE
* Arr::is_assoc(array('username' => 'john.doe'));
*
* // Returns FALSE
* Arr::is_assoc('foo', 'bar');
*
* @param array $array array to check
* @return boolean
*/
public static function is_assoc(array $array)
{
// Keys of the array
$keys = array_keys($array);
// If the array keys of the keys match the keys, then the array must
// not be associative (e.g. the keys array looked like {0:0, 1:1...}).
return array_keys($keys) !== $keys;
}
/**
* Test if a value is an array with an additional check for array-like objects.
*
* // Returns TRUE
* Arr::is_array(array());
* Arr::is_array(new ArrayObject);
*
* // Returns FALSE
* Arr::is_array(FALSE);
* Arr::is_array('not an array!');
* Arr::is_array(Database::instance());
*
* @param mixed $value value to check
* @return boolean
*/
public static function is_array($value)
{
if (is_array($value))
{
// Definitely an array
return TRUE;
}
else
{
// Possibly a Traversable object, functionally the same as an array
return (is_object($value) AND $value instanceof Traversable);
}
}
/**
* Gets a value from an array using a dot separated path.
*
* // Get the value of $array['foo']['bar']
* $value = Arr::path($array, 'foo.bar');
*
* Using a wildcard "*" will search intermediate arrays and return an array.
*
* // Get the values of "color" in theme
* $colors = Arr::path($array, 'theme.*.color');
*
* // Using an array of keys
* $colors = Arr::path($array, array('theme', '*', 'color'));
*
* @param array $array array to search
* @param mixed $path key path string (delimiter separated) or array of keys
* @param mixed $default default value if the path is not set
* @param string $delimiter key path delimiter
* @return mixed
*/
public static function path($array, $path, $default = NULL, $delimiter = NULL)
{
if ( ! Arr::is_array($array))
{
// This is not an array!
return $default;
}
if (is_array($path))
{
// The path has already been separated into keys
$keys = $path;
}
else
{
if (array_key_exists($path, $array))
{
// No need to do extra processing
return $array[$path];
}
if ($delimiter === NULL)
{
// Use the default delimiter
$delimiter = Arr::$delimiter;
}
// Remove starting delimiters and spaces
$path = ltrim($path, "{$delimiter} ");
// Remove ending delimiters, spaces, and wildcards
$path = rtrim($path, "{$delimiter} *");
// Split the keys by delimiter
$keys = explode($delimiter, $path);
}
do
{
$key = array_shift($keys);
if (ctype_digit($key))
{
// Make the key an integer
$key = (int) $key;
}
if (isset($array[$key]))
{
if ($keys)
{
if (Arr::is_array($array[$key]))
{
// Dig down into the next part of the path
$array = $array[$key];
}
else
{
// Unable to dig deeper
break;
}
}
else
{
// Found the path requested
return $array[$key];
}
}
elseif ($key === '*')
{
// Handle wildcards
$values = array();
foreach ($array as $arr)
{
if ($value = Arr::path($arr, implode('.', $keys)))
{
$values[] = $value;
}
}
if ($values)
{
// Found the values requested
return $values;
}
else
{
// Unable to dig deeper
break;
}
}
else
{
// Unable to dig deeper
break;
}
}
while ($keys);
// Unable to find the value requested
return $default;
}
/**
* Set a value on an array by path.
*
* @see Arr::path()
* @param array $array Array to update
* @param string $path Path
* @param mixed $value Value to set
* @param string $delimiter Path delimiter
*/
public static function set_path( & $array, $path, $value, $delimiter = NULL)
{
if ( ! $delimiter)
{
// Use the default delimiter
$delimiter = Arr::$delimiter;
}
// Split the keys by delimiter
$keys = explode($delimiter, $path);
// Set current $array to inner-most array path
while (count($keys) > 1)
{
$key = array_shift($keys);
if (ctype_digit($key))
{
// Make the key an integer
$key = (int) $key;
}
if ( ! isset($array[$key]))
{
$array[$key] = array();
}
$array = & $array[$key];
}
// Set key on inner-most array
$array[array_shift($keys)] = $value;
}
/**
* Fill an array with a range of numbers.
*
* // Fill an array with values 5, 10, 15, 20
* $values = Arr::range(5, 20);
*
* @param integer $step stepping
* @param integer $max ending number
* @return array
*/
public static function range($step = 10, $max = 100)
{
if ($step < 1)
return array();
$array = array();
for ($i = $step; $i <= $max; $i += $step)
{
$array[$i] = $i;
}
return $array;
}
/**
* Retrieve a single key from an array. If the key does not exist in the
* array, the default value will be returned instead.
*
* // Get the value "username" from $_POST, if it exists
* $username = Arr::get($_POST, 'username');
*
* // Get the value "sorting" from $_GET, if it exists
* $sorting = Arr::get($_GET, 'sorting');
*
* @param array $array array to extract from
* @param string $key key name
* @param mixed $default default value
* @return mixed
*/
public static function get($array, $key, $default = NULL)
{
return isset($array[$key]) ? $array[$key] : $default;
}
/**
* Retrieves multiple paths from an array. If the path does not exist in the
* array, the default value will be added instead.
*
* // Get the values "username", "password" from $_POST
* $auth = Arr::extract($_POST, array('username', 'password'));
*
* // Get the value "level1.level2a" from $data
* $data = array('level1' => array('level2a' => 'value 1', 'level2b' => 'value 2'));
* Arr::extract($data, array('level1.level2a', 'password'));
*
* @param array $array array to extract paths from
* @param array $paths list of path
* @param mixed $default default value
* @return array
*/
public static function extract($array, array $paths, $default = NULL)
{
$found = array();
foreach ($paths as $path)
{
Arr::set_path($found, $path, Arr::path($array, $path, $default));
}
return $found;
}
/**
* Retrieves muliple single-key values from a list of arrays.
*
* // Get all of the "id" values from a result
* $ids = Arr::pluck($result, 'id');
*
* [!!] A list of arrays is an array that contains arrays, eg: array(array $a, array $b, array $c, ...)
*
* @param array $array list of arrays to check
* @param string $key key to pluck
* @return array
*/
public static function pluck($array, $key)
{
$values = array();
foreach ($array as $row)
{
if (isset($row[$key]))
{
// Found a value in this row
$values[] = $row[$key];
}
}
return $values;
}
/**
* Adds a value to the beginning of an associative array.
*
* // Add an empty value to the start of a select list
* Arr::unshift($array, 'none', 'Select a value');
*
* @param array $array array to modify
* @param string $key array key name
* @param mixed $val array value
* @return array
*/
public static function unshift( array & $array, $key, $val)
{
$array = array_reverse($array, TRUE);
$array[$key] = $val;
$array = array_reverse($array, TRUE);
return $array;
}
/**
* Recursive version of [array_map](http://php.net/array_map), applies one or more
* callbacks to all elements in an array, including sub-arrays.
*
* // Apply "strip_tags" to every element in the array
* $array = Arr::map('strip_tags', $array);
*
* // Apply $this->filter to every element in the array
* $array = Arr::map(array(array($this,'filter')), $array);
*
* // Apply strip_tags and $this->filter to every element
* $array = Arr::map(array('strip_tags',array($this,'filter')), $array);
*
* [!!] Because you can pass an array of callbacks, if you wish to use an array-form callback
* you must nest it in an additional array as above. Calling Arr::map(array($this,'filter'), $array)
* will cause an error.
* [!!] Unlike `array_map`, this method requires a callback and will only map
* a single array.
*
* @param mixed $callbacks array of callbacks to apply to every element in the array
* @param array $array array to map
* @param array $keys array of keys to apply to
* @return array
*/
public static function map($callbacks, $array, $keys = NULL)
{
foreach ($array as $key => $val)
{
if (is_array($val))
{
$array[$key] = Arr::map($callbacks, $array[$key]);
}
elseif ( ! is_array($keys) OR in_array($key, $keys))
{
if (is_array($callbacks))
{
foreach ($callbacks as $cb)
{
$array[$key] = call_user_func($cb, $array[$key]);
}
}
else
{
$array[$key] = call_user_func($callbacks, $array[$key]);
}
}
}
return $array;
}
/**
* Recursively merge two or more arrays. Values in an associative array
* overwrite previous values with the same key. Values in an indexed array
* are appended, but only when they do not already exist in the result.
*
* Note that this does not work the same as [array_merge_recursive](http://php.net/array_merge_recursive)!
*
* $john = array('name' => 'john', 'children' => array('fred', 'paul', 'sally', 'jane'));
* $mary = array('name' => 'mary', 'children' => array('jane'));
*
* // John and Mary are married, merge them together
* $john = Arr::merge($john, $mary);
*
* // The output of $john will now be:
* array('name' => 'mary', 'children' => array('fred', 'paul', 'sally', 'jane'))
*
* @param array $array1 initial array
* @param array $array2,... array to merge
* @return array
*/
public static function merge($array1, $array2)
{
if (Arr::is_assoc($array2))
{
foreach ($array2 as $key => $value)
{
if (is_array($value)
AND isset($array1[$key])
AND is_array($array1[$key])
)
{
$array1[$key] = Arr::merge($array1[$key], $value);
}
else
{
$array1[$key] = $value;
}
}
}
else
{
foreach ($array2 as $value)
{
if ( ! in_array($value, $array1, TRUE))
{
$array1[] = $value;
}
}
}
if (func_num_args() > 2)
{
foreach (array_slice(func_get_args(), 2) as $array2)
{
if (Arr::is_assoc($array2))
{
foreach ($array2 as $key => $value)
{
if (is_array($value)
AND isset($array1[$key])
AND is_array($array1[$key])
)
{
$array1[$key] = Arr::merge($array1[$key], $value);
}
else
{
$array1[$key] = $value;
}
}
}
else
{
foreach ($array2 as $value)
{
if ( ! in_array($value, $array1, TRUE))
{
$array1[] = $value;
}
}
}
}
}
return $array1;
}
/**
* Overwrites an array with values from input arrays.
* Keys that do not exist in the first array will not be added!
*
* $a1 = array('name' => 'john', 'mood' => 'happy', 'food' => 'bacon');
* $a2 = array('name' => 'jack', 'food' => 'tacos', 'drink' => 'beer');
*
* // Overwrite the values of $a1 with $a2
* $array = Arr::overwrite($a1, $a2);
*
* // The output of $array will now be:
* array('name' => 'jack', 'mood' => 'happy', 'food' => 'tacos')
*
* @param array $array1 master array
* @param array $array2 input arrays that will overwrite existing values
* @return array
*/
public static function overwrite($array1, $array2)
{
foreach (array_intersect_key($array2, $array1) as $key => $value)
{
$array1[$key] = $value;
}
if (func_num_args() > 2)
{
foreach (array_slice(func_get_args(), 2) as $array2)
{
foreach (array_intersect_key($array2, $array1) as $key => $value)
{
$array1[$key] = $value;
}
}
}
return $array1;
}
/**
* Creates a callable function and parameter list from a string representation.
* Note that this function does not validate the callback string.
*
* // Get the callback function and parameters
* list($func, $params) = Arr::callback('Foo::bar(apple,orange)');
*
* // Get the result of the callback
* $result = call_user_func_array($func, $params);
*
* @param string $str callback string
* @return array function, params
*/
public static function callback($str)
{
// Overloaded as parts are found
$command = $params = NULL;
// command[param,param]
if (preg_match('/^([^\(]*+)\((.*)\)$/', $str, $match))
{
// command
$command = $match[1];
if ($match[2] !== '')
{
// param,param
$params = preg_split('/(?<!\\\\),/', $match[2]);
$params = str_replace('\,', ',', $params);
}
}
else
{
// command
$command = $str;
}
if (strpos($command, '::') !== FALSE)
{
// Create a static method callable command
$command = explode('::', $command, 2);
}
return array($command, $params);
}
/**
* Convert a multi-dimensional array into a single-dimensional array.
*
* $array = array('set' => array('one' => 'something'), 'two' => 'other');
*
* // Flatten the array
* $array = Arr::flatten($array);
*
* // The array will now be
* array('one' => 'something', 'two' => 'other');
*
* [!!] The keys of array values will be discarded.
*
* @param array $array array to flatten
* @return array
* @since 3.0.6
*/
public static function flatten($array)
{
$is_assoc = Arr::is_assoc($array);
$flat = array();
foreach ($array as $key => $value)
{
if (is_array($value))
{
$flat = array_merge($flat, Arr::flatten($value));
}
else
{
if ($is_assoc)
{
$flat[$key] = $value;
}
else
{
$flat[] = $value;
}
}
}
return $flat;
}
} // End arr

View File

@@ -0,0 +1,192 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* Wrapper for configuration arrays. Multiple configuration readers can be
* attached to allow loading configuration from files, database, etc.
*
* Configuration directives cascade across config sources in the same way that
* files cascade across the filesystem.
*
* Directives from sources high in the sources list will override ones from those
* below them.
*
* @package Kohana
* @category Configuration
* @author Kohana Team
* @copyright (c) 2009-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
class Kohana_Config {
// Configuration readers
protected $_sources = array();
// Array of config groups
protected $_groups = array();
/**
* Attach a configuration reader. By default, the reader will be added as
* the first used reader. However, if the reader should be used only when
* all other readers fail, use `FALSE` for the second parameter.
*
* $config->attach($reader); // Try first
* $config->attach($reader, FALSE); // Try last
*
* @param Kohana_Config_Source $source instance
* @param boolean $first add the reader as the first used object
* @return $this
*/
public function attach(Kohana_Config_Source $source, $first = TRUE)
{
if ($first === TRUE)
{
// Place the log reader at the top of the stack
array_unshift($this->_sources, $source);
}
else
{
// Place the reader at the bottom of the stack
$this->_sources[] = $source;
}
// Clear any cached _groups
$this->_groups = array();
return $this;
}
/**
* Detach a configuration reader.
*
* $config->detach($reader);
*
* @param Kohana_Config_Source $source instance
* @return $this
*/
public function detach(Kohana_Config_Source $source)
{
if (($key = array_search($source, $this->_sources)) !== FALSE)
{
// Remove the writer
unset($this->_sources[$key]);
}
return $this;
}
/**
* Load a configuration group. Searches all the config sources, merging all the
* directives found into a single config group. Any changes made to the config
* in this group will be mirrored across all writable sources.
*
* $array = $config->load($name);
*
* See [Kohana_Config_Group] for more info
*
* @param string $group configuration group name
* @return Kohana_Config_Group
* @throws Kohana_Exception
*/
public function load($group)
{
if ( ! count($this->_sources))
{
throw new Kohana_Exception('No configuration sources attached');
}
if (empty($group))
{
throw new Kohana_Exception("Need to specify a config group");
}
if ( ! is_string($group))
{
throw new Kohana_Exception("Config group must be a string");
}
if (strpos($group, '.') !== FALSE)
{
// Split the config group and path
list($group, $path) = explode('.', $group, 2);
}
if (isset($this->_groups[$group]))
{
if (isset($path))
{
return Arr::path($this->_groups[$group], $path, NULL, '.');
}
return $this->_groups[$group];
}
$config = array();
// We search from the "lowest" source and work our way up
$sources = array_reverse($this->_sources);
foreach ($sources as $source)
{
if ($source instanceof Kohana_Config_Reader)
{
if ($source_config = $source->load($group))
{
$config = Arr::merge($config, $source_config);
}
}
}
$this->_groups[$group] = new Config_Group($this, $group, $config);
if (isset($path))
{
return Arr::path($config, $path, NULL, '.');
}
return $this->_groups[$group];
}
/**
* Copy one configuration group to all of the other writers.
*
* $config->copy($name);
*
* @param string $group configuration group name
* @return $this
*/
public function copy($group)
{
// Load the configuration group
$config = $this->load($group);
foreach ($config->as_array() as $key => $value)
{
$this->_write_config($group, $key, $value);
}
return $this;
}
/**
* Callback used by the config group to store changes made to configuration
*
* @param string $group Group name
* @param string $key Variable name
* @param mixed $value The new value
* @return Kohana_Config Chainable instance
*/
public function _write_config($group, $key, $value)
{
foreach ($this->_sources as $source)
{
if ( ! ($source instanceof Kohana_Config_Writer))
{
continue;
}
// Copy each value in the config
$source->write($group, $key, $value);
}
return $this;
}
} // End Kohana_Config

View File

@@ -0,0 +1,15 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* File-based configuration reader. Multiple configuration directories can be
* used by attaching multiple instances of this class to [Config].
*
* @package Kohana
* @category Configuration
* @author Kohana Team
* @copyright (c) 2009-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
class Kohana_Config_File extends Kohana_Config_File_Reader
{
// @see Kohana_Config_File_Reader
}

View File

@@ -0,0 +1,56 @@
<?php
/**
* File-based configuration reader. Multiple configuration directories can be
* used by attaching multiple instances of this class to [Kohana_Config].
*
* @package Kohana
* @category Configuration
* @author Kohana Team
* @copyright (c) 2009-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
class Kohana_Config_File_Reader implements Kohana_Config_Reader {
/**
* The directory where config files are located
* @var string
*/
protected $_directory = '';
/**
* Creates a new file reader using the given directory as a config source
*
* @param string $directory Configuration directory to search
*/
public function __construct($directory = 'config')
{
// Set the configuration directory name
$this->_directory = trim($directory, '/');
}
/**
* Load and merge all of the configuration files in this group.
*
* $config->load($name);
*
* @param string $group configuration group name
* @return $this current object
* @uses Kohana::load
*/
public function load($group)
{
$config = array();
if ($files = Kohana::find_file($this->_directory, $group, NULL, TRUE))
{
foreach ($files as $file)
{
// Merge each file to the configuration array
$config = Arr::merge($config, Kohana::load($file));
}
}
return $config;
}
} // End Kohana_Config

View File

@@ -0,0 +1,130 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* The group wrapper acts as an interface to all the config directives
* gathered from across the system.
*
* This is the object returned from Kohana_Config::load
*
* Any modifications to configuration items should be done through an instance of this object
*
* @package Kohana
* @category Configuration
* @author Kohana Team
* @copyright (c) 2012 Kohana Team
* @license http://kohanaphp.com/license
*/
class Kohana_Config_Group extends ArrayObject {
/**
* Reference the config object that created this group
* Used when updating config
* @var Kohana_Config
*/
protected $_parent_instance = NULL;
/**
* The group this config is for
* Used when updating config items
* @var string
*/
protected $_group_name = '';
/**
* Constructs the group object. Kohana_Config passes the config group
* and its config items to the object here.
*
* @param Kohana_Config $instance "Owning" instance of Kohana_Config
* @param string $group The group name
* @param array $config Group's config
*/
public function __construct(Kohana_Config $instance, $group, array $config = array())
{
$this->_parent_instance = $instance;
$this->_group_name = $group;
parent::__construct($config, ArrayObject::ARRAY_AS_PROPS);
}
/**
* Return the current group in serialized form.
*
* echo $config;
*
* @return string
*/
public function __toString()
{
return serialize($this->getArrayCopy());
}
/**
* Alias for getArrayCopy()
*
* @return array Array copy of the group's config
*/
public function as_array()
{
return $this->getArrayCopy();
}
/**
* Returns the config group's name
*
* @return string The group name
*/
public function group_name()
{
return $this->_group_name;
}
/**
* Get a variable from the configuration or return the default value.
*
* $value = $config->get($key);
*
* @param string $key array key
* @param mixed $default default value
* @return mixed
*/
public function get($key, $default = NULL)
{
return $this->offsetExists($key) ? $this->offsetGet($key) : $default;
}
/**
* Sets a value in the configuration array.
*
* $config->set($key, $new_value);
*
* @param string $key array key
* @param mixed $value array value
* @return $this
*/
public function set($key, $value)
{
$this->offsetSet($key, $value);
return $this;
}
/**
* Overrides ArrayObject::offsetSet()
* This method is called when config is changed via
*
* $config->var = 'asd';
*
* // OR
*
* $config['var'] = 'asd';
*
* @param string $key The key of the config item we're changing
* @param mixed $value The new array value
*/
public function offsetSet($key, $value)
{
$this->_parent_instance->_write_config($this->_group_name, $key, $value);
return parent::offsetSet($key, $value);
}
}

View File

@@ -0,0 +1,25 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* Interface for config readers
*
* @package Kohana
* @category Configuration
* @author Kohana Team
* @copyright (c) 2008-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
interface Kohana_Config_Reader extends Kohana_Config_Source
{
/**
* Tries to load the specificed configuration group
*
* Returns FALSE if group does not exist or an array if it does
*
* @param string $group Configuration group
* @return boolean|array
*/
public function load($group);
}

View File

@@ -0,0 +1,14 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* Base Config source Interface
*
* Used to identify either config readers or writers when calling [Kohana_Config::attach()]
*
* @package Kohana
* @category Configuration
* @author Kohana Team
* @copyright (c) 2012 Kohana Team
* @license http://kohanaphp.com/license
*/
interface Kohana_Config_Source {}

View File

@@ -0,0 +1,27 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* Interface for config writers
*
* Specifies the methods that a config writer must implement
*
* @package Kohana
* @author Kohana Team
* @copyright (c) 2008-2012 Kohana Team
* @license http://kohanaphp.com/license
*/
interface Kohana_Config_Writer extends Kohana_Config_Source
{
/**
* Writes the passed config for $group
*
* Returns chainable instance on success or throws
* Kohana_Config_Exception on failure
*
* @param string $group The config group
* @param string $key The config key to write to
* @param array $config The configuration to write
* @return boolean
*/
public function write($group, $key, $config);
}

View File

@@ -0,0 +1,145 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* Abstract controller class. Controllers should only be created using a [Request].
*
* Controllers methods will be automatically called in the following order by
* the request:
*
* $controller = new Controller_Foo($request);
* $controller->before();
* $controller->action_bar();
* $controller->after();
*
* The controller action should add the output it creates to
* `$this->response->body($output)`, typically in the form of a [View], during the
* "action" part of execution.
*
* @package Kohana
* @category Controller
* @author Kohana Team
* @copyright (c) 2008-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
abstract class Kohana_Controller {
/**
* @var Request Request that created the controller
*/
public $request;
/**
* @var Response The response that will be returned from controller
*/
public $response;
/**
* Creates a new controller instance. Each controller must be constructed
* with the request object that created it.
*
* @param Request $request Request that created the controller
* @param Response $response The request's response
* @return void
*/
public function __construct(Request $request, Response $response)
{
// Assign the request to the controller
$this->request = $request;
// Assign a response to the controller
$this->response = $response;
}
/**
* Executes the given action and calls the [Controller::before] and [Controller::after] methods.
*
* Can also be used to catch exceptions from actions in a single place.
*
* 1. Before the controller action is called, the [Controller::before] method
* will be called.
* 2. Next the controller action will be called.
* 3. After the controller action is called, the [Controller::after] method
* will be called.
*
* @throws HTTP_Exception_404
* @return Response
*/
public function execute()
{
// Execute the "before action" method
$this->before();
// Determine the action to use
$action = 'action_'.$this->request->action();
// If the action doesn't exist, it's a 404
if ( ! method_exists($this, $action))
{
throw HTTP_Exception::factory(404,
'The requested URL :uri was not found on this server.',
array(':uri' => $this->request->uri())
)->request($this->request);
}
// Execute the action itself
$this->{$action}();
// Execute the "after action" method
$this->after();
// Return the response
return $this->response;
}
/**
* Automatically executed before the controller action. Can be used to set
* class properties, do authorization checks, and execute other custom code.
*
* @return void
*/
public function before()
{
// Nothing by default
}
/**
* Automatically executed after the controller action. Can be used to apply
* transformation to the response, add extra output, and execute
* other custom code.
*
* @return void
*/
public function after()
{
// Nothing by default
}
/**
* Issues a HTTP redirect.
*
* Proxies to the [HTTP::redirect] method.
*
* @param string $uri URI to redirect to
* @param int $code HTTP Status code to use for the redirect
* @throws HTTP_Exception
*/
public static function redirect($uri = '', $code = 302)
{
return HTTP::redirect($uri, $code);
}
/**
* Checks the browser cache to see the response needs to be returned,
* execution will halt and a 304 Not Modified will be sent if the
* browser cache is up to date.
*
* $this->check_cache(sha1($content));
*
* @param string $etag Resource Etag
* @return Response
*/
protected function check_cache($etag = NULL)
{
return HTTP::check_cache($this->request, $this->response, $etag);
}
} // End Controller

View File

@@ -0,0 +1,50 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* Abstract controller class for automatic templating.
*
* @package Kohana
* @category Controller
* @author Kohana Team
* @copyright (c) 2008-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
abstract class Kohana_Controller_Template extends Controller {
/**
* @var View page template
*/
public $template = 'template';
/**
* @var boolean auto render template
**/
public $auto_render = TRUE;
/**
* Loads the template [View] object.
*/
public function before()
{
parent::before();
if ($this->auto_render === TRUE)
{
// Load the template
$this->template = View::factory($this->template);
}
}
/**
* Assigns the template [View] as the request response.
*/
public function after()
{
if ($this->auto_render === TRUE)
{
$this->response->body($this->template->render());
}
parent::after();
}
} // End Controller_Template

View File

@@ -0,0 +1,161 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* Cookie helper.
*
* @package Kohana
* @category Helpers
* @author Kohana Team
* @copyright (c) 2008-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
class Kohana_Cookie {
/**
* @var string Magic salt to add to the cookie
*/
public static $salt = NULL;
/**
* @var integer Number of seconds before the cookie expires
*/
public static $expiration = 0;
/**
* @var string Restrict the path that the cookie is available to
*/
public static $path = '/';
/**
* @var string Restrict the domain that the cookie is available to
*/
public static $domain = NULL;
/**
* @var boolean Only transmit cookies over secure connections
*/
public static $secure = FALSE;
/**
* @var boolean Only transmit cookies over HTTP, disabling Javascript access
*/
public static $httponly = FALSE;
/**
* Gets the value of a signed cookie. Cookies without signatures will not
* be returned. If the cookie signature is present, but invalid, the cookie
* will be deleted.
*
* // Get the "theme" cookie, or use "blue" if the cookie does not exist
* $theme = Cookie::get('theme', 'blue');
*
* @param string $key cookie name
* @param mixed $default default value to return
* @return string
*/
public static function get($key, $default = NULL)
{
if ( ! isset($_COOKIE[$key]))
{
// The cookie does not exist
return $default;
}
// Get the cookie value
$cookie = $_COOKIE[$key];
// Find the position of the split between salt and contents
$split = strlen(Cookie::salt($key, NULL));
if (isset($cookie[$split]) AND $cookie[$split] === '~')
{
// Separate the salt and the value
list ($hash, $value) = explode('~', $cookie, 2);
if (Cookie::salt($key, $value) === $hash)
{
// Cookie signature is valid
return $value;
}
// The cookie signature is invalid, delete it
Cookie::delete($key);
}
return $default;
}
/**
* Sets a signed cookie. Note that all cookie values must be strings and no
* automatic serialization will be performed!
*
* // Set the "theme" cookie
* Cookie::set('theme', 'red');
*
* @param string $name name of cookie
* @param string $value value of cookie
* @param integer $expiration lifetime in seconds
* @return boolean
* @uses Cookie::salt
*/
public static function set($name, $value, $expiration = NULL)
{
if ($expiration === NULL)
{
// Use the default expiration
$expiration = Cookie::$expiration;
}
if ($expiration !== 0)
{
// The expiration is expected to be a UNIX timestamp
$expiration += time();
}
// Add the salt to the cookie value
$value = Cookie::salt($name, $value).'~'.$value;
return setcookie($name, $value, $expiration, Cookie::$path, Cookie::$domain, Cookie::$secure, Cookie::$httponly);
}
/**
* Deletes a cookie by making the value NULL and expiring it.
*
* Cookie::delete('theme');
*
* @param string $name cookie name
* @return boolean
* @uses Cookie::set
*/
public static function delete($name)
{
// Remove the cookie
unset($_COOKIE[$name]);
// Nullify the cookie and make it expire
return setcookie($name, NULL, -86400, Cookie::$path, Cookie::$domain, Cookie::$secure, Cookie::$httponly);
}
/**
* Generates a salt string for a cookie based on the name and value.
*
* $salt = Cookie::salt('theme', 'red');
*
* @param string $name name of cookie
* @param string $value value of cookie
* @return string
*/
public static function salt($name, $value)
{
// Require a valid salt
if ( ! Cookie::$salt)
{
throw new Kohana_Exception('A valid cookie salt is required. Please set Cookie::$salt.');
}
// Determine the user agent
$agent = isset($_SERVER['HTTP_USER_AGENT']) ? strtolower($_SERVER['HTTP_USER_AGENT']) : 'unknown';
return sha1($agent.$name.$value.Cookie::$salt);
}
} // End cookie

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,603 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* Date helper.
*
* @package Kohana
* @category Helpers
* @author Kohana Team
* @copyright (c) 2007-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
class Kohana_Date {
// Second amounts for various time increments
const YEAR = 31556926;
const MONTH = 2629744;
const WEEK = 604800;
const DAY = 86400;
const HOUR = 3600;
const MINUTE = 60;
// Available formats for Date::months()
const MONTHS_LONG = '%B';
const MONTHS_SHORT = '%b';
/**
* Default timestamp format for formatted_time
* @var string
*/
public static $timestamp_format = 'Y-m-d H:i:s';
/**
* Timezone for formatted_time
* @link http://uk2.php.net/manual/en/timezones.php
* @var string
*/
public static $timezone;
/**
* Returns the offset (in seconds) between two time zones. Use this to
* display dates to users in different time zones.
*
* $seconds = Date::offset('America/Chicago', 'GMT');
*
* [!!] A list of time zones that PHP supports can be found at
* <http://php.net/timezones>.
*
* @param string $remote timezone that to find the offset of
* @param string $local timezone used as the baseline
* @param mixed $now UNIX timestamp or date string
* @return integer
*/
public static function offset($remote, $local = NULL, $now = NULL)
{
if ($local === NULL)
{
// Use the default timezone
$local = date_default_timezone_get();
}
if (is_int($now))
{
// Convert the timestamp into a string
$now = date(DateTime::RFC2822, $now);
}
// Create timezone objects
$zone_remote = new DateTimeZone($remote);
$zone_local = new DateTimeZone($local);
// Create date objects from timezones
$time_remote = new DateTime($now, $zone_remote);
$time_local = new DateTime($now, $zone_local);
// Find the offset
$offset = $zone_remote->getOffset($time_remote) - $zone_local->getOffset($time_local);
return $offset;
}
/**
* Number of seconds in a minute, incrementing by a step. Typically used as
* a shortcut for generating a list that can used in a form.
*
* $seconds = Date::seconds(); // 01, 02, 03, ..., 58, 59, 60
*
* @param integer $step amount to increment each step by, 1 to 30
* @param integer $start start value
* @param integer $end end value
* @return array A mirrored (foo => foo) array from 1-60.
*/
public static function seconds($step = 1, $start = 0, $end = 60)
{
// Always integer
$step = (int) $step;
$seconds = array();
for ($i = $start; $i < $end; $i += $step)
{
$seconds[$i] = sprintf('%02d', $i);
}
return $seconds;
}
/**
* Number of minutes in an hour, incrementing by a step. Typically used as
* a shortcut for generating a list that can be used in a form.
*
* $minutes = Date::minutes(); // 05, 10, 15, ..., 50, 55, 60
*
* @uses Date::seconds
* @param integer $step amount to increment each step by, 1 to 30
* @return array A mirrored (foo => foo) array from 1-60.
*/
public static function minutes($step = 5)
{
// Because there are the same number of minutes as seconds in this set,
// we choose to re-use seconds(), rather than creating an entirely new
// function. Shhhh, it's cheating! ;) There are several more of these
// in the following methods.
return Date::seconds($step);
}
/**
* Number of hours in a day. Typically used as a shortcut for generating a
* list that can be used in a form.
*
* $hours = Date::hours(); // 01, 02, 03, ..., 10, 11, 12
*
* @param integer $step amount to increment each step by
* @param boolean $long use 24-hour time
* @param integer $start the hour to start at
* @return array A mirrored (foo => foo) array from start-12 or start-23.
*/
public static function hours($step = 1, $long = FALSE, $start = NULL)
{
// Default values
$step = (int) $step;
$long = (bool) $long;
$hours = array();
// Set the default start if none was specified.
if ($start === NULL)
{
$start = ($long === FALSE) ? 1 : 0;
}
$hours = array();
// 24-hour time has 24 hours, instead of 12
$size = ($long === TRUE) ? 23 : 12;
for ($i = $start; $i <= $size; $i += $step)
{
$hours[$i] = (string) $i;
}
return $hours;
}
/**
* Returns AM or PM, based on a given hour (in 24 hour format).
*
* $type = Date::ampm(12); // PM
* $type = Date::ampm(1); // AM
*
* @param integer $hour number of the hour
* @return string
*/
public static function ampm($hour)
{
// Always integer
$hour = (int) $hour;
return ($hour > 11) ? 'PM' : 'AM';
}
/**
* Adjusts a non-24-hour number into a 24-hour number.
*
* $hour = Date::adjust(3, 'pm'); // 15
*
* @param integer $hour hour to adjust
* @param string $ampm AM or PM
* @return string
*/
public static function adjust($hour, $ampm)
{
$hour = (int) $hour;
$ampm = strtolower($ampm);
switch ($ampm)
{
case 'am':
if ($hour == 12)
{
$hour = 0;
}
break;
case 'pm':
if ($hour < 12)
{
$hour += 12;
}
break;
}
return sprintf('%02d', $hour);
}
/**
* Number of days in a given month and year. Typically used as a shortcut
* for generating a list that can be used in a form.
*
* Date::days(4, 2010); // 1, 2, 3, ..., 28, 29, 30
*
* @param integer $month number of month
* @param integer $year number of year to check month, defaults to the current year
* @return array A mirrored (foo => foo) array of the days.
*/
public static function days($month, $year = FALSE)
{
static $months;
if ($year === FALSE)
{
// Use the current year by default
$year = date('Y');
}
// Always integers
$month = (int) $month;
$year = (int) $year;
// We use caching for months, because time functions are used
if (empty($months[$year][$month]))
{
$months[$year][$month] = array();
// Use date to find the number of days in the given month
$total = date('t', mktime(1, 0, 0, $month, 1, $year)) + 1;
for ($i = 1; $i < $total; $i++)
{
$months[$year][$month][$i] = (string) $i;
}
}
return $months[$year][$month];
}
/**
* Number of months in a year. Typically used as a shortcut for generating
* a list that can be used in a form.
*
* By default a mirrored array of $month_number => $month_number is returned
*
* Date::months();
* // aray(1 => 1, 2 => 2, 3 => 3, ..., 12 => 12)
*
* But you can customise this by passing in either Date::MONTHS_LONG
*
* Date::months(Date::MONTHS_LONG);
* // array(1 => 'January', 2 => 'February', ..., 12 => 'December')
*
* Or Date::MONTHS_SHORT
*
* Date::months(Date::MONTHS_SHORT);
* // array(1 => 'Jan', 2 => 'Feb', ..., 12 => 'Dec')
*
* @uses Date::hours
* @param string $format The format to use for months
* @return array An array of months based on the specified format
*/
public static function months($format = NULL)
{
$months = array();
if ($format === Date::MONTHS_LONG OR $format === Date::MONTHS_SHORT)
{
for ($i = 1; $i <= 12; ++$i)
{
$months[$i] = strftime($format, mktime(0, 0, 0, $i, 1));
}
}
else
{
$months = Date::hours();
}
return $months;
}
/**
* Returns an array of years between a starting and ending year. By default,
* the the current year - 5 and current year + 5 will be used. Typically used
* as a shortcut for generating a list that can be used in a form.
*
* $years = Date::years(2000, 2010); // 2000, 2001, ..., 2009, 2010
*
* @param integer $start starting year (default is current year - 5)
* @param integer $end ending year (default is current year + 5)
* @return array
*/
public static function years($start = FALSE, $end = FALSE)
{
// Default values
$start = ($start === FALSE) ? (date('Y') - 5) : (int) $start;
$end = ($end === FALSE) ? (date('Y') + 5) : (int) $end;
$years = array();
for ($i = $start; $i <= $end; $i++)
{
$years[$i] = (string) $i;
}
return $years;
}
/**
* Returns time difference between two timestamps, in human readable format.
* If the second timestamp is not given, the current time will be used.
* Also consider using [Date::fuzzy_span] when displaying a span.
*
* $span = Date::span(60, 182, 'minutes,seconds'); // array('minutes' => 2, 'seconds' => 2)
* $span = Date::span(60, 182, 'minutes'); // 2
*
* @param integer $remote timestamp to find the span of
* @param integer $local timestamp to use as the baseline
* @param string $output formatting string
* @return string when only a single output is requested
* @return array associative list of all outputs requested
*/
public static function span($remote, $local = NULL, $output = 'years,months,weeks,days,hours,minutes,seconds')
{
// Normalize output
$output = trim(strtolower( (string) $output));
if ( ! $output)
{
// Invalid output
return FALSE;
}
// Array with the output formats
$output = preg_split('/[^a-z]+/', $output);
// Convert the list of outputs to an associative array
$output = array_combine($output, array_fill(0, count($output), 0));
// Make the output values into keys
extract(array_flip($output), EXTR_SKIP);
if ($local === NULL)
{
// Calculate the span from the current time
$local = time();
}
// Calculate timespan (seconds)
$timespan = abs($remote - $local);
if (isset($output['years']))
{
$timespan -= Date::YEAR * ($output['years'] = (int) floor($timespan / Date::YEAR));
}
if (isset($output['months']))
{
$timespan -= Date::MONTH * ($output['months'] = (int) floor($timespan / Date::MONTH));
}
if (isset($output['weeks']))
{
$timespan -= Date::WEEK * ($output['weeks'] = (int) floor($timespan / Date::WEEK));
}
if (isset($output['days']))
{
$timespan -= Date::DAY * ($output['days'] = (int) floor($timespan / Date::DAY));
}
if (isset($output['hours']))
{
$timespan -= Date::HOUR * ($output['hours'] = (int) floor($timespan / Date::HOUR));
}
if (isset($output['minutes']))
{
$timespan -= Date::MINUTE * ($output['minutes'] = (int) floor($timespan / Date::MINUTE));
}
// Seconds ago, 1
if (isset($output['seconds']))
{
$output['seconds'] = $timespan;
}
if (count($output) === 1)
{
// Only a single output was requested, return it
return array_pop($output);
}
// Return array
return $output;
}
/**
* Returns the difference between a time and now in a "fuzzy" way.
* Displaying a fuzzy time instead of a date is usually faster to read and understand.
*
* $span = Date::fuzzy_span(time() - 10); // "moments ago"
* $span = Date::fuzzy_span(time() + 20); // "in moments"
*
* A second parameter is available to manually set the "local" timestamp,
* however this parameter shouldn't be needed in normal usage and is only
* included for unit tests
*
* @param integer $timestamp "remote" timestamp
* @param integer $local_timestamp "local" timestamp, defaults to time()
* @return string
*/
public static function fuzzy_span($timestamp, $local_timestamp = NULL)
{
$local_timestamp = ($local_timestamp === NULL) ? time() : (int) $local_timestamp;
// Determine the difference in seconds
$offset = abs($local_timestamp - $timestamp);
if ($offset <= Date::MINUTE)
{
$span = 'moments';
}
elseif ($offset < (Date::MINUTE * 20))
{
$span = 'a few minutes';
}
elseif ($offset < Date::HOUR)
{
$span = 'less than an hour';
}
elseif ($offset < (Date::HOUR * 4))
{
$span = 'a couple of hours';
}
elseif ($offset < Date::DAY)
{
$span = 'less than a day';
}
elseif ($offset < (Date::DAY * 2))
{
$span = 'about a day';
}
elseif ($offset < (Date::DAY * 4))
{
$span = 'a couple of days';
}
elseif ($offset < Date::WEEK)
{
$span = 'less than a week';
}
elseif ($offset < (Date::WEEK * 2))
{
$span = 'about a week';
}
elseif ($offset < Date::MONTH)
{
$span = 'less than a month';
}
elseif ($offset < (Date::MONTH * 2))
{
$span = 'about a month';
}
elseif ($offset < (Date::MONTH * 4))
{
$span = 'a couple of months';
}
elseif ($offset < Date::YEAR)
{
$span = 'less than a year';
}
elseif ($offset < (Date::YEAR * 2))
{
$span = 'about a year';
}
elseif ($offset < (Date::YEAR * 4))
{
$span = 'a couple of years';
}
elseif ($offset < (Date::YEAR * 8))
{
$span = 'a few years';
}
elseif ($offset < (Date::YEAR * 12))
{
$span = 'about a decade';
}
elseif ($offset < (Date::YEAR * 24))
{
$span = 'a couple of decades';
}
elseif ($offset < (Date::YEAR * 64))
{
$span = 'several decades';
}
else
{
$span = 'a long time';
}
if ($timestamp <= $local_timestamp)
{
// This is in the past
return $span.' ago';
}
else
{
// This in the future
return 'in '.$span;
}
}
/**
* Converts a UNIX timestamp to DOS format. There are very few cases where
* this is needed, but some binary formats use it (eg: zip files.)
* Converting the other direction is done using {@link Date::dos2unix}.
*
* $dos = Date::unix2dos($unix);
*
* @param integer $timestamp UNIX timestamp
* @return integer
*/
public static function unix2dos($timestamp = FALSE)
{
$timestamp = ($timestamp === FALSE) ? getdate() : getdate($timestamp);
if ($timestamp['year'] < 1980)
{
return (1 << 21 | 1 << 16);
}
$timestamp['year'] -= 1980;
// What voodoo is this? I have no idea... Geert can explain it though,
// and that's good enough for me.
return ($timestamp['year'] << 25 | $timestamp['mon'] << 21 |
$timestamp['mday'] << 16 | $timestamp['hours'] << 11 |
$timestamp['minutes'] << 5 | $timestamp['seconds'] >> 1);
}
/**
* Converts a DOS timestamp to UNIX format.There are very few cases where
* this is needed, but some binary formats use it (eg: zip files.)
* Converting the other direction is done using {@link Date::unix2dos}.
*
* $unix = Date::dos2unix($dos);
*
* @param integer $timestamp DOS timestamp
* @return integer
*/
public static function dos2unix($timestamp = FALSE)
{
$sec = 2 * ($timestamp & 0x1f);
$min = ($timestamp >> 5) & 0x3f;
$hrs = ($timestamp >> 11) & 0x1f;
$day = ($timestamp >> 16) & 0x1f;
$mon = ($timestamp >> 21) & 0x0f;
$year = ($timestamp >> 25) & 0x7f;
return mktime($hrs, $min, $sec, $mon, $day, $year + 1980);
}
/**
* Returns a date/time string with the specified timestamp format
*
* $time = Date::formatted_time('5 minutes ago');
*
* @link http://www.php.net/manual/datetime.construct
* @param string $datetime_str datetime string
* @param string $timestamp_format timestamp format
* @param string $timezone timezone identifier
* @return string
*/
public static function formatted_time($datetime_str = 'now', $timestamp_format = NULL, $timezone = NULL)
{
$timestamp_format = ($timestamp_format == NULL) ? Date::$timestamp_format : $timestamp_format;
$timezone = ($timezone === NULL) ? Date::$timezone : $timezone;
$tz = new DateTimeZone($timezone ? $timezone : date_default_timezone_get());
$time = new DateTime($datetime_str, $tz);
if ($time->getTimeZone()->getName() !== $tz->getName())
{
$time->setTimeZone($tz);
}
return $time->format($timestamp_format);
}
} // End date

View File

@@ -0,0 +1,469 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* Contains debugging and dumping tools.
*
* @package Kohana
* @category Base
* @author Kohana Team
* @copyright (c) 2008-2012 Kohana Team
* @license http://kohanaphp.com/license
*/
class Kohana_Debug {
/**
* Returns an HTML string of debugging information about any number of
* variables, each wrapped in a "pre" tag:
*
* // Displays the type and value of each variable
* echo Debug::vars($foo, $bar, $baz);
*
* @param mixed $var,... variable to debug
* @return string
*/
public static function vars()
{
if (func_num_args() === 0)
return;
// Get all passed variables
$variables = func_get_args();
$output = array();
foreach ($variables as $var)
{
$output[] = Debug::_dump($var, 1024);
}
return '<pre class="debug">'.implode("\n", $output).'</pre>';
}
/**
* Returns an HTML string of information about a single variable.
*
* Borrows heavily on concepts from the Debug class of [Nette](http://nettephp.com/).
*
* @param mixed $value variable to dump
* @param integer $length maximum length of strings
* @param integer $level_recursion recursion limit
* @return string
*/
public static function dump($value, $length = 128, $level_recursion = 10)
{
return Debug::_dump($value, $length, $level_recursion);
}
/**
* Helper for Debug::dump(), handles recursion in arrays and objects.
*
* @param mixed $var variable to dump
* @param integer $length maximum length of strings
* @param integer $limit recursion limit
* @param integer $level current recursion level (internal usage only!)
* @return string
*/
protected static function _dump( & $var, $length = 128, $limit = 10, $level = 0)
{
if ($var === NULL)
{
return '<small>NULL</small>';
}
elseif (is_bool($var))
{
return '<small>bool</small> '.($var ? 'TRUE' : 'FALSE');
}
elseif (is_float($var))
{
return '<small>float</small> '.$var;
}
elseif (is_resource($var))
{
if (($type = get_resource_type($var)) === 'stream' AND $meta = stream_get_meta_data($var))
{
$meta = stream_get_meta_data($var);
if (isset($meta['uri']))
{
$file = $meta['uri'];
if (function_exists('stream_is_local'))
{
// Only exists on PHP >= 5.2.4
if (stream_is_local($file))
{
$file = Debug::path($file);
}
}
return '<small>resource</small><span>('.$type.')</span> '.htmlspecialchars($file, ENT_NOQUOTES, Kohana::$charset);
}
}
else
{
return '<small>resource</small><span>('.$type.')</span>';
}
}
elseif (is_string($var))
{
// Clean invalid multibyte characters. iconv is only invoked
// if there are non ASCII characters in the string, so this
// isn't too much of a hit.
$var = UTF8::clean($var, Kohana::$charset);
if (UTF8::strlen($var) > $length)
{
// Encode the truncated string
$str = htmlspecialchars(UTF8::substr($var, 0, $length), ENT_NOQUOTES, Kohana::$charset).'&nbsp;&hellip;';
}
else
{
// Encode the string
$str = htmlspecialchars($var, ENT_NOQUOTES, Kohana::$charset);
}
return '<small>string</small><span>('.strlen($var).')</span> "'.$str.'"';
}
elseif (is_array($var))
{
$output = array();
// Indentation for this variable
$space = str_repeat($s = ' ', $level);
static $marker;
if ($marker === NULL)
{
// Make a unique marker
$marker = uniqid("\x00");
}
if (empty($var))
{
// Do nothing
}
elseif (isset($var[$marker]))
{
$output[] = "(\n$space$s*RECURSION*\n$space)";
}
elseif ($level < $limit)
{
$output[] = "<span>(";
$var[$marker] = TRUE;
foreach ($var as $key => & $val)
{
if ($key === $marker) continue;
if ( ! is_int($key))
{
$key = '"'.htmlspecialchars($key, ENT_NOQUOTES, Kohana::$charset).'"';
}
$output[] = "$space$s$key => ".Debug::_dump($val, $length, $limit, $level + 1);
}
unset($var[$marker]);
$output[] = "$space)</span>";
}
else
{
// Depth too great
$output[] = "(\n$space$s...\n$space)";
}
return '<small>array</small><span>('.count($var).')</span> '.implode("\n", $output);
}
elseif (is_object($var))
{
// Copy the object as an array
$array = (array) $var;
$output = array();
// Indentation for this variable
$space = str_repeat($s = ' ', $level);
$hash = spl_object_hash($var);
// Objects that are being dumped
static $objects = array();
if (empty($var))
{
// Do nothing
}
elseif (isset($objects[$hash]))
{
$output[] = "{\n$space$s*RECURSION*\n$space}";
}
elseif ($level < $limit)
{
$output[] = "<code>{";
$objects[$hash] = TRUE;
foreach ($array as $key => & $val)
{
if ($key[0] === "\x00")
{
// Determine if the access is protected or protected
$access = '<small>'.(($key[1] === '*') ? 'protected' : 'private').'</small>';
// Remove the access level from the variable name
$key = substr($key, strrpos($key, "\x00") + 1);
}
else
{
$access = '<small>public</small>';
}
$output[] = "$space$s$access $key => ".Debug::_dump($val, $length, $limit, $level + 1);
}
unset($objects[$hash]);
$output[] = "$space}</code>";
}
else
{
// Depth too great
$output[] = "{\n$space$s...\n$space}";
}
return '<small>object</small> <span>'.get_class($var).'('.count($array).')</span> '.implode("\n", $output);
}
else
{
return '<small>'.gettype($var).'</small> '.htmlspecialchars(print_r($var, TRUE), ENT_NOQUOTES, Kohana::$charset);
}
}
/**
* Removes application, system, modpath, or docroot from a filename,
* replacing them with the plain text equivalents. Useful for debugging
* when you want to display a shorter path.
*
* // Displays SYSPATH/classes/kohana.php
* echo Debug::path(Kohana::find_file('classes', 'kohana'));
*
* @param string $file path to debug
* @return string
*/
public static function path($file)
{
if (strpos($file, APPPATH) === 0)
{
$file = 'APPPATH'.DIRECTORY_SEPARATOR.substr($file, strlen(APPPATH));
}
elseif (strpos($file, MODPATH) === 0)
{
$file = 'MODPATH'.DIRECTORY_SEPARATOR.substr($file, strlen(MODPATH));
}
elseif (strpos($file, SYSPATH) === 0)
{
$file = 'SYSPATH'.DIRECTORY_SEPARATOR.substr($file, strlen(SYSPATH));
}
elseif (strpos($file, SMDPATH) === 0)
{
$file = 'SMDPATH'.DIRECTORY_SEPARATOR.substr($file, strlen(SMDPATH));
}
elseif (strpos($file, DOCROOT) === 0)
{
$file = 'DOCROOT'.DIRECTORY_SEPARATOR.substr($file, strlen(DOCROOT));
}
return $file;
}
/**
* Returns an HTML string, highlighting a specific line of a file, with some
* number of lines padded above and below.
*
* // Highlights the current line of the current file
* echo Debug::source(__FILE__, __LINE__);
*
* @param string $file file to open
* @param integer $line_number line number to highlight
* @param integer $padding number of padding lines
* @return string source of file
* @return FALSE file is unreadable
*/
public static function source($file, $line_number, $padding = 5)
{
if ( ! $file OR ! is_readable($file))
{
// Continuing will cause errors
return FALSE;
}
// Open the file and set the line position
$file = fopen($file, 'r');
$line = 0;
// Set the reading range
$range = array('start' => $line_number - $padding, 'end' => $line_number + $padding);
// Set the zero-padding amount for line numbers
$format = '% '.strlen($range['end']).'d';
$source = '';
while (($row = fgets($file)) !== FALSE)
{
// Increment the line number
if (++$line > $range['end'])
break;
if ($line >= $range['start'])
{
// Make the row safe for output
$row = htmlspecialchars($row, ENT_NOQUOTES, Kohana::$charset);
// Trim whitespace and sanitize the row
$row = '<span class="number">'.sprintf($format, $line).'</span> '.$row;
if ($line === $line_number)
{
// Apply highlighting to this row
$row = '<span class="line highlight">'.$row.'</span>';
}
else
{
$row = '<span class="line">'.$row.'</span>';
}
// Add to the captured source
$source .= $row;
}
}
// Close the file
fclose($file);
return '<pre class="source"><code>'.$source.'</code></pre>';
}
/**
* Returns an array of HTML strings that represent each step in the backtrace.
*
* // Displays the entire current backtrace
* echo implode('<br/>', Debug::trace());
*
* @param array $trace
* @return string
*/
public static function trace(array $trace = NULL)
{
if ($trace === NULL)
{
// Start a new trace
$trace = debug_backtrace();
}
// Non-standard function calls
$statements = array('include', 'include_once', 'require', 'require_once');
$output = array();
foreach ($trace as $step)
{
if ( ! isset($step['function']))
{
// Invalid trace step
continue;
}
if (isset($step['file']) AND isset($step['line']))
{
// Include the source of this step
$source = Debug::source($step['file'], $step['line']);
}
if (isset($step['file']))
{
$file = $step['file'];
if (isset($step['line']))
{
$line = $step['line'];
}
}
// function()
$function = $step['function'];
if (in_array($step['function'], $statements))
{
if (empty($step['args']))
{
// No arguments
$args = array();
}
else
{
// Sanitize the file path
$args = array($step['args'][0]);
}
}
elseif (isset($step['args']))
{
if ( ! function_exists($step['function']) OR strpos($step['function'], '{closure}') !== FALSE)
{
// Introspection on closures or language constructs in a stack trace is impossible
$params = NULL;
}
else
{
if (isset($step['class']))
{
if (method_exists($step['class'], $step['function']))
{
$reflection = new ReflectionMethod($step['class'], $step['function']);
}
else
{
$reflection = new ReflectionMethod($step['class'], '__call');
}
}
else
{
$reflection = new ReflectionFunction($step['function']);
}
// Get the function parameters
$params = $reflection->getParameters();
}
$args = array();
foreach ($step['args'] as $i => $arg)
{
if (isset($params[$i]))
{
// Assign the argument by the parameter name
$args[$params[$i]->name] = $arg;
}
else
{
// Assign the argument by number
$args[$i] = $arg;
}
}
}
if (isset($step['class']))
{
// Class->method() or Class::method()
$function = $step['class'].$step['type'].$step['function'];
}
$output[] = array(
'function' => $function,
'args' => isset($args) ? $args : NULL,
'file' => isset($file) ? $file : NULL,
'line' => isset($line) ? $line : NULL,
'source' => isset($source) ? $source : NULL,
);
unset($function, $args, $file, $line, $source);
}
return $output;
}
}

View File

@@ -0,0 +1,213 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* The Encrypt library provides two-way encryption of text and binary strings
* using the [Mcrypt](http://php.net/mcrypt) extension, which consists of three
* parts: the key, the cipher, and the mode.
*
* The Key
* : A secret passphrase that is used for encoding and decoding
*
* The Cipher
* : A [cipher](http://php.net/mcrypt.ciphers) determines how the encryption
* is mathematically calculated. By default, the "rijndael-128" cipher
* is used. This is commonly known as "AES-128" and is an industry standard.
*
* The Mode
* : The [mode](http://php.net/mcrypt.constants) determines how the encrypted
* data is written in binary form. By default, the "nofb" mode is used,
* which produces short output with high entropy.
*
* @package Kohana
* @category Security
* @author Kohana Team
* @copyright (c) 2007-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
class Kohana_Encrypt {
/**
* @var string default instance name
*/
public static $default = 'default';
/**
* @var array Encrypt class instances
*/
public static $instances = array();
/**
* @var string OS-dependent RAND type to use
*/
protected static $_rand;
/**
* Returns a singleton instance of Encrypt. An encryption key must be
* provided in your "encrypt" configuration file.
*
* $encrypt = Encrypt::instance();
*
* @param string $name configuration group name
* @return Encrypt
*/
public static function instance($name = NULL)
{
if ($name === NULL)
{
// Use the default instance name
$name = Encrypt::$default;
}
if ( ! isset(Encrypt::$instances[$name]))
{
// Load the configuration data
$config = Kohana::$config->load('encrypt')->$name;
if ( ! isset($config['key']))
{
// No default encryption key is provided!
throw new Kohana_Exception('No encryption key is defined in the encryption configuration group: :group',
array(':group' => $name));
}
if ( ! isset($config['mode']))
{
// Add the default mode
$config['mode'] = MCRYPT_MODE_NOFB;
}
if ( ! isset($config['cipher']))
{
// Add the default cipher
$config['cipher'] = MCRYPT_RIJNDAEL_128;
}
// Create a new instance
Encrypt::$instances[$name] = new Encrypt($config['key'], $config['mode'], $config['cipher']);
}
return Encrypt::$instances[$name];
}
/**
* Creates a new mcrypt wrapper.
*
* @param string $key encryption key
* @param string $mode mcrypt mode
* @param string $cipher mcrypt cipher
*/
public function __construct($key, $mode, $cipher)
{
// Find the max length of the key, based on cipher and mode
$size = mcrypt_get_key_size($cipher, $mode);
if (isset($key[$size]))
{
// Shorten the key to the maximum size
$key = substr($key, 0, $size);
}
// Store the key, mode, and cipher
$this->_key = $key;
$this->_mode = $mode;
$this->_cipher = $cipher;
// Store the IV size
$this->_iv_size = mcrypt_get_iv_size($this->_cipher, $this->_mode);
}
/**
* Encrypts a string and returns an encrypted string that can be decoded.
*
* $data = $encrypt->encode($data);
*
* The encrypted binary data is encoded using [base64](http://php.net/base64_encode)
* to convert it to a string. This string can be stored in a database,
* displayed, and passed using most other means without corruption.
*
* @param string $data data to be encrypted
* @return string
*/
public function encode($data)
{
// Set the rand type if it has not already been set
if (Encrypt::$_rand === NULL)
{
if (Kohana::$is_windows)
{
// Windows only supports the system random number generator
Encrypt::$_rand = MCRYPT_RAND;
}
else
{
if (defined('MCRYPT_DEV_URANDOM'))
{
// Use /dev/urandom
Encrypt::$_rand = MCRYPT_DEV_URANDOM;
}
elseif (defined('MCRYPT_DEV_RANDOM'))
{
// Use /dev/random
Encrypt::$_rand = MCRYPT_DEV_RANDOM;
}
else
{
// Use the system random number generator
Encrypt::$_rand = MCRYPT_RAND;
}
}
}
if (Encrypt::$_rand === MCRYPT_RAND)
{
// The system random number generator must always be seeded each
// time it is used, or it will not produce true random results
mt_srand();
}
// Create a random initialization vector of the proper size for the current cipher
$iv = mcrypt_create_iv($this->_iv_size, Encrypt::$_rand);
// Encrypt the data using the configured options and generated iv
$data = mcrypt_encrypt($this->_cipher, $this->_key, $data, $this->_mode, $iv);
// Use base64 encoding to convert to a string
return base64_encode($iv.$data);
}
/**
* Decrypts an encoded string back to its original value.
*
* $data = $encrypt->decode($data);
*
* @param string $data encoded string to be decrypted
* @return FALSE if decryption fails
* @return string
*/
public function decode($data)
{
// Convert the data back to binary
$data = base64_decode($data, TRUE);
if ( ! $data)
{
// Invalid base64 data
return FALSE;
}
// Extract the initialization vector from the data
$iv = substr($data, 0, $this->_iv_size);
if ($this->_iv_size !== strlen($iv))
{
// The iv is not the expected size
return FALSE;
}
// Remove the iv from the data
$data = substr($data, $this->_iv_size);
// Return the decrypted data, trimming the \0 padding bytes from the end of the data
return rtrim(mcrypt_decrypt($this->_cipher, $this->_key, $data, $this->_mode, $iv), "\0");
}
} // End Encrypt

View File

@@ -0,0 +1,3 @@
<?php defined('SYSPATH') OR die('No direct script access.');
class Kohana_Exception extends Kohana_Kohana_Exception {}

View File

@@ -0,0 +1,185 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* RSS and Atom feed helper.
*
* @package Kohana
* @category Helpers
* @author Kohana Team
* @copyright (c) 2007-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
class Kohana_Feed {
/**
* Parses a remote feed into an array.
*
* @param string $feed remote feed URL
* @param integer $limit item limit to fetch
* @return array
*/
public static function parse($feed, $limit = 0)
{
// Check if SimpleXML is installed
if ( ! function_exists('simplexml_load_file'))
throw new Kohana_Exception('SimpleXML must be installed!');
// Make limit an integer
$limit = (int) $limit;
// Disable error reporting while opening the feed
$error_level = error_reporting(0);
// Allow loading by filename or raw XML string
if (Valid::url($feed))
{
// Use native Request client to get remote contents
$response = Request::factory($feed)->execute();
$feed = $response->body();
}
elseif (is_file($feed))
{
// Get file contents
$feed = file_get_contents($feed);
}
// Load the feed
$feed = simplexml_load_string($feed, 'SimpleXMLElement', LIBXML_NOCDATA);
// Restore error reporting
error_reporting($error_level);
// Feed could not be loaded
if ($feed === FALSE)
return array();
$namespaces = $feed->getNamespaces(TRUE);
// Detect the feed type. RSS 1.0/2.0 and Atom 1.0 are supported.
$feed = isset($feed->channel) ? $feed->xpath('//item') : $feed->entry;
$i = 0;
$items = array();
foreach ($feed as $item)
{
if ($limit > 0 AND $i++ === $limit)
break;
$item_fields = (array) $item;
// get namespaced tags
foreach ($namespaces as $ns)
{
$item_fields += (array) $item->children($ns);
}
$items[] = $item_fields;
}
return $items;
}
/**
* Creates a feed from the given parameters.
*
* @param array $info feed information
* @param array $items items to add to the feed
* @param string $encoding define which encoding to use
* @return string
*/
public static function create($info, $items, $encoding = 'UTF-8')
{
$info += array('title' => 'Generated Feed', 'link' => '', 'generator' => 'KohanaPHP');
$feed = '<?xml version="1.0" encoding="'.$encoding.'"?><rss version="2.0"><channel></channel></rss>';
$feed = simplexml_load_string($feed);
foreach ($info as $name => $value)
{
if ($name === 'image')
{
// Create an image element
$image = $feed->channel->addChild('image');
if ( ! isset($value['link'], $value['url'], $value['title']))
{
throw new Kohana_Exception('Feed images require a link, url, and title');
}
if (strpos($value['link'], '://') === FALSE)
{
// Convert URIs to URLs
$value['link'] = URL::site($value['link'], 'http');
}
if (strpos($value['url'], '://') === FALSE)
{
// Convert URIs to URLs
$value['url'] = URL::site($value['url'], 'http');
}
// Create the image elements
$image->addChild('link', $value['link']);
$image->addChild('url', $value['url']);
$image->addChild('title', $value['title']);
}
else
{
if (($name === 'pubDate' OR $name === 'lastBuildDate') AND (is_int($value) OR ctype_digit($value)))
{
// Convert timestamps to RFC 822 formatted dates
$value = date('r', $value);
}
elseif (($name === 'link' OR $name === 'docs') AND strpos($value, '://') === FALSE)
{
// Convert URIs to URLs
$value = URL::site($value, 'http');
}
// Add the info to the channel
$feed->channel->addChild($name, $value);
}
}
foreach ($items as $item)
{
// Add the item to the channel
$row = $feed->channel->addChild('item');
foreach ($item as $name => $value)
{
if ($name === 'pubDate' AND (is_int($value) OR ctype_digit($value)))
{
// Convert timestamps to RFC 822 formatted dates
$value = date('r', $value);
}
elseif (($name === 'link' OR $name === 'guid') AND strpos($value, '://') === FALSE)
{
// Convert URIs to URLs
$value = URL::site($value, 'http');
}
// Add the info to the row
$row->addChild($name, $value);
}
}
if (function_exists('dom_import_simplexml'))
{
// Convert the feed object to a DOM object
$feed = dom_import_simplexml($feed)->ownerDocument;
// DOM generates more readable XML
$feed->formatOutput = TRUE;
// Export the document as XML
$feed = $feed->saveXML();
}
else
{
// Export the document as XML
$feed = $feed->asXML();
}
return $feed;
}
} // End Feed

View File

@@ -0,0 +1,241 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* File helper class.
*
* @package Kohana
* @category Helpers
* @author Kohana Team
* @copyright (c) 2007-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
class Kohana_File {
/**
* Attempt to get the mime type from a file. This method is horribly
* unreliable, due to PHP being horribly unreliable when it comes to
* determining the mime type of a file.
*
* $mime = File::mime($file);
*
* @param string $filename file name or path
* @return string mime type on success
* @return FALSE on failure
*/
public static function mime($filename)
{
// Get the complete path to the file
$filename = realpath($filename);
// Get the extension from the filename
$extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
if (preg_match('/^(?:jpe?g|png|[gt]if|bmp|swf)$/', $extension))
{
// Use getimagesize() to find the mime type on images
$file = getimagesize($filename);
if (isset($file['mime']))
return $file['mime'];
}
if (class_exists('finfo', FALSE))
{
if ($info = new finfo(defined('FILEINFO_MIME_TYPE') ? FILEINFO_MIME_TYPE : FILEINFO_MIME))
{
return $info->file($filename);
}
}
if (ini_get('mime_magic.magicfile') AND function_exists('mime_content_type'))
{
// The mime_content_type function is only useful with a magic file
return mime_content_type($filename);
}
if ( ! empty($extension))
{
return File::mime_by_ext($extension);
}
// Unable to find the mime-type
return FALSE;
}
/**
* Return the mime type of an extension.
*
* $mime = File::mime_by_ext('png'); // "image/png"
*
* @param string $extension php, pdf, txt, etc
* @return string mime type on success
* @return FALSE on failure
*/
public static function mime_by_ext($extension)
{
// Load all of the mime types
$mimes = Kohana::$config->load('mimes');
return isset($mimes[$extension]) ? $mimes[$extension][0] : FALSE;
}
/**
* Lookup MIME types for a file
*
* @see Kohana_File::mime_by_ext()
* @param string $extension Extension to lookup
* @return array Array of MIMEs associated with the specified extension
*/
public static function mimes_by_ext($extension)
{
// Load all of the mime types
$mimes = Kohana::$config->load('mimes');
return isset($mimes[$extension]) ? ( (array) $mimes[$extension]) : array();
}
/**
* Lookup file extensions by MIME type
*
* @param string $type File MIME type
* @return array File extensions matching MIME type
*/
public static function exts_by_mime($type)
{
static $types = array();
// Fill the static array
if (empty($types))
{
foreach (Kohana::$config->load('mimes') as $ext => $mimes)
{
foreach ($mimes as $mime)
{
if ($mime == 'application/octet-stream')
{
// octet-stream is a generic binary
continue;
}
if ( ! isset($types[$mime]))
{
$types[$mime] = array( (string) $ext);
}
elseif ( ! in_array($ext, $types[$mime]))
{
$types[$mime][] = (string) $ext;
}
}
}
}
return isset($types[$type]) ? $types[$type] : FALSE;
}
/**
* Lookup a single file extension by MIME type.
*
* @param string $type MIME type to lookup
* @return mixed First file extension matching or false
*/
public static function ext_by_mime($type)
{
return current(File::exts_by_mime($type));
}
/**
* Split a file into pieces matching a specific size. Used when you need to
* split large files into smaller pieces for easy transmission.
*
* $count = File::split($file);
*
* @param string $filename file to be split
* @param integer $piece_size size, in MB, for each piece to be
* @return integer The number of pieces that were created
*/
public static function split($filename, $piece_size = 10)
{
// Open the input file
$file = fopen($filename, 'rb');
// Change the piece size to bytes
$piece_size = floor($piece_size * 1024 * 1024);
// Write files in 8k blocks
$block_size = 1024 * 8;
// Total number of peices
$peices = 0;
while ( ! feof($file))
{
// Create another piece
$peices += 1;
// Create a new file piece
$piece = str_pad($peices, 3, '0', STR_PAD_LEFT);
$piece = fopen($filename.'.'.$piece, 'wb+');
// Number of bytes read
$read = 0;
do
{
// Transfer the data in blocks
fwrite($piece, fread($file, $block_size));
// Another block has been read
$read += $block_size;
}
while ($read < $piece_size);
// Close the piece
fclose($piece);
}
// Close the file
fclose($file);
return $peices;
}
/**
* Join a split file into a whole file. Does the reverse of [File::split].
*
* $count = File::join($file);
*
* @param string $filename split filename, without .000 extension
* @return integer The number of pieces that were joined.
*/
public static function join($filename)
{
// Open the file
$file = fopen($filename, 'wb+');
// Read files in 8k blocks
$block_size = 1024 * 8;
// Total number of peices
$pieces = 0;
while (is_file($piece = $filename.'.'.str_pad($pieces + 1, 3, '0', STR_PAD_LEFT)))
{
// Read another piece
$pieces += 1;
// Open the piece for reading
$piece = fopen($piece, 'rb');
while ( ! feof($piece))
{
// Transfer the data in blocks
fwrite($file, fread($piece, $block_size));
}
// Close the peice
fclose($piece);
}
return $pieces;
}
} // End file

View File

@@ -0,0 +1,434 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* Form helper class. Unless otherwise noted, all generated HTML will be made
* safe using the [HTML::chars] method. This prevents against simple XSS
* attacks that could otherwise be trigged by inserting HTML characters into
* form fields.
*
* @package Kohana
* @category Helpers
* @author Kohana Team
* @copyright (c) 2007-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
class Kohana_Form {
/**
* Generates an opening HTML form tag.
*
* // Form will submit back to the current page using POST
* echo Form::open();
*
* // Form will submit to 'search' using GET
* echo Form::open('search', array('method' => 'get'));
*
* // When "file" inputs are present, you must include the "enctype"
* echo Form::open(NULL, array('enctype' => 'multipart/form-data'));
*
* @param mixed $action form action, defaults to the current request URI, or [Request] class to use
* @param array $attributes html attributes
* @return string
* @uses Request::instance
* @uses URL::site
* @uses HTML::attributes
*/
public static function open($action = NULL, array $attributes = NULL)
{
if ($action instanceof Request)
{
// Use the current URI
$action = $action->uri();
}
if ( ! $action)
{
// Allow empty form actions (submits back to the current url).
$action = '';
}
elseif (strpos($action, '://') === FALSE)
{
// Make the URI absolute
$action = URL::site($action);
}
// Add the form action to the attributes
$attributes['action'] = $action;
// Only accept the default character set
$attributes['accept-charset'] = Kohana::$charset;
if ( ! isset($attributes['method']))
{
// Use POST method
$attributes['method'] = 'post';
}
return '<form'.HTML::attributes($attributes).'>';
}
/**
* Creates the closing form tag.
*
* echo Form::close();
*
* @return string
*/
public static function close()
{
return '</form>';
}
/**
* Creates a form input. If no type is specified, a "text" type input will
* be returned.
*
* echo Form::input('username', $username);
*
* @param string $name input name
* @param string $value input value
* @param array $attributes html attributes
* @return string
* @uses HTML::attributes
*/
public static function input($name, $value = NULL, array $attributes = NULL)
{
// Set the input name
$attributes['name'] = $name;
// Set the input value
$attributes['value'] = $value;
if ( ! isset($attributes['type']))
{
// Default type is text
$attributes['type'] = 'text';
}
return '<input'.HTML::attributes($attributes).' />';
}
/**
* Creates a hidden form input.
*
* echo Form::hidden('csrf', $token);
*
* @param string $name input name
* @param string $value input value
* @param array $attributes html attributes
* @return string
* @uses Form::input
*/
public static function hidden($name, $value = NULL, array $attributes = NULL)
{
$attributes['type'] = 'hidden';
return Form::input($name, $value, $attributes);
}
/**
* Creates a password form input.
*
* echo Form::password('password');
*
* @param string $name input name
* @param string $value input value
* @param array $attributes html attributes
* @return string
* @uses Form::input
*/
public static function password($name, $value = NULL, array $attributes = NULL)
{
$attributes['type'] = 'password';
return Form::input($name, $value, $attributes);
}
/**
* Creates a file upload form input. No input value can be specified.
*
* echo Form::file('image');
*
* @param string $name input name
* @param array $attributes html attributes
* @return string
* @uses Form::input
*/
public static function file($name, array $attributes = NULL)
{
$attributes['type'] = 'file';
return Form::input($name, NULL, $attributes);
}
/**
* Creates a checkbox form input.
*
* echo Form::checkbox('remember_me', 1, (bool) $remember);
*
* @param string $name input name
* @param string $value input value
* @param boolean $checked checked status
* @param array $attributes html attributes
* @return string
* @uses Form::input
*/
public static function checkbox($name, $value = NULL, $checked = FALSE, array $attributes = NULL)
{
$attributes['type'] = 'checkbox';
if ($checked === TRUE)
{
// Make the checkbox active
$attributes[] = 'checked';
}
return Form::input($name, $value, $attributes);
}
/**
* Creates a radio form input.
*
* echo Form::radio('like_cats', 1, $cats);
* echo Form::radio('like_cats', 0, ! $cats);
*
* @param string $name input name
* @param string $value input value
* @param boolean $checked checked status
* @param array $attributes html attributes
* @return string
* @uses Form::input
*/
public static function radio($name, $value = NULL, $checked = FALSE, array $attributes = NULL)
{
$attributes['type'] = 'radio';
if ($checked === TRUE)
{
// Make the radio active
$attributes[] = 'checked';
}
return Form::input($name, $value, $attributes);
}
/**
* Creates a textarea form input.
*
* echo Form::textarea('about', $about);
*
* @param string $name textarea name
* @param string $body textarea body
* @param array $attributes html attributes
* @param boolean $double_encode encode existing HTML characters
* @return string
* @uses HTML::attributes
* @uses HTML::chars
*/
public static function textarea($name, $body = '', array $attributes = NULL, $double_encode = TRUE)
{
// Set the input name
$attributes['name'] = $name;
// Add default rows and cols attributes (required)
$attributes += array('rows' => 10, 'cols' => 50);
return '<textarea'.HTML::attributes($attributes).'>'.HTML::chars($body, $double_encode).'</textarea>';
}
/**
* Creates a select form input.
*
* echo Form::select('country', $countries, $country);
*
* [!!] Support for multiple selected options was added in v3.0.7.
*
* @param string $name input name
* @param array $options available options
* @param mixed $selected selected option string, or an array of selected options
* @param array $attributes html attributes
* @return string
* @uses HTML::attributes
*/
public static function select($name, array $options = NULL, $selected = NULL, array $attributes = NULL)
{
// Set the input name
$attributes['name'] = $name;
if (is_array($selected))
{
// This is a multi-select, god save us!
$attributes[] = 'multiple';
}
if ( ! is_array($selected))
{
if ($selected === NULL)
{
// Use an empty array
$selected = array();
}
else
{
// Convert the selected options to an array
$selected = array( (string) $selected);
}
}
if (empty($options))
{
// There are no options
$options = '';
}
else
{
foreach ($options as $value => $name)
{
if (is_array($name))
{
// Create a new optgroup
$group = array('label' => $value);
// Create a new list of options
$_options = array();
foreach ($name as $_value => $_name)
{
// Force value to be string
$_value = (string) $_value;
// Create a new attribute set for this option
$option = array('value' => $_value);
if (in_array($_value, $selected))
{
// This option is selected
$option[] = 'selected';
}
// Change the option to the HTML string
$_options[] = '<option'.HTML::attributes($option).'>'.HTML::chars($_name, FALSE).'</option>';
}
// Compile the options into a string
$_options = "\n".implode("\n", $_options)."\n";
$options[$value] = '<optgroup'.HTML::attributes($group).'>'.$_options.'</optgroup>';
}
else
{
// Force value to be string
$value = (string) $value;
// Create a new attribute set for this option
$option = array('value' => $value);
if (in_array($value, $selected))
{
// This option is selected
$option[] = 'selected';
}
// Change the option to the HTML string
$options[$value] = '<option'.HTML::attributes($option).'>'.HTML::chars($name, FALSE).'</option>';
}
}
// Compile the options into a single string
$options = "\n".implode("\n", $options)."\n";
}
return '<select'.HTML::attributes($attributes).'>'.$options.'</select>';
}
/**
* Creates a submit form input.
*
* echo Form::submit(NULL, 'Login');
*
* @param string $name input name
* @param string $value input value
* @param array $attributes html attributes
* @return string
* @uses Form::input
*/
public static function submit($name, $value, array $attributes = NULL)
{
$attributes['type'] = 'submit';
return Form::input($name, $value, $attributes);
}
/**
* Creates a image form input.
*
* echo Form::image(NULL, NULL, array('src' => 'media/img/login.png'));
*
* @param string $name input name
* @param string $value input value
* @param array $attributes html attributes
* @param boolean $index add index file to URL?
* @return string
* @uses Form::input
*/
public static function image($name, $value, array $attributes = NULL, $index = FALSE)
{
if ( ! empty($attributes['src']))
{
if (strpos($attributes['src'], '://') === FALSE)
{
// Add the base URL
$attributes['src'] = URL::base($index).$attributes['src'];
}
}
$attributes['type'] = 'image';
return Form::input($name, $value, $attributes);
}
/**
* Creates a button form input. Note that the body of a button is NOT escaped,
* to allow images and other HTML to be used.
*
* echo Form::button('save', 'Save Profile', array('type' => 'submit'));
*
* @param string $name input name
* @param string $body input value
* @param array $attributes html attributes
* @return string
* @uses HTML::attributes
*/
public static function button($name, $body, array $attributes = NULL)
{
// Set the input name
$attributes['name'] = $name;
return '<button'.HTML::attributes($attributes).'>'.$body.'</button>';
}
/**
* Creates a form label. Label text is not automatically translated.
*
* echo Form::label('username', 'Username');
*
* @param string $input target input
* @param string $text label text
* @param array $attributes html attributes
* @return string
* @uses HTML::attributes
*/
public static function label($input, $text = NULL, array $attributes = NULL)
{
if ($text === NULL)
{
// Use the input name as the text
$text = ucwords(preg_replace('/[\W_]+/', ' ', $input));
}
// Set the label target
$attributes['for'] = $input;
return '<label'.HTML::attributes($attributes).'>'.$text.'</label>';
}
} // End form

View File

@@ -0,0 +1,147 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* View fragment caching. This is primarily used to cache small parts of a view
* that rarely change. For instance, you may want to cache the footer of your
* template because it has very little dynamic content. Or you could cache a
* user profile page and delete the fragment when the user updates.
*
* For obvious reasons, fragment caching should not be applied to any
* content that contains forms.
*
* [!!] Multiple language (I18n) support was added in v3.0.4.
*
* @package Kohana
* @category Helpers
* @author Kohana Team
* @copyright (c) 2009-2012 Kohana Team
* @license http://kohanaframework.org/license
* @uses Kohana::cache
*/
class Kohana_Fragment {
/**
* @var integer default number of seconds to cache for
*/
public static $lifetime = 30;
/**
* @var boolean use multilingual fragment support?
*/
public static $i18n = FALSE;
/**
* @var array list of buffer => cache key
*/
protected static $_caches = array();
/**
* Generate the cache key name for a fragment.
*
* $key = Fragment::_cache_key('footer', TRUE);
*
* @param string $name fragment name
* @param boolean $i18n multilingual fragment support
* @return string
* @uses I18n::lang
* @since 3.0.4
*/
protected static function _cache_key($name, $i18n = NULL)
{
if ($i18n === NULL)
{
// Use the default setting
$i18n = Fragment::$i18n;
}
// Language prefix for cache key
$i18n = ($i18n === TRUE) ? I18n::lang() : '';
// Note: $i18n and $name need to be delimited to prevent naming collisions
return 'Fragment::cache('.$i18n.'+'.$name.')';
}
/**
* Load a fragment from cache and display it. Multiple fragments can
* be nested with different life times.
*
* if ( ! Fragment::load('footer')) {
* // Anything that is echo'ed here will be saved
* Fragment::save();
* }
*
* @param string $name fragment name
* @param integer $lifetime fragment cache lifetime
* @param boolean $i18n multilingual fragment support
* @return boolean
*/
public static function load($name, $lifetime = NULL, $i18n = NULL)
{
// Set the cache lifetime
$lifetime = ($lifetime === NULL) ? Fragment::$lifetime : (int) $lifetime;
// Get the cache key name
$cache_key = Fragment::_cache_key($name, $i18n);
if ($fragment = Kohana::cache($cache_key, NULL, $lifetime))
{
// Display the cached fragment now
echo $fragment;
return TRUE;
}
else
{
// Start the output buffer
ob_start();
// Store the cache key by the buffer level
Fragment::$_caches[ob_get_level()] = $cache_key;
return FALSE;
}
}
/**
* Saves the currently open fragment in the cache.
*
* Fragment::save();
*
* @return void
*/
public static function save()
{
// Get the buffer level
$level = ob_get_level();
if (isset(Fragment::$_caches[$level]))
{
// Get the cache key based on the level
$cache_key = Fragment::$_caches[$level];
// Delete the cache key, we don't need it anymore
unset(Fragment::$_caches[$level]);
// Get the output buffer and display it at the same time
$fragment = ob_get_flush();
// Cache the fragment
Kohana::cache($cache_key, $fragment);
}
}
/**
* Delete a cached fragment.
*
* Fragment::delete($key);
*
* @param string $name fragment name
* @param boolean $i18n multilingual fragment support
* @return void
*/
public static function delete($name, $i18n = NULL)
{
// Invalid the cache
Kohana::cache(Fragment::_cache_key($name, $i18n), NULL, -3600);
}
} // End Fragment

View File

@@ -0,0 +1,345 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* HTML helper class. Provides generic methods for generating various HTML
* tags and making output HTML safe.
*
* @package Kohana
* @category Helpers
* @author Kohana Team
* @copyright (c) 2007-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
class Kohana_HTML {
/**
* @var array preferred order of attributes
*/
public static $attribute_order = array
(
'action',
'method',
'type',
'id',
'name',
'value',
'href',
'src',
'width',
'height',
'cols',
'rows',
'size',
'maxlength',
'rel',
'media',
'accept-charset',
'accept',
'tabindex',
'accesskey',
'alt',
'title',
'class',
'style',
'selected',
'checked',
'readonly',
'disabled',
);
/**
* @var boolean use strict XHTML mode?
*/
public static $strict = TRUE;
/**
* @var boolean automatically target external URLs to a new window?
*/
public static $windowed_urls = FALSE;
/**
* Convert special characters to HTML entities. All untrusted content
* should be passed through this method to prevent XSS injections.
*
* echo HTML::chars($username);
*
* @param string $value string to convert
* @param boolean $double_encode encode existing entities
* @return string
*/
public static function chars($value, $double_encode = TRUE)
{
return htmlspecialchars( (string) $value, ENT_QUOTES, Kohana::$charset, $double_encode);
}
/**
* Convert all applicable characters to HTML entities. All characters
* that cannot be represented in HTML with the current character set
* will be converted to entities.
*
* echo HTML::entities($username);
*
* @param string $value string to convert
* @param boolean $double_encode encode existing entities
* @return string
*/
public static function entities($value, $double_encode = TRUE)
{
return htmlentities( (string) $value, ENT_QUOTES, Kohana::$charset, $double_encode);
}
/**
* Create HTML link anchors. Note that the title is not escaped, to allow
* HTML elements within links (images, etc).
*
* echo HTML::anchor('/user/profile', 'My Profile');
*
* @param string $uri URL or URI string
* @param string $title link text
* @param array $attributes HTML anchor attributes
* @param mixed $protocol protocol to pass to URL::base()
* @param boolean $index include the index page
* @return string
* @uses URL::base
* @uses URL::site
* @uses HTML::attributes
*/
public static function anchor($uri, $title = NULL, array $attributes = NULL, $protocol = NULL, $index = TRUE)
{
if ($title === NULL)
{
// Use the URI as the title
$title = $uri;
}
if ($uri === '')
{
// Only use the base URL
$uri = URL::base($protocol, $index);
}
else
{
if (strpos($uri, '://') !== FALSE)
{
if (HTML::$windowed_urls === TRUE AND empty($attributes['target']))
{
// Make the link open in a new window
$attributes['target'] = '_blank';
}
}
elseif ($uri[0] !== '#')
{
// Make the URI absolute for non-id anchors
$uri = URL::site($uri, $protocol, $index);
}
}
// Add the sanitized link to the attributes
$attributes['href'] = $uri;
return '<a'.HTML::attributes($attributes).'>'.$title.'</a>';
}
/**
* Creates an HTML anchor to a file. Note that the title is not escaped,
* to allow HTML elements within links (images, etc).
*
* echo HTML::file_anchor('media/doc/user_guide.pdf', 'User Guide');
*
* @param string $file name of file to link to
* @param string $title link text
* @param array $attributes HTML anchor attributes
* @param mixed $protocol protocol to pass to URL::base()
* @param boolean $index include the index page
* @return string
* @uses URL::base
* @uses HTML::attributes
*/
public static function file_anchor($file, $title = NULL, array $attributes = NULL, $protocol = NULL, $index = FALSE)
{
if ($title === NULL)
{
// Use the file name as the title
$title = basename($file);
}
// Add the file link to the attributes
$attributes['href'] = URL::site($file, $protocol, $index);
return '<a'.HTML::attributes($attributes).'>'.$title.'</a>';
}
/**
* Creates an email (mailto:) anchor. Note that the title is not escaped,
* to allow HTML elements within links (images, etc).
*
* echo HTML::mailto($address);
*
* @param string $email email address to send to
* @param string $title link text
* @param array $attributes HTML anchor attributes
* @return string
* @uses HTML::attributes
*/
public static function mailto($email, $title = NULL, array $attributes = NULL)
{
if ($title === NULL)
{
// Use the email address as the title
$title = $email;
}
return '<a href="&#109;&#097;&#105;&#108;&#116;&#111;&#058;'.$email.'"'.HTML::attributes($attributes).'>'.$title.'</a>';
}
/**
* Creates a style sheet link element.
*
* echo HTML::style('media/css/screen.css');
*
* @param string $file file name
* @param array $attributes default attributes
* @param mixed $protocol protocol to pass to URL::base()
* @param boolean $index include the index page
* @return string
* @uses URL::base
* @uses HTML::attributes
*/
public static function style($file, array $attributes = NULL, $protocol = NULL, $index = FALSE)
{
if (strpos($file, '://') === FALSE)
{
// Add the base URL
$file = URL::site($file, $protocol, $index);
}
// Set the stylesheet link
$attributes['href'] = $file;
// Set the stylesheet rel
$attributes['rel'] = empty($attributes['rel']) ? 'stylesheet' : $attributes['rel'];
// Set the stylesheet type
$attributes['type'] = 'text/css';
return '<link'.HTML::attributes($attributes).' />';
}
/**
* Creates a script link.
*
* echo HTML::script('media/js/jquery.min.js');
*
* @param string $file file name
* @param array $attributes default attributes
* @param mixed $protocol protocol to pass to URL::base()
* @param boolean $index include the index page
* @return string
* @uses URL::base
* @uses HTML::attributes
*/
public static function script($file, array $attributes = NULL, $protocol = NULL, $index = FALSE)
{
if (strpos($file, '://') === FALSE)
{
// Add the base URL
$file = URL::site($file, $protocol, $index);
}
// Set the script link
$attributes['src'] = $file;
// Set the script type
$attributes['type'] = 'text/javascript';
return '<script'.HTML::attributes($attributes).'></script>';
}
/**
* Creates a image link.
*
* echo HTML::image('media/img/logo.png', array('alt' => 'My Company'));
*
* @param string $file file name
* @param array $attributes default attributes
* @param mixed $protocol protocol to pass to URL::base()
* @param boolean $index include the index page
* @return string
* @uses URL::base
* @uses HTML::attributes
*/
public static function image($file, array $attributes = NULL, $protocol = NULL, $index = FALSE)
{
if (strpos($file, '://') === FALSE)
{
// Add the base URL
$file = URL::site($file, $protocol, $index);
}
// Add the image link
$attributes['src'] = $file;
return '<img'.HTML::attributes($attributes).' />';
}
/**
* Compiles an array of HTML attributes into an attribute string.
* Attributes will be sorted using HTML::$attribute_order for consistency.
*
* echo '<div'.HTML::attributes($attrs).'>'.$content.'</div>';
*
* @param array $attributes attribute list
* @return string
*/
public static function attributes(array $attributes = NULL)
{
if (empty($attributes))
return '';
$sorted = array();
foreach (HTML::$attribute_order as $key)
{
if (isset($attributes[$key]))
{
// Add the attribute to the sorted list
$sorted[$key] = $attributes[$key];
}
}
// Combine the sorted attributes
$attributes = $sorted + $attributes;
$compiled = '';
foreach ($attributes as $key => $value)
{
if ($value === NULL)
{
// Skip attributes that have NULL values
continue;
}
if (is_int($key))
{
// Assume non-associative keys are mirrored attributes
$key = $value;
if ( ! HTML::$strict)
{
// Just use a key
$value = FALSE;
}
}
// Add the attribute key
$compiled .= ' '.$key;
if ($value OR HTML::$strict)
{
// Add the attribute value
$compiled .= '="'.HTML::chars($value).'"';
}
}
return $compiled;
}
} // End html

View File

@@ -0,0 +1,217 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* Contains the most low-level helpers methods in Kohana:
*
* - Environment initialization
* - Locating files within the cascading filesystem
* - Auto-loading and transparent extension of classes
* - Variable and path debugging
*
* @package Kohana
* @category HTTP
* @author Kohana Team
* @since 3.1.0
* @copyright (c) 2008-2012 Kohana Team
* @license http://kohanaphp.com/license
*/
abstract class Kohana_HTTP {
/**
* @var The default protocol to use if it cannot be detected
*/
public static $protocol = 'HTTP/1.1';
/**
* Issues a HTTP redirect.
*
* @param string $uri URI to redirect to
* @param int $code HTTP Status code to use for the redirect
* @throws HTTP_Exception
*/
public static function redirect($uri = '', $code = 302)
{
$e = HTTP_Exception::factory($code);
if ( ! $e instanceof HTTP_Exception_Redirect)
throw new Kohana_Exception('Invalid redirect code \':code\'', array(
':code' => $code
));
throw $e->location($uri);
}
/**
* Checks the browser cache to see the response needs to be returned,
* execution will halt and a 304 Not Modified will be sent if the
* browser cache is up to date.
*
* @param Request $request Request
* @param Response $response Response
* @param string $etag Resource ETag
* @throws HTTP_Exception_304
* @return Response
*/
public static function check_cache(Request $request, Response $response, $etag = NULL)
{
// Generate an etag if necessary
if ($etag == NULL)
{
$etag = $response->generate_etag();
}
// Set the ETag header
$response->headers('etag', $etag);
// Add the Cache-Control header if it is not already set
// This allows etags to be used with max-age, etc
if ($response->headers('cache-control'))
{
$response->headers('cache-control', $response->headers('cache-control').', must-revalidate');
}
else
{
$response->headers('cache-control', 'must-revalidate');
}
// Check if we have a matching etag
if ($request->headers('if-none-match') AND (string) $request->headers('if-none-match') === $etag)
{
// No need to send data again
throw HTTP_Exception::factory(304)->headers('etag', $etag);
}
return $response;
}
/**
* Parses a HTTP header string into an associative array
*
* @param string $header_string Header string to parse
* @return HTTP_Header
*/
public static function parse_header_string($header_string)
{
// If the PECL HTTP extension is loaded
if (extension_loaded('http'))
{
// Use the fast method to parse header string
return new HTTP_Header(http_parse_headers($header_string));
}
// Otherwise we use the slower PHP parsing
$headers = array();
// Match all HTTP headers
if (preg_match_all('/(\w[^\s:]*):[ ]*([^\r\n]*(?:\r\n[ \t][^\r\n]*)*)/', $header_string, $matches))
{
// Parse each matched header
foreach ($matches[0] as $key => $value)
{
// If the header has not already been set
if ( ! isset($headers[$matches[1][$key]]))
{
// Apply the header directly
$headers[$matches[1][$key]] = $matches[2][$key];
}
// Otherwise there is an existing entry
else
{
// If the entry is an array
if (is_array($headers[$matches[1][$key]]))
{
// Apply the new entry to the array
$headers[$matches[1][$key]][] = $matches[2][$key];
}
// Otherwise create a new array with the entries
else
{
$headers[$matches[1][$key]] = array(
$headers[$matches[1][$key]],
$matches[2][$key],
);
}
}
}
}
// Return the headers
return new HTTP_Header($headers);
}
/**
* Parses the the HTTP request headers and returns an array containing
* key value pairs. This method is slow, but provides an accurate
* representation of the HTTP request.
*
* // Get http headers into the request
* $request->headers = HTTP::request_headers();
*
* @return HTTP_Header
*/
public static function request_headers()
{
// If running on apache server
if (function_exists('apache_request_headers'))
{
// Return the much faster method
return new HTTP_Header(apache_request_headers());
}
// If the PECL HTTP tools are installed
elseif (extension_loaded('http'))
{
// Return the much faster method
return new HTTP_Header(http_get_request_headers());
}
// Setup the output
$headers = array();
// Parse the content type
if ( ! empty($_SERVER['CONTENT_TYPE']))
{
$headers['content-type'] = $_SERVER['CONTENT_TYPE'];
}
// Parse the content length
if ( ! empty($_SERVER['CONTENT_LENGTH']))
{
$headers['content-length'] = $_SERVER['CONTENT_LENGTH'];
}
foreach ($_SERVER as $key => $value)
{
// If there is no HTTP header here, skip
if (strpos($key, 'HTTP_') !== 0)
{
continue;
}
// This is a dirty hack to ensure HTTP_X_FOO_BAR becomes x-foo-bar
$headers[str_replace(array('HTTP_', '_'), array('', '-'), $key)] = $value;
}
return new HTTP_Header($headers);
}
/**
* Processes an array of key value pairs and encodes
* the values to meet RFC 3986
*
* @param array $params Params
* @return string
*/
public static function www_form_urlencode(array $params = array())
{
if ( ! $params)
return;
$encoded = array();
foreach ($params as $key => $value)
{
$encoded[] = $key.'='.rawurlencode($value);
}
return implode('&', $encoded);
}
} // End Kohana_HTTP

View File

@@ -0,0 +1,72 @@
<?php defined('SYSPATH') OR die('No direct script access.');
abstract class Kohana_HTTP_Exception extends Kohana_Exception {
/**
* Creates an HTTP_Exception of the specified type.
*
* @param integer $code the http status code
* @param string $message status message, custom content to display with error
* @param array $variables translation variables
* @return HTTP_Exception
*/
public static function factory($code, $message = NULL, array $variables = NULL, Exception $previous = NULL)
{
$class = 'HTTP_Exception_'.$code;
return new $class($message, $variables, $previous);
}
/**
* @var int http status code
*/
protected $_code = 0;
/**
* @var Request Request instance that triggered this exception.
*/
protected $_request;
/**
* Creates a new translated exception.
*
* throw new Kohana_Exception('Something went terrible wrong, :user',
* array(':user' => $user));
*
* @param string $message status message, custom content to display with error
* @param array $variables translation variables
* @return void
*/
public function __construct($message = NULL, array $variables = NULL, Exception $previous = NULL)
{
parent::__construct($message, $variables, $this->_code, $previous);
}
/**
* Store the Request that triggered this exception.
*
* @param Request $request Request object that triggered this exception.
* @return Response
*/
public function request(Request $request = NULL)
{
if ($request === NULL)
return $this->_request;
$this->_request = $request;
return $this;
}
/**
* Generate a Response for the current Exception
*
* @uses Kohana_Exception::response()
* @return Response
*/
public function get_response()
{
return Kohana_Exception::response($this);
}
} // End Kohana_HTTP_Exception

View File

@@ -0,0 +1,10 @@
<?php defined('SYSPATH') OR die('No direct script access.');
class Kohana_HTTP_Exception_300 extends HTTP_Exception_Redirect {
/**
* @var integer HTTP 300 Multiple Choices
*/
protected $_code = 300;
}

View File

@@ -0,0 +1,10 @@
<?php defined('SYSPATH') OR die('No direct script access.');
class Kohana_HTTP_Exception_301 extends HTTP_Exception_Redirect {
/**
* @var integer HTTP 301 Moved Permanently
*/
protected $_code = 301;
}

View File

@@ -0,0 +1,10 @@
<?php defined('SYSPATH') OR die('No direct script access.');
class Kohana_HTTP_Exception_302 extends HTTP_Exception_Redirect {
/**
* @var integer HTTP 302 Found
*/
protected $_code = 302;
}

View File

@@ -0,0 +1,10 @@
<?php defined('SYSPATH') OR die('No direct script access.');
class Kohana_HTTP_Exception_303 extends HTTP_Exception_Redirect {
/**
* @var integer HTTP 303 See Other
*/
protected $_code = 303;
}

View File

@@ -0,0 +1,10 @@
<?php defined('SYSPATH') OR die('No direct script access.');
class Kohana_HTTP_Exception_304 extends HTTP_Exception_Expected {
/**
* @var integer HTTP 304 Not Modified
*/
protected $_code = 304;
}

View File

@@ -0,0 +1,41 @@
<?php defined('SYSPATH') OR die('No direct script access.');
class Kohana_HTTP_Exception_305 extends HTTP_Exception_Expected {
/**
* @var integer HTTP 305 Use Proxy
*/
protected $_code = 305;
/**
* Specifies the proxy to replay this request via
*
* @param string $location URI of the proxy
*/
public function location($uri = NULL)
{
if ($uri === NULL)
return $this->headers('Location');
$this->headers('Location', $uri);
return $this;
}
/**
* Validate this exception contains everything needed to continue.
*
* @throws Kohana_Exception
* @return bool
*/
public function check()
{
if ($location = $this->headers('location') === NULL)
throw new Kohana_Exception('A \'location\' must be specified for a redirect');
if (strpos($location, '://') === FALSE)
throw new Kohana_Exception('An absolute URI to the proxy server must be specified');
return TRUE;
}
}

View File

@@ -0,0 +1,10 @@
<?php defined('SYSPATH') OR die('No direct script access.');
class Kohana_HTTP_Exception_307 extends HTTP_Exception_Redirect {
/**
* @var integer HTTP 307 Temporary Redirect
*/
protected $_code = 307;
}

View File

@@ -0,0 +1,10 @@
<?php defined('SYSPATH') OR die('No direct script access.');
class Kohana_HTTP_Exception_400 extends HTTP_Exception {
/**
* @var integer HTTP 400 Bad Request
*/
protected $_code = 400;
}

View File

@@ -0,0 +1,38 @@
<?php defined('SYSPATH') OR die('No direct script access.');
class Kohana_HTTP_Exception_401 extends HTTP_Exception_Expected {
/**
* @var integer HTTP 401 Unauthorized
*/
protected $_code = 401;
/**
* Specifies the WWW-Authenticate challenge.
*
* @param string $challenge WWW-Authenticate challenge (eg `Basic realm="Control Panel"`)
*/
public function authenticate($challenge = NULL)
{
if ($challenge === NULL)
return $this->headers('www-authenticate');
$this->headers('www-authenticate', $challenge);
return $this;
}
/**
* Validate this exception contains everything needed to continue.
*
* @throws Kohana_Exception
* @return bool
*/
public function check()
{
if ($this->headers('www-authenticate') === NULL)
throw new Kohana_Exception('A \'www-authenticate\' header must be specified for a HTTP 401 Unauthorized');
return TRUE;
}
}

View File

@@ -0,0 +1,10 @@
<?php defined('SYSPATH') OR die('No direct script access.');
class Kohana_HTTP_Exception_402 extends HTTP_Exception {
/**
* @var integer HTTP 402 Payment Required
*/
protected $_code = 402;
}

View File

@@ -0,0 +1,10 @@
<?php defined('SYSPATH') OR die('No direct script access.');
class Kohana_HTTP_Exception_403 extends HTTP_Exception {
/**
* @var integer HTTP 403 Forbidden
*/
protected $_code = 403;
}

View File

@@ -0,0 +1,10 @@
<?php defined('SYSPATH') OR die('No direct script access.');
class Kohana_HTTP_Exception_404 extends HTTP_Exception {
/**
* @var integer HTTP 404 Not Found
*/
protected $_code = 404;
}

View File

@@ -0,0 +1,41 @@
<?php defined('SYSPATH') OR die('No direct script access.');
class Kohana_HTTP_Exception_405 extends HTTP_Exception_Expected {
/**
* @var integer HTTP 405 Method Not Allowed
*/
protected $_code = 405;
/**
* Specifies the list of allowed HTTP methods
*
* @param array $methods List of allowed methods
*/
public function allowed($methods)
{
if (is_array($methods))
{
$methods = implode(',', $methods);
}
$this->headers('allow', $methods);
return $this;
}
/**
* Validate this exception contains everything needed to continue.
*
* @throws Kohana_Exception
* @return bool
*/
public function check()
{
if ($location = $this->headers('allow') === NULL)
throw new Kohana_Exception('A list of allowed methods must be specified');
return TRUE;
}
}

View File

@@ -0,0 +1,10 @@
<?php defined('SYSPATH') OR die('No direct script access.');
class Kohana_HTTP_Exception_406 extends HTTP_Exception {
/**
* @var integer HTTP 406 Not Acceptable
*/
protected $_code = 406;
}

View File

@@ -0,0 +1,10 @@
<?php defined('SYSPATH') OR die('No direct script access.');
class Kohana_HTTP_Exception_407 extends HTTP_Exception {
/**
* @var integer HTTP 407 Proxy Authentication Required
*/
protected $_code = 407;
}

View File

@@ -0,0 +1,10 @@
<?php defined('SYSPATH') OR die('No direct script access.');
class Kohana_HTTP_Exception_408 extends HTTP_Exception {
/**
* @var integer HTTP 408 Request Timeout
*/
protected $_code = 408;
}

View File

@@ -0,0 +1,10 @@
<?php defined('SYSPATH') OR die('No direct script access.');
class Kohana_HTTP_Exception_409 extends HTTP_Exception {
/**
* @var integer HTTP 409 Conflict
*/
protected $_code = 409;
}

View File

@@ -0,0 +1,10 @@
<?php defined('SYSPATH') OR die('No direct script access.');
class Kohana_HTTP_Exception_410 extends HTTP_Exception {
/**
* @var integer HTTP 410 Gone
*/
protected $_code = 410;
}

View File

@@ -0,0 +1,10 @@
<?php defined('SYSPATH') OR die('No direct script access.');
class Kohana_HTTP_Exception_411 extends HTTP_Exception {
/**
* @var integer HTTP 411 Length Required
*/
protected $_code = 411;
}

View File

@@ -0,0 +1,10 @@
<?php defined('SYSPATH') OR die('No direct script access.');
class Kohana_HTTP_Exception_412 extends HTTP_Exception {
/**
* @var integer HTTP 412 Precondition Failed
*/
protected $_code = 412;
}

View File

@@ -0,0 +1,10 @@
<?php defined('SYSPATH') OR die('No direct script access.');
class Kohana_HTTP_Exception_413 extends HTTP_Exception {
/**
* @var integer HTTP 413 Request Entity Too Large
*/
protected $_code = 413;
}

View File

@@ -0,0 +1,10 @@
<?php defined('SYSPATH') OR die('No direct script access.');
class Kohana_HTTP_Exception_414 extends HTTP_Exception {
/**
* @var integer HTTP 414 Request-URI Too Long
*/
protected $_code = 414;
}

View File

@@ -0,0 +1,10 @@
<?php defined('SYSPATH') OR die('No direct script access.');
class Kohana_HTTP_Exception_415 extends HTTP_Exception {
/**
* @var integer HTTP 415 Unsupported Media Type
*/
protected $_code = 415;
}

View File

@@ -0,0 +1,10 @@
<?php defined('SYSPATH') OR die('No direct script access.');
class Kohana_HTTP_Exception_416 extends HTTP_Exception {
/**
* @var integer HTTP 416 Request Range Not Satisfiable
*/
protected $_code = 416;
}

View File

@@ -0,0 +1,10 @@
<?php defined('SYSPATH') OR die('No direct script access.');
class Kohana_HTTP_Exception_417 extends HTTP_Exception {
/**
* @var integer HTTP 417 Expectation Failed
*/
protected $_code = 417;
}

View File

@@ -0,0 +1,10 @@
<?php defined('SYSPATH') OR die('No direct script access.');
class Kohana_HTTP_Exception_500 extends HTTP_Exception {
/**
* @var integer HTTP 500 Internal Server Error
*/
protected $_code = 500;
}

View File

@@ -0,0 +1,10 @@
<?php defined('SYSPATH') OR die('No direct script access.');
class Kohana_HTTP_Exception_501 extends HTTP_Exception {
/**
* @var integer HTTP 501 Not Implemented
*/
protected $_code = 501;
}

View File

@@ -0,0 +1,10 @@
<?php defined('SYSPATH') OR die('No direct script access.');
class Kohana_HTTP_Exception_502 extends HTTP_Exception {
/**
* @var integer HTTP 502 Bad Gateway
*/
protected $_code = 502;
}

View File

@@ -0,0 +1,10 @@
<?php defined('SYSPATH') OR die('No direct script access.');
class Kohana_HTTP_Exception_503 extends HTTP_Exception {
/**
* @var integer HTTP 503 Service Unavailable
*/
protected $_code = 503;
}

View File

@@ -0,0 +1,10 @@
<?php defined('SYSPATH') OR die('No direct script access.');
class Kohana_HTTP_Exception_504 extends HTTP_Exception {
/**
* @var integer HTTP 504 Gateway Timeout
*/
protected $_code = 504;
}

View File

@@ -0,0 +1,10 @@
<?php defined('SYSPATH') OR die('No direct script access.');
class Kohana_HTTP_Exception_505 extends HTTP_Exception {
/**
* @var integer HTTP 505 HTTP Version Not Supported
*/
protected $_code = 505;
}

View File

@@ -0,0 +1,82 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* "Expected" HTTP exception class. Used for all [HTTP_Exception]'s where a standard
* Kohana error page should never be shown.
*
* Eg [HTTP_Exception_301], [HTTP_Exception_302] etc
*
* @package Kohana
* @category Exceptions
* @author Kohana Team
* @copyright (c) 2008-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
abstract class Kohana_HTTP_Exception_Expected extends HTTP_Exception {
/**
* @var Response Response Object
*/
protected $_response;
/**
* Creates a new translated exception.
*
* throw new Kohana_Exception('Something went terrible wrong, :user',
* array(':user' => $user));
*
* @param string $message status message, custom content to display with error
* @param array $variables translation variables
* @return void
*/
public function __construct($message = NULL, array $variables = NULL, Exception $previous = NULL)
{
parent::__construct($message, $variables, $previous);
// Prepare our response object and set the correct status code.
$this->_response = Response::factory()
->status($this->_code);
}
/**
* Gets and sets headers to the [Response].
*
* @see [Response::headers]
* @param mixed $key
* @param string $value
* @return mixed
*/
public function headers($key = NULL, $value = NULL)
{
$result = $this->_response->headers($key, $value);
if ( ! $result instanceof Response)
return $result;
return $this;
}
/**
* Validate this exception contains everything needed to continue.
*
* @throws Kohana_Exception
* @return bool
*/
public function check()
{
return TRUE;
}
/**
* Generate a Response for the current Exception
*
* @uses Kohana_Exception::response()
* @return Response
*/
public function get_response()
{
$this->check();
return $this->_response;
}
} // End Kohana_HTTP_Exception

View File

@@ -0,0 +1,51 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* Redirect HTTP exception class. Used for all [HTTP_Exception]'s where the status
* code indicates a redirect.
*
* Eg [HTTP_Exception_301], [HTTP_Exception_302] and most of the other 30x's
*
* @package Kohana
* @category Exceptions
* @author Kohana Team
* @copyright (c) 2008-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
abstract class Kohana_HTTP_Exception_Redirect extends HTTP_Exception_Expected {
/**
* Specifies the URI to redirect to.
*
* @param string $location URI of the proxy
*/
public function location($uri = NULL)
{
if ($uri === NULL)
return $this->headers('Location');
if (strpos($uri, '://') === FALSE)
{
// Make the URI into a URL
$uri = URL::site($uri, TRUE, ! empty(Kohana::$index_file));
}
$this->headers('Location', $uri);
return $this;
}
/**
* Validate this exception contains everything needed to continue.
*
* @throws Kohana_Exception
* @return bool
*/
public function check()
{
if ($this->headers('location') === NULL)
throw new Kohana_Exception('A \'location\' must be specified for a redirect');
return TRUE;
}
} // End Kohana_HTTP_Exception_Redirect

View File

@@ -0,0 +1,949 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* The Kohana_HTTP_Header class provides an Object-Orientated interface
* to HTTP headers. This can parse header arrays returned from the
* PHP functions `apache_request_headers()` or the `http_parse_headers()`
* function available within the PECL HTTP library.
*
* @package Kohana
* @category HTTP
* @author Kohana Team
* @since 3.1.0
* @copyright (c) 2008-2012 Kohana Team
* @license http://kohanaphp.com/license
*/
class Kohana_HTTP_Header extends ArrayObject {
// Default Accept-* quality value if none supplied
const DEFAULT_QUALITY = 1;
/**
* Parses an Accept(-*) header and detects the quality
*
* @param array $parts accept header parts
* @return array
* @since 3.2.0
*/
public static function accept_quality(array $parts)
{
$parsed = array();
// Resource light iteration
$parts_keys = array_keys($parts);
foreach ($parts_keys as $key)
{
$value = trim(str_replace(array("\r", "\n"), '', $parts[$key]));
$pattern = '~\b(\;\s*+)?q\s*+=\s*+([.0-9]+)~';
// If there is no quality directive, return default
if ( ! preg_match($pattern, $value, $quality))
{
$parsed[$value] = (float) HTTP_Header::DEFAULT_QUALITY;
}
else
{
$quality = $quality[2];
if ($quality[0] === '.')
{
$quality = '0'.$quality;
}
// Remove the quality value from the string and apply quality
$parsed[trim(preg_replace($pattern, '', $value, 1), '; ')] = (float) $quality;
}
}
return $parsed;
}
/**
* Parses the accept header to provide the correct quality values
* for each supplied accept type.
*
* @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
* @param string $accepts accept content header string to parse
* @return array
* @since 3.2.0
*/
public static function parse_accept_header($accepts = NULL)
{
$accepts = explode(',', (string) $accepts);
// If there is no accept, lets accept everything
if ($accepts === NULL)
return array('*' => array('*' => (float) HTTP_Header::DEFAULT_QUALITY));
// Parse the accept header qualities
$accepts = HTTP_Header::accept_quality($accepts);
$parsed_accept = array();
// This method of iteration uses less resource
$keys = array_keys($accepts);
foreach ($keys as $key)
{
// Extract the parts
$parts = explode('/', $key, 2);
// Invalid content type- bail
if ( ! isset($parts[1]))
continue;
// Set the parsed output
$parsed_accept[$parts[0]][$parts[1]] = $accepts[$key];
}
return $parsed_accept;
}
/**
* Parses the `Accept-Charset:` HTTP header and returns an array containing
* the charset and associated quality.
*
* @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.2
* @param string $charset charset string to parse
* @return array
* @since 3.2.0
*/
public static function parse_charset_header($charset = NULL)
{
if ($charset === NULL)
{
return array('*' => (float) HTTP_Header::DEFAULT_QUALITY);
}
return HTTP_Header::accept_quality(explode(',', (string) $charset));
}
/**
* Parses the `Accept-Encoding:` HTTP header and returns an array containing
* the charsets and associated quality.
*
* @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3
* @param string $encoding charset string to parse
* @return array
* @since 3.2.0
*/
public static function parse_encoding_header($encoding = NULL)
{
// Accept everything
if ($encoding === NULL)
{
return array('*' => (float) HTTP_Header::DEFAULT_QUALITY);
}
elseif ($encoding === '')
{
return array('identity' => (float) HTTP_Header::DEFAULT_QUALITY);
}
else
{
return HTTP_Header::accept_quality(explode(',', (string) $encoding));
}
}
/**
* Parses the `Accept-Language:` HTTP header and returns an array containing
* the languages and associated quality.
*
* @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
* @param string $language charset string to parse
* @return array
* @since 3.2.0
*/
public static function parse_language_header($language = NULL)
{
if ($language === NULL)
{
return array('*' => array('*' => (float) HTTP_Header::DEFAULT_QUALITY));
}
$language = HTTP_Header::accept_quality(explode(',', (string) $language));
$parsed_language = array();
$keys = array_keys($language);
foreach ($keys as $key)
{
// Extract the parts
$parts = explode('-', $key, 2);
// Invalid content type- bail
if ( ! isset($parts[1]))
{
$parsed_language[$parts[0]]['*'] = $language[$key];
}
else
{
// Set the parsed output
$parsed_language[$parts[0]][$parts[1]] = $language[$key];
}
}
return $parsed_language;
}
/**
* Generates a Cache-Control HTTP header based on the supplied array.
*
* // Set the cache control headers you want to use
* $cache_control = array(
* 'max-age' => 3600,
* 'must-revalidate',
* 'public'
* );
*
* // Create the cache control header, creates :
* // cache-control: max-age=3600, must-revalidate, public
* $response->headers('Cache-Control', HTTP_Header::create_cache_control($cache_control);
*
* @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13
* @param array $cache_control Cache-Control to render to string
* @return string
*/
public static function create_cache_control(array $cache_control)
{
$parts = array();
foreach ($cache_control as $key => $value)
{
$parts[] = (is_int($key)) ? $value : ($key.'='.$value);
}
return implode(', ', $parts);
}
/**
* Parses the Cache-Control header and returning an array representation of the Cache-Control
* header.
*
* // Create the cache control header
* $response->headers('cache-control', 'max-age=3600, must-revalidate, public');
*
* // Parse the cache control header
* if ($cache_control = HTTP_Header::parse_cache_control($response->headers('cache-control')))
* {
* // Cache-Control header was found
* $maxage = $cache_control['max-age'];
* }
*
* @param array $cache_control Array of headers
* @return mixed
*/
public static function parse_cache_control($cache_control)
{
$directives = explode(',', strtolower($cache_control));
if ($directives === FALSE)
return FALSE;
$output = array();
foreach ($directives as $directive)
{
if (strpos($directive, '=') !== FALSE)
{
list($key, $value) = explode('=', trim($directive), 2);
$output[$key] = ctype_digit($value) ? (int) $value : $value;
}
else
{
$output[] = trim($directive);
}
}
return $output;
}
/**
* @var array Accept: (content) types
*/
protected $_accept_content;
/**
* @var array Accept-Charset: parsed header
*/
protected $_accept_charset;
/**
* @var array Accept-Encoding: parsed header
*/
protected $_accept_encoding;
/**
* @var array Accept-Language: parsed header
*/
protected $_accept_language;
/**
* Constructor method for [Kohana_HTTP_Header]. Uses the standard constructor
* of the parent `ArrayObject` class.
*
* $header_object = new HTTP_Header(array('x-powered-by' => 'Kohana 3.1.x', 'expires' => '...'));
*
* @param mixed $input Input array
* @param int $flags Flags
* @param string $iterator_class The iterator class to use
*/
public function __construct(array $input = array(), $flags = NULL, $iterator_class = 'ArrayIterator')
{
/**
* @link http://www.w3.org/Protocols/rfc2616/rfc2616.html
*
* HTTP header declarations should be treated as case-insensitive
*/
$input = array_change_key_case( (array) $input, CASE_LOWER);
parent::__construct($input, $flags, $iterator_class);
}
/**
* Returns the header object as a string, including
* the terminating new line
*
* // Return the header as a string
* echo (string) $request->headers();
*
* @return string
*/
public function __toString()
{
$header = '';
foreach ($this as $key => $value)
{
// Put the keys back the Case-Convention expected
$key = Text::ucfirst($key);
if (is_array($value))
{
$header .= $key.': '.(implode(', ', $value))."\r\n";
}
else
{
$header .= $key.': '.$value."\r\n";
}
}
return $header."\r\n";
}
/**
* Overloads `ArrayObject::offsetSet()` to enable handling of header
* with multiple instances of the same directive. If the `$replace` flag
* is `FALSE`, the header will be appended rather than replacing the
* original setting.
*
* @param mixed $index index to set `$newval` to
* @param mixed $newval new value to set
* @param boolean $replace replace existing value
* @return void
* @since 3.2.0
*/
public function offsetSet($index, $newval, $replace = TRUE)
{
// Ensure the index is lowercase
$index = strtolower($index);
if ($replace OR ! $this->offsetExists($index))
{
return parent::offsetSet($index, $newval);
}
$current_value = $this->offsetGet($index);
if (is_array($current_value))
{
$current_value[] = $newval;
}
else
{
$current_value = array($current_value, $newval);
}
return parent::offsetSet($index, $current_value);
}
/**
* Overloads the `ArrayObject::offsetExists()` method to ensure keys
* are lowercase.
*
* @param string $index
* @return boolean
* @since 3.2.0
*/
public function offsetExists($index)
{
return parent::offsetExists(strtolower($index));
}
/**
* Overloads the `ArrayObject::offsetUnset()` method to ensure keys
* are lowercase.
*
* @param string $index
* @return void
* @since 3.2.0
*/
public function offsetUnset($index)
{
return parent::offsetUnset(strtolower($index));
}
/**
* Overload the `ArrayObject::offsetGet()` method to ensure that all
* keys passed to it are formatted correctly for this object.
*
* @param string $index index to retrieve
* @return mixed
* @since 3.2.0
*/
public function offsetGet($index)
{
return parent::offsetGet(strtolower($index));
}
/**
* Overloads the `ArrayObject::exchangeArray()` method to ensure that
* all keys are changed to lowercase.
*
* @param mixed $input
* @return array
* @since 3.2.0
*/
public function exchangeArray($input)
{
/**
* @link http://www.w3.org/Protocols/rfc2616/rfc2616.html
*
* HTTP header declarations should be treated as case-insensitive
*/
$input = array_change_key_case( (array) $input, CASE_LOWER);
return parent::exchangeArray($input);
}
/**
* Parses a HTTP Message header line and applies it to this HTTP_Header
*
* $header = $response->headers();
* $header->parse_header_string(NULL, 'content-type: application/json');
*
* @param resource $resource the resource (required by Curl API)
* @param string $header_line the line from the header to parse
* @return int
* @since 3.2.0
*/
public function parse_header_string($resource, $header_line)
{
$headers = array();
if (preg_match_all('/(\w[^\s:]*):[ ]*([^\r\n]*(?:\r\n[ \t][^\r\n]*)*)/', $header_line, $matches))
{
foreach ($matches[0] as $key => $value)
{
$this->offsetSet($matches[1][$key], $matches[2][$key], FALSE);
}
}
return strlen($header_line);
}
/**
* Returns the accept quality of a submitted mime type based on the
* request `Accept:` header. If the `$explicit` argument is `TRUE`,
* only precise matches will be returned, excluding all wildcard (`*`)
* directives.
*
* // Accept: application/xml; application/json; q=.5; text/html; q=.2, text/*
* // Accept quality for application/json
*
* // $quality = 0.5
* $quality = $request->headers()->accepts_at_quality('application/json');
*
* // $quality_explicit = FALSE
* $quality_explicit = $request->headers()->accepts_at_quality('text/plain', TRUE);
*
* @param string $type
* @param boolean $explicit explicit check, excludes `*`
* @return mixed
* @since 3.2.0
*/
public function accepts_at_quality($type, $explicit = FALSE)
{
// Parse Accept header if required
if ($this->_accept_content === NULL)
{
if ($this->offsetExists('Accept'))
{
$accept = $this->offsetGet('Accept');
}
else
{
$accept = '*/*';
}
$this->_accept_content = HTTP_Header::parse_accept_header($accept);
}
// If not a real mime, try and find it in config
if (strpos($type, '/') === FALSE)
{
$mime = Kohana::$config->load('mimes.'.$type);
if ($mime === NULL)
return FALSE;
$quality = FALSE;
foreach ($mime as $_type)
{
$quality_check = $this->accepts_at_quality($_type, $explicit);
$quality = ($quality_check > $quality) ? $quality_check : $quality;
}
return $quality;
}
$parts = explode('/', $type, 2);
if (isset($this->_accept_content[$parts[0]][$parts[1]]))
{
return $this->_accept_content[$parts[0]][$parts[1]];
}
elseif ($explicit === TRUE)
{
return FALSE;
}
else
{
if (isset($this->_accept_content[$parts[0]]['*']))
{
return $this->_accept_content[$parts[0]]['*'];
}
elseif (isset($this->_accept_content['*']['*']))
{
return $this->_accept_content['*']['*'];
}
else
{
return FALSE;
}
}
}
/**
* Returns the preferred response content type based on the accept header
* quality settings. If items have the same quality value, the first item
* found in the array supplied as `$types` will be returned.
*
* // Get the preferred acceptable content type
* // Accept: text/html, application/json; q=.8, text/*
* $result = $header->preferred_accept(array(
* 'text/html'
* 'text/rtf',
* 'application/json'
* )); // $result = 'application/json'
*
* $result = $header->preferred_accept(array(
* 'text/rtf',
* 'application/xml'
* ), TRUE); // $result = FALSE (none matched explicitly)
*
*
* @param array $types the content types to examine
* @param boolean $explicit only allow explicit references, no wildcards
* @return string name of the preferred content type
* @since 3.2.0
*/
public function preferred_accept(array $types, $explicit = FALSE)
{
$preferred = FALSE;
$ceiling = 0;
foreach ($types as $type)
{
$quality = $this->accepts_at_quality($type, $explicit);
if ($quality > $ceiling)
{
$preferred = $type;
$ceiling = $quality;
}
}
return $preferred;
}
/**
* Returns the quality of the supplied `$charset` argument. This method
* will automatically parse the `Accept-Charset` header if present and
* return the associated resolved quality value.
*
* // Accept-Charset: utf-8, utf-16; q=.8, iso-8859-1; q=.5
* $quality = $header->accepts_charset_at_quality('utf-8');
* // $quality = (float) 1
*
* @param string $charset charset to examine
* @return float the quality of the charset
* @since 3.2.0
*/
public function accepts_charset_at_quality($charset)
{
if ($this->_accept_charset === NULL)
{
if ($this->offsetExists('Accept-Charset'))
{
$charset_header = strtolower($this->offsetGet('Accept-Charset'));
$this->_accept_charset = HTTP_Header::parse_charset_header($charset_header);
}
else
{
$this->_accept_charset = HTTP_Header::parse_charset_header(NULL);
}
}
$charset = strtolower($charset);
if (isset($this->_accept_charset[$charset]))
{
return $this->_accept_charset[$charset];
}
elseif (isset($this->_accept_charset['*']))
{
return $this->_accept_charset['*'];
}
elseif ($charset === 'iso-8859-1')
{
return (float) 1;
}
return (float) 0;
}
/**
* Returns the preferred charset from the supplied array `$charsets` based
* on the `Accept-Charset` header directive.
*
* // Accept-Charset: utf-8, utf-16; q=.8, iso-8859-1; q=.5
* $charset = $header->preferred_charset(array(
* 'utf-10', 'ascii', 'utf-16', 'utf-8'
* )); // $charset = 'utf-8'
*
* @param array $charsets charsets to test
* @return mixed preferred charset or `FALSE`
* @since 3.2.0
*/
public function preferred_charset(array $charsets)
{
$preferred = FALSE;
$ceiling = 0;
foreach ($charsets as $charset)
{
$quality = $this->accepts_charset_at_quality($charset);
if ($quality > $ceiling)
{
$preferred = $charset;
$ceiling = $quality;
}
}
return $preferred;
}
/**
* Returns the quality of the `$encoding` type passed to it. Encoding
* is usually compression such as `gzip`, but could be some other
* message encoding algorithm. This method allows explicit checks to be
* done ignoring wildcards.
*
* // Accept-Encoding: compress, gzip, *; q=.5
* $encoding = $header->accepts_encoding_at_quality('gzip');
* // $encoding = (float) 1.0s
*
* @param string $encoding encoding type to interrogate
* @param boolean $explicit explicit check, ignoring wildcards and `identity`
* @return float
* @since 3.2.0
*/
public function accepts_encoding_at_quality($encoding, $explicit = FALSE)
{
if ($this->_accept_encoding === NULL)
{
if ($this->offsetExists('Accept-Encoding'))
{
$encoding_header = $this->offsetGet('Accept-Encoding');
}
else
{
$encoding_header = NULL;
}
$this->_accept_encoding = HTTP_Header::parse_encoding_header($encoding_header);
}
// Normalize the encoding
$encoding = strtolower($encoding);
if (isset($this->_accept_encoding[$encoding]))
{
return $this->_accept_encoding[$encoding];
}
if ($explicit === FALSE)
{
if (isset($this->_accept_encoding['*']))
{
return $this->_accept_encoding['*'];
}
elseif ($encoding === 'identity')
{
return (float) HTTP_Header::DEFAULT_QUALITY;
}
}
return (float) 0;
}
/**
* Returns the preferred message encoding type based on quality, and can
* optionally ignore wildcard references. If two or more encodings have the
* same quality, the first listed in `$encodings` will be returned.
*
* // Accept-Encoding: compress, gzip, *; q.5
* $encoding = $header->preferred_encoding(array(
* 'gzip', 'bzip', 'blowfish'
* ));
* // $encoding = 'gzip';
*
* @param array $encodings encodings to test against
* @param boolean $explicit explicit check, if `TRUE` wildcards are excluded
* @return mixed
* @since 3.2.0
*/
public function preferred_encoding(array $encodings, $explicit = FALSE)
{
$ceiling = 0;
$preferred = FALSE;
foreach ($encodings as $encoding)
{
$quality = $this->accepts_encoding_at_quality($encoding, $explicit);
if ($quality > $ceiling)
{
$ceiling = $quality;
$preferred = $encoding;
}
}
return $preferred;
}
/**
* Returns the quality of `$language` supplied, optionally ignoring
* wildcards if `$explicit` is set to a non-`FALSE` value. If the quality
* is not found, `0.0` is returned.
*
* // Accept-Language: en-us, en-gb; q=.7, en; q=.5
* $lang = $header->accepts_language_at_quality('en-gb');
* // $lang = (float) 0.7
*
* $lang2 = $header->accepts_language_at_quality('en-au');
* // $lang2 = (float) 0.5
*
* $lang3 = $header->accepts_language_at_quality('en-au', TRUE);
* // $lang3 = (float) 0.0
*
* @param string $language language to interrogate
* @param boolean $explicit explicit interrogation, `TRUE` ignores wildcards
* @return float
* @since 3.2.0
*/
public function accepts_language_at_quality($language, $explicit = FALSE)
{
if ($this->_accept_language === NULL)
{
if ($this->offsetExists('Accept-Language'))
{
$language_header = strtolower($this->offsetGet('Accept-Language'));
}
else
{
$language_header = NULL;
}
$this->_accept_language = HTTP_Header::parse_language_header($language_header);
}
// Normalize the language
$language_parts = explode('-', strtolower($language), 2);
if (isset($this->_accept_language[$language_parts[0]]))
{
if (isset($language_parts[1]))
{
if (isset($this->_accept_language[$language_parts[0]][$language_parts[1]]))
{
return $this->_accept_language[$language_parts[0]][$language_parts[1]];
}
elseif ($explicit === FALSE AND isset($this->_accept_language[$language_parts[0]]['*']))
{
return $this->_accept_language[$language_parts[0]]['*'];
}
}
elseif (isset($this->_accept_language[$language_parts[0]]['*']))
{
return $this->_accept_language[$language_parts[0]]['*'];
}
}
if ($explicit === FALSE AND isset($this->_accept_language['*']))
{
return $this->_accept_language['*'];
}
return (float) 0;
}
/**
* Returns the preferred language from the supplied array `$languages` based
* on the `Accept-Language` header directive.
*
* // Accept-Language: en-us, en-gb; q=.7, en; q=.5
* $lang = $header->preferred_language(array(
* 'en-gb', 'en-au', 'fr', 'es'
* )); // $lang = 'en-gb'
*
* @param array $languages
* @param boolean $explicit
* @return mixed
* @since 3.2.0
*/
public function preferred_language(array $languages, $explicit = FALSE)
{
$ceiling = 0;
$preferred = FALSE;
foreach ($languages as $language)
{
$quality = $this->accepts_language_at_quality($language, $explicit);
if ($quality > $ceiling)
{
$ceiling = $quality;
$preferred = $language;
}
}
return $preferred;
}
/**
* Sends headers to the php processor, or supplied `$callback` argument.
* This method formats the headers correctly for output, re-instating their
* capitalization for transmission.
*
* [!!] if you supply a custom header handler via `$callback`, it is
* recommended that `$response` is returned
*
* @param HTTP_Response $response header to send
* @param boolean $replace replace existing value
* @param callback $callback optional callback to replace PHP header function
* @return mixed
* @since 3.2.0
*/
public function send_headers(HTTP_Response $response = NULL, $replace = FALSE, $callback = NULL)
{
if ($response === NULL)
{
// Default to the initial request message
$response = Request::initial()->response();
}
$protocol = $response->protocol();
$status = $response->status();
// Create the response header
$processed_headers = array($protocol.' '.$status.' '.Response::$messages[$status]);
// Get the headers array
$headers = $response->headers()->getArrayCopy();
foreach ($headers as $header => $value)
{
if (is_array($value))
{
$value = implode(', ', $value);
}
$processed_headers[] = Text::ucfirst($header).': '.$value;
}
if ( ! isset($headers['content-type']))
{
$processed_headers[] = 'Content-Type: '.Kohana::$content_type.'; charset='.Kohana::$charset;
}
if (Kohana::$expose AND ! isset($headers['x-powered-by']))
{
$processed_headers[] = 'X-Powered-By: '.Kohana::version();
}
// Get the cookies and apply
if ($cookies = $response->cookie())
{
$processed_headers['Set-Cookie'] = $cookies;
}
if (is_callable($callback))
{
// Use the callback method to set header
return call_user_func($callback, $response, $processed_headers, $replace);
}
else
{
$this->_send_headers_to_php($processed_headers, $replace);
return $response;
}
}
/**
* Sends the supplied headers to the PHP output buffer. If cookies
* are included in the message they will be handled appropriately.
*
* @param array $headers headers to send to php
* @param boolean $replace replace existing headers
* @return self
* @since 3.2.0
*/
protected function _send_headers_to_php(array $headers, $replace)
{
// If the headers have been sent, get out
if (headers_sent())
return $this;
foreach ($headers as $key => $line)
{
if ($key == 'Set-Cookie' AND is_array($line))
{
// Send cookies
foreach ($line as $name => $value)
{
Cookie::set($name, $value['value'], $value['expiration']);
}
continue;
}
header($line, $replace);
}
return $this;
}
} // End Kohana_HTTP_Header

View File

@@ -0,0 +1,56 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* The HTTP Interaction interface providing the core HTTP methods that
* should be implemented by any HTTP request or response class.
*
* @package Kohana
* @category HTTP
* @author Kohana Team
* @since 3.1.0
* @copyright (c) 2008-2012 Kohana Team
* @license http://kohanaphp.com/license
*/
interface Kohana_HTTP_Message {
/**
* Gets or sets the HTTP protocol. The standard protocol to use
* is `HTTP/1.1`.
*
* @param string $protocol Protocol to set to the request/response
* @return mixed
*/
public function protocol($protocol = NULL);
/**
* Gets or sets HTTP headers to the request or response. All headers
* are included immediately after the HTTP protocol definition during
* transmission. This method provides a simple array or key/value
* interface to the headers.
*
* @param mixed $key Key or array of key/value pairs to set
* @param string $value Value to set to the supplied key
* @return mixed
*/
public function headers($key = NULL, $value = NULL);
/**
* Gets or sets the HTTP body to the request or response. The body is
* included after the header, separated by a single empty new line.
*
* @param string $content Content to set to the object
* @return string
* @return void
*/
public function body($content = NULL);
/**
* Renders the HTTP_Interaction to a string, producing
*
* - Protocol
* - Headers
* - Body
*
* @return string
*/
public function render();
}

View File

@@ -0,0 +1,64 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* A HTTP Request specific interface that adds the methods required
* by HTTP requests. Over and above [Kohana_HTTP_Interaction], this
* interface provides method, uri, get and post methods.
*
* @package Kohana
* @category HTTP
* @author Kohana Team
* @since 3.1.0
* @copyright (c) 2008-2012 Kohana Team
* @license http://kohanaphp.com/license
*/
interface Kohana_HTTP_Request extends HTTP_Message {
// HTTP Methods
const GET = 'GET';
const POST = 'POST';
const PUT = 'PUT';
const DELETE = 'DELETE';
const HEAD = 'HEAD';
const OPTIONS = 'OPTIONS';
const TRACE = 'TRACE';
const CONNECT = 'CONNECT';
/**
* Gets or sets the HTTP method. Usually GET, POST, PUT or DELETE in
* traditional CRUD applications.
*
* @param string $method Method to use for this request
* @return mixed
*/
public function method($method = NULL);
/**
* Gets the URI of this request, optionally allows setting
* of [Route] specific parameters during the URI generation.
* If no parameters are passed, the request will use the
* default values defined in the Route.
*
* @param array $params Optional parameters to include in uri generation
* @return string
*/
public function uri();
/**
* Gets or sets HTTP query string.
*
* @param mixed $key Key or key value pairs to set
* @param string $value Value to set to a key
* @return mixed
*/
public function query($key = NULL, $value = NULL);
/**
* Gets or sets HTTP POST parameters to the request.
*
* @param mixed $key Key or key value pairs to set
* @param string $value Value to set to a key
* @return mixed
*/
public function post($key = NULL, $value = NULL);
}

View File

@@ -0,0 +1,31 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* A HTTP Reponse specific interface that adds the methods required
* by HTTP responses. Over and above [Kohana_HTTP_Interaction], this
* interface provides status.
*
* @package Kohana
* @category HTTP
* @author Kohana Team
* @since 3.1.0
* @copyright (c) 2008-2012 Kohana Team
* @license http://kohanaphp.com/license
*/
interface Kohana_HTTP_Response extends HTTP_Message {
/**
* Sets or gets the HTTP status from this response.
*
* // Set the HTTP status to 404 Not Found
* $response = Response::factory()
* ->status(404);
*
* // Get the current status
* $status = $response->status();
*
* @param integer $code Status to set to this response
* @return mixed
*/
public function status($code = NULL);
}

View File

@@ -0,0 +1,166 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* Internationalization (i18n) class. Provides language loading and translation
* methods without dependencies on [gettext](http://php.net/gettext).
*
* Typically this class would never be used directly, but used via the __()
* function, which loads the message and replaces parameters:
*
* // Display a translated message
* echo __('Hello, world');
*
* // With parameter replacement
* echo __('Hello, :user', array(':user' => $username));
*
* @package Kohana
* @category Base
* @author Kohana Team
* @copyright (c) 2008-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
class Kohana_I18n {
/**
* @var string target language: en-us, es-es, zh-cn, etc
*/
public static $lang = 'en-us';
/**
* @var string source language: en-us, es-es, zh-cn, etc
*/
public static $source = 'en-us';
/**
* @var array cache of loaded languages
*/
protected static $_cache = array();
/**
* Get and set the target language.
*
* // Get the current language
* $lang = I18n::lang();
*
* // Change the current language to Spanish
* I18n::lang('es-es');
*
* @param string $lang new language setting
* @return string
* @since 3.0.2
*/
public static function lang($lang = NULL)
{
if ($lang)
{
// Normalize the language
I18n::$lang = strtolower(str_replace(array(' ', '_'), '-', $lang));
}
return I18n::$lang;
}
/**
* Returns translation of a string. If no translation exists, the original
* string will be returned. No parameters are replaced.
*
* $hello = I18n::get('Hello friends, my name is :name');
*
* @param string $string text to translate
* @param string $lang target language
* @return string
*/
public static function get($string, $lang = NULL)
{
if ( ! $lang)
{
// Use the global target language
$lang = I18n::$lang;
}
// Load the translation table for this language
$table = I18n::load($lang);
// Return the translated string if it exists
return isset($table[$string]) ? $table[$string] : $string;
}
/**
* Returns the translation table for a given language.
*
* // Get all defined Spanish messages
* $messages = I18n::load('es-es');
*
* @param string $lang language to load
* @return array
*/
public static function load($lang)
{
if (isset(I18n::$_cache[$lang]))
{
return I18n::$_cache[$lang];
}
// New translation table
$table = array();
// Split the language: language, region, locale, etc
$parts = explode('-', $lang);
do
{
// Create a path for this set of parts
$path = implode(DIRECTORY_SEPARATOR, $parts);
if ($files = Kohana::find_file('i18n', $path, NULL, TRUE))
{
$t = array();
foreach ($files as $file)
{
// Merge the language strings into the sub table
$t = array_merge($t, Kohana::load($file));
}
// Append the sub table, preventing less specific language
// files from overloading more specific files
$table += $t;
}
// Remove the last part
array_pop($parts);
}
while ($parts);
// Cache the translation table locally
return I18n::$_cache[$lang] = $table;
}
} // End I18n
if ( ! function_exists('__'))
{
/**
* Kohana translation/internationalization function. The PHP function
* [strtr](http://php.net/strtr) is used for replacing parameters.
*
* __('Welcome back, :user', array(':user' => $username));
*
* [!!] The target language is defined by [I18n::$lang].
*
* @uses I18n::get
* @param string $string text to translate
* @param array $values values to replace in the translated text
* @param string $lang source language
* @return string
*/
function __($string, array $values = NULL, $lang = 'en-us')
{
if ($lang !== I18n::$lang)
{
// The message and target languages are different
// Get the translation for this message
$string = I18n::get($string);
}
return empty($values) ? $string : strtr($string, $values);
}
}

View File

@@ -0,0 +1,269 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* Inflector helper class. Inflection is changing the form of a word based on
* the context it is used in. For example, changing a word into a plural form.
*
* [!!] Inflection is only tested with English, and is will not work with other languages.
*
* @package Kohana
* @category Helpers
* @author Kohana Team
* @copyright (c) 2007-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
class Kohana_Inflector {
/**
* @var array cached inflections
*/
protected static $cache = array();
/**
* @var array uncountable words
*/
protected static $uncountable;
/**
* @var array irregular words
*/
protected static $irregular;
/**
* Checks if a word is defined as uncountable. An uncountable word has a
* single form. For instance, one "fish" and many "fish", not "fishes".
*
* Inflector::uncountable('fish'); // TRUE
* Inflector::uncountable('cat'); // FALSE
*
* If you find a word is being pluralized improperly, it has probably not
* been defined as uncountable in `config/inflector.php`. If this is the
* case, please report [an issue](http://dev.kohanaphp.com/projects/kohana3/issues).
*
* @param string $str word to check
* @return boolean
*/
public static function uncountable($str)
{
if (Inflector::$uncountable === NULL)
{
// Cache uncountables
Inflector::$uncountable = Kohana::$config->load('inflector')->uncountable;
// Make uncountables mirrored
Inflector::$uncountable = array_combine(Inflector::$uncountable, Inflector::$uncountable);
}
return isset(Inflector::$uncountable[strtolower($str)]);
}
/**
* Makes a plural word singular.
*
* echo Inflector::singular('cats'); // "cat"
* echo Inflector::singular('fish'); // "fish", uncountable
*
* You can also provide the count to make inflection more intelligent.
* In this case, it will only return the singular value if the count is
* greater than one and not zero.
*
* echo Inflector::singular('cats', 2); // "cats"
*
* [!!] Special inflections are defined in `config/inflector.php`.
*
* @param string $str word to singularize
* @param integer $count count of thing
* @return string
* @uses Inflector::uncountable
*/
public static function singular($str, $count = NULL)
{
// $count should always be a float
$count = ($count === NULL) ? 1.0 : (float) $count;
// Do nothing when $count is not 1
if ($count != 1)
return $str;
// Remove garbage
$str = strtolower(trim($str));
// Cache key name
$key = 'singular_'.$str.$count;
if (isset(Inflector::$cache[$key]))
return Inflector::$cache[$key];
if (Inflector::uncountable($str))
return Inflector::$cache[$key] = $str;
if (empty(Inflector::$irregular))
{
// Cache irregular words
Inflector::$irregular = Kohana::$config->load('inflector')->irregular;
}
if ($irregular = array_search($str, Inflector::$irregular))
{
$str = $irregular;
}
elseif (preg_match('/us$/', $str))
{
// http://en.wikipedia.org/wiki/Plural_form_of_words_ending_in_-us
// Already singular, do nothing
}
elseif (preg_match('/[sxz]es$/', $str) OR preg_match('/[^aeioudgkprt]hes$/', $str))
{
// Remove "es"
$str = substr($str, 0, -2);
}
elseif (preg_match('/[^aeiou]ies$/', $str))
{
// Replace "ies" with "y"
$str = substr($str, 0, -3).'y';
}
elseif (substr($str, -1) === 's' AND substr($str, -2) !== 'ss')
{
// Remove singular "s"
$str = substr($str, 0, -1);
}
return Inflector::$cache[$key] = $str;
}
/**
* Makes a singular word plural.
*
* echo Inflector::plural('fish'); // "fish", uncountable
* echo Inflector::plural('cat'); // "cats"
*
* You can also provide the count to make inflection more intelligent.
* In this case, it will only return the plural value if the count is
* not one.
*
* echo Inflector::singular('cats', 3); // "cats"
*
* [!!] Special inflections are defined in `config/inflector.php`.
*
* @param string $str word to pluralize
* @param integer $count count of thing
* @return string
* @uses Inflector::uncountable
*/
public static function plural($str, $count = NULL)
{
// $count should always be a float
$count = ($count === NULL) ? 0.0 : (float) $count;
// Do nothing with singular
if ($count == 1)
return $str;
// Remove garbage
$str = trim($str);
// Cache key name
$key = 'plural_'.$str.$count;
// Check uppercase
$is_uppercase = ctype_upper($str);
if (isset(Inflector::$cache[$key]))
return Inflector::$cache[$key];
if (Inflector::uncountable($str))
return Inflector::$cache[$key] = $str;
if (empty(Inflector::$irregular))
{
// Cache irregular words
Inflector::$irregular = Kohana::$config->load('inflector')->irregular;
}
if (isset(Inflector::$irregular[$str]))
{
$str = Inflector::$irregular[$str];
}
elseif (preg_match('/[sxz]$/', $str) OR preg_match('/[^aeioudgkprt]h$/', $str))
{
$str .= 'es';
}
elseif (preg_match('/[^aeiou]y$/', $str))
{
// Change "y" to "ies"
$str = substr_replace($str, 'ies', -1);
}
else
{
$str .= 's';
}
// Convert to uppsecase if nessasary
if ($is_uppercase)
{
$str = strtoupper($str);
}
// Set the cache and return
return Inflector::$cache[$key] = $str;
}
/**
* Makes a phrase camel case. Spaces and underscores will be removed.
*
* $str = Inflector::camelize('mother cat'); // "motherCat"
* $str = Inflector::camelize('kittens in bed'); // "kittensInBed"
*
* @param string $str phrase to camelize
* @return string
*/
public static function camelize($str)
{
$str = 'x'.strtolower(trim($str));
$str = ucwords(preg_replace('/[\s_]+/', ' ', $str));
return substr(str_replace(' ', '', $str), 1);
}
/**
* Converts a camel case phrase into a spaced phrase.
*
* $str = Inflector::decamelize('houseCat'); // "house cat"
* $str = Inflector::decamelize('kingAllyCat'); // "king ally cat"
*
* @param string $str phrase to camelize
* @param string $sep word separator
* @return string
*/
public static function decamelize($str, $sep = ' ')
{
return strtolower(preg_replace('/([a-z])([A-Z])/', '$1'.$sep.'$2', trim($str)));
}
/**
* Makes a phrase underscored instead of spaced.
*
* $str = Inflector::underscore('five cats'); // "five_cats";
*
* @param string $str phrase to underscore
* @return string
*/
public static function underscore($str)
{
return preg_replace('/\s+/', '_', trim($str));
}
/**
* Makes an underscored or dashed phrase human-readable.
*
* $str = Inflector::humanize('kittens-are-cats'); // "kittens are cats"
* $str = Inflector::humanize('dogs_as_well'); // "dogs as well"
*
* @param string $str phrase to make human-readable
* @return string
*/
public static function humanize($str)
{
return preg_replace('/[_-]+/', ' ', trim($str));
}
} // End Inflector

View File

@@ -0,0 +1,282 @@
<?php defined('SYSPATH') OR die('No direct access');
/**
* Kohana exception class. Translates exceptions using the [I18n] class.
*
* @package Kohana
* @category Exceptions
* @author Kohana Team
* @copyright (c) 2008-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
class Kohana_Kohana_Exception extends Exception {
/**
* @var array PHP error code => human readable name
*/
public static $php_errors = array(
E_ERROR => 'Fatal Error',
E_USER_ERROR => 'User Error',
E_PARSE => 'Parse Error',
E_WARNING => 'Warning',
E_USER_WARNING => 'User Warning',
E_STRICT => 'Strict',
E_NOTICE => 'Notice',
E_RECOVERABLE_ERROR => 'Recoverable Error',
E_DEPRECATED => 'Deprecated',
);
/**
* @var string error rendering view
*/
public static $error_view = 'kohana/error';
/**
* @var string error view content type
*/
public static $error_view_content_type = 'text/html';
/**
* Creates a new translated exception.
*
* throw new Kohana_Exception('Something went terrible wrong, :user',
* array(':user' => $user));
*
* @param string $message error message
* @param array $variables translation variables
* @param integer|string $code the exception code
* @param Exception $previous Previous exception
* @return void
*/
public function __construct($message = "", array $variables = NULL, $code = 0, Exception $previous = NULL)
{
// Set the message
$message = __($message, $variables);
// Pass the message and integer code to the parent
parent::__construct($message, (int) $code, $previous);
// Save the unmodified code
// @link http://bugs.php.net/39615
$this->code = $code;
}
/**
* Magic object-to-string method.
*
* echo $exception;
*
* @uses Kohana_Exception::text
* @return string
*/
public function __toString()
{
return Kohana_Exception::text($this);
}
/**
* Inline exception handler, displays the error message, source of the
* exception, and the stack trace of the error.
*
* @uses Kohana_Exception::response
* @param Exception $e
* @return boolean
*/
public static function handler(Exception $e)
{
$response = Kohana_Exception::_handler($e);
// Send the response to the browser
echo $response->send_headers()->body();
exit(1);
}
/**
* Exception handler, logs the exception and generates a Response object
* for display.
*
* @uses Kohana_Exception::response
* @param Exception $e
* @return boolean
*/
public static function _handler(Exception $e)
{
try
{
// Log the exception
Kohana_Exception::log($e);
// Generate the response
$response = Kohana_Exception::response($e);
return $response;
}
catch (Exception $e)
{
/**
* Things are going *really* badly for us, We now have no choice
* but to bail. Hard.
*/
// Clean the output buffer if one exists
ob_get_level() AND ob_clean();
// Set the Status code to 500, and Content-Type to text/plain.
header('Content-Type: text/plain; charset='.Kohana::$charset, TRUE, 500);
echo Kohana_Exception::text($e);
exit(1);
}
}
/**
* Logs an exception.
*
* @uses Kohana_Exception::text
* @param Exception $e
* @param int $level
* @return void
*/
public static function log(Exception $e, $level = Log::EMERGENCY)
{
if (is_object(Kohana::$log))
{
// Create a text version of the exception
$error = Kohana_Exception::text($e);
// Add this exception to the log
Kohana::$log->add($level, $error, NULL, array('exception' => $e));
// Make sure the logs are written
Kohana::$log->write();
}
}
/**
* Get a single line of text representing the exception:
*
* Error [ Code ]: Message ~ File [ Line ]
*
* @param Exception $e
* @return string
*/
public static function text(Exception $e)
{
return sprintf('%s [ %s ]: %s ~ %s [ %d ]',
get_class($e), $e->getCode(), strip_tags($e->getMessage()), Debug::path($e->getFile()), $e->getLine());
}
/**
* Get a Response object representing the exception
*
* @uses Kohana_Exception::text
* @param Exception $e
* @return Response
*/
public static function response(Exception $e)
{
try
{
// Get the exception information
$class = get_class($e);
$code = $e->getCode();
$message = $e->getMessage();
$file = $e->getFile();
$line = $e->getLine();
$trace = $e->getTrace();
if ( ! headers_sent())
{
// Make sure the proper http header is sent
$http_header_status = ($e instanceof HTTP_Exception) ? $code : 500;
}
/**
* HTTP_Exceptions are constructed in the HTTP_Exception::factory()
* method. We need to remove that entry from the trace and overwrite
* the variables from above.
*/
if ($e instanceof HTTP_Exception AND $trace[0]['function'] == 'factory')
{
extract(array_shift($trace));
}
if ($e instanceof ErrorException)
{
/**
* If XDebug is installed, and this is a fatal error,
* use XDebug to generate the stack trace
*/
if (function_exists('xdebug_get_function_stack') AND $code == E_ERROR)
{
$trace = array_slice(array_reverse(xdebug_get_function_stack()), 4);
foreach ($trace as & $frame)
{
/**
* XDebug pre 2.1.1 doesn't currently set the call type key
* http://bugs.xdebug.org/view.php?id=695
*/
if ( ! isset($frame['type']))
{
$frame['type'] = '??';
}
// XDebug also has a different name for the parameters array
if (isset($frame['params']) AND ! isset($frame['args']))
{
$frame['args'] = $frame['params'];
}
}
}
if (isset(Kohana_Exception::$php_errors[$code]))
{
// Use the human-readable error name
$code = Kohana_Exception::$php_errors[$code];
}
}
/**
* The stack trace becomes unmanageable inside PHPUnit.
*
* The error view ends up several GB in size, taking
* serveral minutes to render.
*/
if (defined('PHPUnit_MAIN_METHOD'))
{
$trace = array_slice($trace, 0, 2);
}
// Instantiate the error view.
$view = View::factory(Kohana_Exception::$error_view, get_defined_vars());
// Prepare the response object.
$response = Response::factory();
// Set the response status
$response->status(($e instanceof HTTP_Exception) ? $e->getCode() : 500);
// Set the response headers
$response->headers('Content-Type', Kohana_Exception::$error_view_content_type.'; charset='.Kohana::$charset);
// Set the response body
$response->body($view->render());
}
catch (Exception $e)
{
/**
* Things are going badly for us, Lets try to keep things under control by
* generating a simpler response object.
*/
$response = Response::factory();
$response->status(500);
$response->headers('Content-Type', 'text/plain');
$response->body(Kohana_Exception::text($e));
}
return $response;
}
} // End Kohana_Exception

View File

@@ -0,0 +1,228 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* Message logging with observer-based log writing.
*
* [!!] This class does not support extensions, only additional writers.
*
* @package Kohana
* @category Logging
* @author Kohana Team
* @copyright (c) 2008-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
class Kohana_Log {
// Log message levels - Windows users see PHP Bug #18090
const EMERGENCY = LOG_EMERG; // 0
const ALERT = LOG_ALERT; // 1
const CRITICAL = LOG_CRIT; // 2
const ERROR = LOG_ERR; // 3
const WARNING = LOG_WARNING; // 4
const NOTICE = LOG_NOTICE; // 5
const INFO = LOG_INFO; // 6
const DEBUG = LOG_DEBUG; // 7
/**
* @var boolean immediately write when logs are added
*/
public static $write_on_add = FALSE;
/**
* @var Log Singleton instance container
*/
protected static $_instance;
/**
* Get the singleton instance of this class and enable writing at shutdown.
*
* $log = Log::instance();
*
* @return Log
*/
public static function instance()
{
if (Log::$_instance === NULL)
{
// Create a new instance
Log::$_instance = new Log;
// Write the logs at shutdown
register_shutdown_function(array(Log::$_instance, 'write'));
}
return Log::$_instance;
}
/**
* @var array list of added messages
*/
protected $_messages = array();
/**
* @var array list of log writers
*/
protected $_writers = array();
/**
* Attaches a log writer, and optionally limits the levels of messages that
* will be written by the writer.
*
* $log->attach($writer);
*
* @param Log_Writer $writer instance
* @param mixed $levels array of messages levels to write OR max level to write
* @param integer $min_level min level to write IF $levels is not an array
* @return Log
*/
public function attach(Log_Writer $writer, $levels = array(), $min_level = 0)
{
if ( ! is_array($levels))
{
$levels = range($min_level, $levels);
}
$this->_writers["{$writer}"] = array
(
'object' => $writer,
'levels' => $levels
);
return $this;
}
/**
* Detaches a log writer. The same writer object must be used.
*
* $log->detach($writer);
*
* @param Log_Writer $writer instance
* @return Log
*/
public function detach(Log_Writer $writer)
{
// Remove the writer
unset($this->_writers["{$writer}"]);
return $this;
}
/**
* Adds a message to the log. Replacement values must be passed in to be
* replaced using [strtr](http://php.net/strtr).
*
* $log->add(Log::ERROR, 'Could not locate user: :user', array(
* ':user' => $username,
* ));
*
* @param string $level level of message
* @param string $message message body
* @param array $values values to replace in the message
* @param array $additional additional custom parameters to supply to the log writer
* @return Log
*/
public function add($level, $message, array $values = NULL, array $additional = NULL)
{
if ($values)
{
// Insert the values into the message
$message = strtr($message, $values);
}
// Grab a copy of the trace
if (isset($additional['exception']))
{
$trace = $additional['exception']->getTrace();
}
else
{
// Older php version don't have 'DEBUG_BACKTRACE_IGNORE_ARGS', so manually remove the args from the backtrace
if ( ! defined('DEBUG_BACKTRACE_IGNORE_ARGS'))
{
$trace = array_map(function ($item) {
unset($item['args']);
return $item;
}, array_slice(debug_backtrace(FALSE), 1));
}
else
{
$trace = array_slice(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), 1);
}
}
if ($additional == NULL)
{
$additional = array();
}
// Create a new message
$this->_messages[] = array
(
'time' => time(),
'level' => $level,
'body' => $message,
'trace' => $trace,
'file' => isset($trace[0]['file']) ? $trace[0]['file'] : NULL,
'line' => isset($trace[0]['line']) ? $trace[0]['line'] : NULL,
'class' => isset($trace[0]['class']) ? $trace[0]['class'] : NULL,
'function' => isset($trace[0]['function']) ? $trace[0]['function'] : NULL,
'additional' => $additional,
);
if (Log::$write_on_add)
{
// Write logs as they are added
$this->write();
}
return $this;
}
/**
* Write and clear all of the messages.
*
* $log->write();
*
* @return void
*/
public function write()
{
if (empty($this->_messages))
{
// There is nothing to write, move along
return;
}
// Import all messages locally
$messages = $this->_messages;
// Reset the messages array
$this->_messages = array();
foreach ($this->_writers as $writer)
{
if (empty($writer['levels']))
{
// Write all of the messages
$writer['object']->write($messages);
}
else
{
// Filtered messages
$filtered = array();
foreach ($messages as $message)
{
if (in_array($message['level'], $writer['levels']))
{
// Writer accepts this kind of message
$filtered[] = $message;
}
}
// Write the filtered messages
$writer['object']->write($filtered);
}
}
}
} // End Kohana_Log

View File

@@ -0,0 +1,94 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* File log writer. Writes out messages and stores them in a YYYY/MM directory.
*
* @package Kohana
* @category Logging
* @author Kohana Team
* @copyright (c) 2008-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
class Kohana_Log_File extends Log_Writer {
/**
* @var string Directory to place log files in
*/
protected $_directory;
/**
* Creates a new file logger. Checks that the directory exists and
* is writable.
*
* $writer = new Log_File($directory);
*
* @param string $directory log directory
* @return void
*/
public function __construct($directory)
{
if ( ! is_dir($directory) OR ! is_writable($directory))
{
throw new Kohana_Exception('Directory :dir must be writable',
array(':dir' => Debug::path($directory)));
}
// Determine the directory path
$this->_directory = realpath($directory).DIRECTORY_SEPARATOR;
}
/**
* Writes each of the messages into the log file. The log file will be
* appended to the `YYYY/MM/DD.log.php` file, where YYYY is the current
* year, MM is the current month, and DD is the current day.
*
* $writer->write($messages);
*
* @param array $messages
* @return void
*/
public function write(array $messages)
{
// Set the yearly directory name
$directory = $this->_directory.date('Y');
if ( ! is_dir($directory))
{
// Create the yearly directory
mkdir($directory, 02777);
// Set permissions (must be manually set to fix umask issues)
chmod($directory, 02777);
}
// Add the month to the directory
$directory .= DIRECTORY_SEPARATOR.date('m');
if ( ! is_dir($directory))
{
// Create the monthly directory
mkdir($directory, 02777);
// Set permissions (must be manually set to fix umask issues)
chmod($directory, 02777);
}
// Set the name of the log file
$filename = $directory.DIRECTORY_SEPARATOR.date('d').EXT;
if ( ! file_exists($filename))
{
// Create the log file
file_put_contents($filename, Kohana::FILE_SECURITY.' ?>'.PHP_EOL);
// Allow anyone to write to log files
chmod($filename, 0666);
}
foreach ($messages as $message)
{
// Write each message into the log file
file_put_contents($filename, PHP_EOL.$this->format_message($message), FILE_APPEND);
}
}
} // End Kohana_Log_File

View File

@@ -0,0 +1,29 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* STDERR log writer. Writes out messages to STDERR.
*
* @package Kohana
* @category Logging
* @author Kohana Team
* @copyright (c) 2008-2012 Kohana Team
* @license http://kohanaphp.com/license
*/
class Kohana_Log_StdErr extends Log_Writer {
/**
* Writes each of the messages to STDERR.
*
* $writer->write($messages);
*
* @param array $messages
* @return void
*/
public function write(array $messages)
{
foreach ($messages as $message)
{
// Writes out each message
fwrite(STDERR, $this->format_message($message).PHP_EOL);
}
}
} // End Kohana_Log_StdErr

View File

@@ -0,0 +1,30 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* STDOUT log writer. Writes out messages to STDOUT.
*
* @package Kohana
* @category Logging
* @author Kohana Team
* @copyright (c) 2008-2012 Kohana Team
* @license http://kohanaphp.com/license
*/
class Kohana_Log_StdOut extends Log_Writer {
/**
* Writes each of the messages to STDOUT.
*
* $writer->write($messages);
*
* @param array $messages
* @return void
*/
public function write(array $messages)
{
foreach ($messages as $message)
{
// Writes out each message
fwrite(STDOUT, $this->format_message($message).PHP_EOL);
}
}
} // End Kohana_Log_StdOut

View File

@@ -0,0 +1,65 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* Syslog log writer.
*
* @package Kohana
* @category Logging
* @author Jeremy Bush
* @copyright (c) 2012 Kohana Team
* @license http://kohanaframework.org/license
*/
class Kohana_Log_Syslog extends Log_Writer {
/**
* @var string The syslog identifier
*/
protected $_ident;
/**
* Creates a new syslog logger.
*
* @link http://www.php.net/manual/function.openlog
*
* @param string $ident syslog identifier
* @param int $facility facility to log to
* @return void
*/
public function __construct($ident = 'KohanaPHP', $facility = LOG_USER)
{
$this->_ident = $ident;
// Open the connection to syslog
openlog($this->_ident, LOG_CONS, $facility);
}
/**
* Writes each of the messages into the syslog.
*
* @param array $messages
* @return void
*/
public function write(array $messages)
{
foreach ($messages as $message)
{
syslog($message['level'], $message['body']);
if (isset($message['additional']['exception']))
{
syslog(Log_Writer::$strace_level, $message['additional']['exception']->getTraceAsString());
}
}
}
/**
* Closes the syslog connection
*
* @return void
*/
public function __destruct()
{
// Close connection to syslog
closelog();
}
} // End Kohana_Log_Syslog

View File

@@ -0,0 +1,95 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* Log writer abstract class. All [Log] writers must extend this class.
*
* @package Kohana
* @category Logging
* @author Kohana Team
* @copyright (c) 2008-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
abstract class Kohana_Log_Writer {
/**
* @var string timestamp format for log entries.
*
* Defaults to Date::$timestamp_format
*/
public static $timestamp;
/**
* @var string timezone for log entries
*
* Defaults to Date::$timezone, which defaults to date_default_timezone_get()
*/
public static $timezone;
/**
* Numeric log level to string lookup table.
* @var array
*/
protected $_log_levels = array(
LOG_EMERG => 'EMERGENCY',
LOG_ALERT => 'ALERT',
LOG_CRIT => 'CRITICAL',
LOG_ERR => 'ERROR',
LOG_WARNING => 'WARNING',
LOG_NOTICE => 'NOTICE',
LOG_INFO => 'INFO',
LOG_DEBUG => 'DEBUG',
);
/**
* @var int Level to use for stack traces
*/
public static $strace_level = LOG_DEBUG;
/**
* Write an array of messages.
*
* $writer->write($messages);
*
* @param array $messages
* @return void
*/
abstract public function write(array $messages);
/**
* Allows the writer to have a unique key when stored.
*
* echo $writer;
*
* @return string
*/
final public function __toString()
{
return spl_object_hash($this);
}
/**
* Formats a log entry.
*
* @param array $message
* @param string $format
* @return string
*/
public function format_message(array $message, $format = "time --- level: body in file:line")
{
$message['time'] = Date::formatted_time('@'.$message['time'], Log_Writer::$timestamp, Log_Writer::$timezone, TRUE);
$message['level'] = $this->_log_levels[$message['level']];
$string = strtr($format, $message);
if (isset($message['additional']['exception']))
{
// Re-use as much as possible, just resetting the body to the trace
$message['body'] = $message['additional']['exception']->getTraceAsString();
$message['level'] = $this->_log_levels[Log_Writer::$strace_level];
$string .= PHP_EOL.strtr($format, $message);
}
return $string;
}
} // End Kohana_Log_Writer

View File

@@ -0,0 +1,29 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* Model base class. All models should extend this class.
*
* @package Kohana
* @category Models
* @author Kohana Team
* @copyright (c) 2008-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
abstract class Kohana_Model {
/**
* Create a new model instance.
*
* $model = Model::factory($name);
*
* @param string $name model name
* @return Model
*/
public static function factory($name)
{
// Add the model prefix
$class = 'Model_'.$name;
return new $class;
}
} // End Model

View File

@@ -0,0 +1,234 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* Number helper class. Provides additional formatting methods that for working
* with numbers.
*
* @package Kohana
* @category Helpers
* @author Kohana Team
* @copyright (c) 2009-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
class Kohana_Num {
const ROUND_HALF_UP = 1;
const ROUND_HALF_DOWN = 2;
const ROUND_HALF_EVEN = 3;
const ROUND_HALF_ODD = 4;
/**
* @var array Valid byte units => power of 2 that defines the unit's size
*/
public static $byte_units = array
(
'B' => 0,
'K' => 10,
'Ki' => 10,
'KB' => 10,
'KiB' => 10,
'M' => 20,
'Mi' => 20,
'MB' => 20,
'MiB' => 20,
'G' => 30,
'Gi' => 30,
'GB' => 30,
'GiB' => 30,
'T' => 40,
'Ti' => 40,
'TB' => 40,
'TiB' => 40,
'P' => 50,
'Pi' => 50,
'PB' => 50,
'PiB' => 50,
'E' => 60,
'Ei' => 60,
'EB' => 60,
'EiB' => 60,
'Z' => 70,
'Zi' => 70,
'ZB' => 70,
'ZiB' => 70,
'Y' => 80,
'Yi' => 80,
'YB' => 80,
'YiB' => 80,
);
/**
* Returns the English ordinal suffix (th, st, nd, etc) of a number.
*
* echo 2, Num::ordinal(2); // "2nd"
* echo 10, Num::ordinal(10); // "10th"
* echo 33, Num::ordinal(33); // "33rd"
*
* @param integer $number
* @return string
*/
public static function ordinal($number)
{
if ($number % 100 > 10 AND $number % 100 < 14)
{
return 'th';
}
switch ($number % 10)
{
case 1:
return 'st';
case 2:
return 'nd';
case 3:
return 'rd';
default:
return 'th';
}
}
/**
* Locale-aware number and monetary formatting.
*
* // In English, "1,200.05"
* // In Spanish, "1200,05"
* // In Portuguese, "1 200,05"
* echo Num::format(1200.05, 2);
*
* // In English, "1,200.05"
* // In Spanish, "1.200,05"
* // In Portuguese, "1.200.05"
* echo Num::format(1200.05, 2, TRUE);
*
* @param float $number number to format
* @param integer $places decimal places
* @param boolean $monetary monetary formatting?
* @return string
* @since 3.0.2
*/
public static function format($number, $places, $monetary = FALSE)
{
$info = localeconv();
if ($monetary)
{
$decimal = $info['mon_decimal_point'];
$thousands = $info['mon_thousands_sep'];
}
else
{
$decimal = $info['decimal_point'];
$thousands = $info['thousands_sep'];
}
return number_format($number, $places, $decimal, $thousands);
}
/**
* Round a number to a specified precision, using a specified tie breaking technique
*
* @param float $value Number to round
* @param integer $precision Desired precision
* @param integer $mode Tie breaking mode, accepts the PHP_ROUND_HALF_* constants
* @param boolean $native Set to false to force use of the userland implementation
* @return float Rounded number
*/
public static function round($value, $precision = 0, $mode = self::ROUND_HALF_UP, $native = TRUE)
{
if (version_compare(PHP_VERSION, '5.3', '>=') AND $native)
{
return round($value, $precision, $mode);
}
if ($mode === self::ROUND_HALF_UP)
{
return round($value, $precision);
}
else
{
$factor = ($precision === 0) ? 1 : pow(10, $precision);
switch ($mode)
{
case self::ROUND_HALF_DOWN:
case self::ROUND_HALF_EVEN:
case self::ROUND_HALF_ODD:
// Check if we have a rounding tie, otherwise we can just call round()
if (($value * $factor) - floor($value * $factor) === 0.5)
{
if ($mode === self::ROUND_HALF_DOWN)
{
// Round down operation, so we round down unless the value
// is -ve because up is down and down is up down there. ;)
$up = ($value < 0);
}
else
{
// Round up if the integer is odd and the round mode is set to even
// or the integer is even and the round mode is set to odd.
// Any other instance round down.
$up = ( ! ( ! (floor($value * $factor) & 1)) === ($mode === self::ROUND_HALF_EVEN));
}
if ($up)
{
$value = ceil($value * $factor);
}
else
{
$value = floor($value * $factor);
}
return $value / $factor;
}
else
{
return round($value, $precision);
}
break;
}
}
}
/**
* Converts a file size number to a byte value. File sizes are defined in
* the format: SB, where S is the size (1, 8.5, 300, etc.) and B is the
* byte unit (K, MiB, GB, etc.). All valid byte units are defined in
* Num::$byte_units
*
* echo Num::bytes('200K'); // 204800
* echo Num::bytes('5MiB'); // 5242880
* echo Num::bytes('1000'); // 1000
* echo Num::bytes('2.5GB'); // 2684354560
*
* @param string $bytes file size in SB format
* @return float
*/
public static function bytes($size)
{
// Prepare the size
$size = trim( (string) $size);
// Construct an OR list of byte units for the regex
$accepted = implode('|', array_keys(Num::$byte_units));
// Construct the regex pattern for verifying the size format
$pattern = '/^([0-9]+(?:\.[0-9]+)?)('.$accepted.')?$/Di';
// Verify the size format and store the matching parts
if ( ! preg_match($pattern, $size, $matches))
throw new Kohana_Exception('The byte unit size, ":size", is improperly formatted.', array(
':size' => $size,
));
// Find the float value of the size
$size = (float) $matches[1];
// Find the actual unit, assume B if no unit specified
$unit = Arr::get($matches, 2, 'B');
// Convert the size into bytes
$bytes = $size * pow(2, Num::$byte_units[$unit]);
return $bytes;
}
} // End num

View File

@@ -0,0 +1,385 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* Provides simple benchmarking and profiling. To display the statistics that
* have been collected, load the `profiler/stats` [View]:
*
* echo View::factory('profiler/stats');
*
* @package Kohana
* @category Helpers
* @author Kohana Team
* @copyright (c) 2009-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
class Kohana_Profiler {
/**
* @var integer maximium number of application stats to keep
*/
public static $rollover = 1000;
/**
* @var array collected benchmarks
*/
protected static $_marks = array();
/**
* Starts a new benchmark and returns a unique token. The returned token
* _must_ be used when stopping the benchmark.
*
* $token = Profiler::start('test', 'profiler');
*
* @param string $group group name
* @param string $name benchmark name
* @return string
*/
public static function start($group, $name)
{
static $counter = 0;
// Create a unique token based on the counter
$token = 'kp/'.base_convert($counter++, 10, 32);
Profiler::$_marks[$token] = array
(
'group' => strtolower($group),
'name' => (string) $name,
// Start the benchmark
'start_time' => microtime(TRUE),
'start_memory' => memory_get_usage(),
// Set the stop keys without values
'stop_time' => FALSE,
'stop_memory' => FALSE,
);
return $token;
}
/**
* Stops a benchmark.
*
* Profiler::stop($token);
*
* @param string $token
* @return void
*/
public static function stop($token)
{
// Stop the benchmark
Profiler::$_marks[$token]['stop_time'] = microtime(TRUE);
Profiler::$_marks[$token]['stop_memory'] = memory_get_usage();
}
/**
* Deletes a benchmark. If an error occurs during the benchmark, it is
* recommended to delete the benchmark to prevent statistics from being
* adversely affected.
*
* Profiler::delete($token);
*
* @param string $token
* @return void
*/
public static function delete($token)
{
// Remove the benchmark
unset(Profiler::$_marks[$token]);
}
/**
* Returns all the benchmark tokens by group and name as an array.
*
* $groups = Profiler::groups();
*
* @return array
*/
public static function groups()
{
$groups = array();
foreach (Profiler::$_marks as $token => $mark)
{
// Sort the tokens by the group and name
$groups[$mark['group']][$mark['name']][] = $token;
}
return $groups;
}
/**
* Gets the min, max, average and total of a set of tokens as an array.
*
* $stats = Profiler::stats($tokens);
*
* @param array $tokens profiler tokens
* @return array min, max, average, total
* @uses Profiler::total
*/
public static function stats(array $tokens)
{
// Min and max are unknown by default
$min = $max = array(
'time' => NULL,
'memory' => NULL);
// Total values are always integers
$total = array(
'time' => 0,
'memory' => 0);
foreach ($tokens as $token)
{
// Get the total time and memory for this benchmark
list($time, $memory) = Profiler::total($token);
if ($max['time'] === NULL OR $time > $max['time'])
{
// Set the maximum time
$max['time'] = $time;
}
if ($min['time'] === NULL OR $time < $min['time'])
{
// Set the minimum time
$min['time'] = $time;
}
// Increase the total time
$total['time'] += $time;
if ($max['memory'] === NULL OR $memory > $max['memory'])
{
// Set the maximum memory
$max['memory'] = $memory;
}
if ($min['memory'] === NULL OR $memory < $min['memory'])
{
// Set the minimum memory
$min['memory'] = $memory;
}
// Increase the total memory
$total['memory'] += $memory;
}
// Determine the number of tokens
$count = count($tokens);
// Determine the averages
$average = array(
'time' => $total['time'] / $count,
'memory' => $total['memory'] / $count);
return array(
'min' => $min,
'max' => $max,
'total' => $total,
'average' => $average);
}
/**
* Gets the min, max, average and total of profiler groups as an array.
*
* $stats = Profiler::group_stats('test');
*
* @param mixed $groups single group name string, or array with group names; all groups by default
* @return array min, max, average, total
* @uses Profiler::groups
* @uses Profiler::stats
*/
public static function group_stats($groups = NULL)
{
// Which groups do we need to calculate stats for?
$groups = ($groups === NULL)
? Profiler::groups()
: array_intersect_key(Profiler::groups(), array_flip( (array) $groups));
// All statistics
$stats = array();
foreach ($groups as $group => $names)
{
foreach ($names as $name => $tokens)
{
// Store the stats for each subgroup.
// We only need the values for "total".
$_stats = Profiler::stats($tokens);
$stats[$group][$name] = $_stats['total'];
}
}
// Group stats
$groups = array();
foreach ($stats as $group => $names)
{
// Min and max are unknown by default
$groups[$group]['min'] = $groups[$group]['max'] = array(
'time' => NULL,
'memory' => NULL);
// Total values are always integers
$groups[$group]['total'] = array(
'time' => 0,
'memory' => 0);
foreach ($names as $total)
{
if ( ! isset($groups[$group]['min']['time']) OR $groups[$group]['min']['time'] > $total['time'])
{
// Set the minimum time
$groups[$group]['min']['time'] = $total['time'];
}
if ( ! isset($groups[$group]['min']['memory']) OR $groups[$group]['min']['memory'] > $total['memory'])
{
// Set the minimum memory
$groups[$group]['min']['memory'] = $total['memory'];
}
if ( ! isset($groups[$group]['max']['time']) OR $groups[$group]['max']['time'] < $total['time'])
{
// Set the maximum time
$groups[$group]['max']['time'] = $total['time'];
}
if ( ! isset($groups[$group]['max']['memory']) OR $groups[$group]['max']['memory'] < $total['memory'])
{
// Set the maximum memory
$groups[$group]['max']['memory'] = $total['memory'];
}
// Increase the total time and memory
$groups[$group]['total']['time'] += $total['time'];
$groups[$group]['total']['memory'] += $total['memory'];
}
// Determine the number of names (subgroups)
$count = count($names);
// Determine the averages
$groups[$group]['average']['time'] = $groups[$group]['total']['time'] / $count;
$groups[$group]['average']['memory'] = $groups[$group]['total']['memory'] / $count;
}
return $groups;
}
/**
* Gets the total execution time and memory usage of a benchmark as a list.
*
* list($time, $memory) = Profiler::total($token);
*
* @param string $token
* @return array execution time, memory
*/
public static function total($token)
{
// Import the benchmark data
$mark = Profiler::$_marks[$token];
if ($mark['stop_time'] === FALSE)
{
// The benchmark has not been stopped yet
$mark['stop_time'] = microtime(TRUE);
$mark['stop_memory'] = memory_get_usage();
}
return array
(
// Total time in seconds
$mark['stop_time'] - $mark['start_time'],
// Amount of memory in bytes
$mark['stop_memory'] - $mark['start_memory'],
);
}
/**
* Gets the total application run time and memory usage. Caches the result
* so that it can be compared between requests.
*
* list($time, $memory) = Profiler::application();
*
* @return array execution time, memory
* @uses Kohana::cache
*/
public static function application()
{
// Load the stats from cache, which is valid for 1 day
$stats = Kohana::cache('profiler_application_stats', NULL, 3600 * 24);
if ( ! is_array($stats) OR $stats['count'] > Profiler::$rollover)
{
// Initialize the stats array
$stats = array(
'min' => array(
'time' => NULL,
'memory' => NULL),
'max' => array(
'time' => NULL,
'memory' => NULL),
'total' => array(
'time' => NULL,
'memory' => NULL),
'count' => 0);
}
// Get the application run time
$time = microtime(TRUE) - KOHANA_START_TIME;
// Get the total memory usage
$memory = memory_get_usage() - KOHANA_START_MEMORY;
// Calculate max time
if ($stats['max']['time'] === NULL OR $time > $stats['max']['time'])
{
$stats['max']['time'] = $time;
}
// Calculate min time
if ($stats['min']['time'] === NULL OR $time < $stats['min']['time'])
{
$stats['min']['time'] = $time;
}
// Add to total time
$stats['total']['time'] += $time;
// Calculate max memory
if ($stats['max']['memory'] === NULL OR $memory > $stats['max']['memory'])
{
$stats['max']['memory'] = $memory;
}
// Calculate min memory
if ($stats['min']['memory'] === NULL OR $memory < $stats['min']['memory'])
{
$stats['min']['memory'] = $memory;
}
// Add to total memory
$stats['total']['memory'] += $memory;
// Another mark has been added to the stats
$stats['count']++;
// Determine the averages
$stats['average'] = array(
'time' => $stats['total']['time'] / $stats['count'],
'memory' => $stats['total']['memory'] / $stats['count']);
// Cache the new stats
Kohana::cache('profiler_application_stats', $stats);
// Set the current application execution time and memory
// Do NOT cache these, they are specific to the current request only
$stats['current']['time'] = $time;
$stats['current']['memory'] = $memory;
// Return the total application run time and memory usage
return $stats;
}
} // End Profiler

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,424 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* Request Client. Processes a [Request] and handles [HTTP_Caching] if
* available. Will usually return a [Response] object as a result of the
* request unless an unexpected error occurs.
*
* @package Kohana
* @category Base
* @author Kohana Team
* @copyright (c) 2008-2012 Kohana Team
* @license http://kohanaframework.org/license
* @since 3.1.0
*/
abstract class Kohana_Request_Client {
/**
* @var Cache Caching library for request caching
*/
protected $_cache;
/**
* @var bool Should redirects be followed?
*/
protected $_follow = FALSE;
/**
* @var array Headers to preserve when following a redirect
*/
protected $_follow_headers = array('Authorization');
/**
* @var bool Follow 302 redirect with original request method?
*/
protected $_strict_redirect = TRUE;
/**
* @var array Callbacks to use when response contains given headers
*/
protected $_header_callbacks = array(
'Location' => 'Request_Client::on_header_location'
);
/**
* @var int Maximum number of requests that header callbacks can trigger before the request is aborted
*/
protected $_max_callback_depth = 5;
/**
* @var int Tracks the callback depth of the currently executing request
*/
protected $_callback_depth = 1;
/**
* @var array Arbitrary parameters that are shared with header callbacks through their Request_Client object
*/
protected $_callback_params = array();
/**
* Creates a new `Request_Client` object,
* allows for dependency injection.
*
* @param array $params Params
*/
public function __construct(array $params = array())
{
foreach ($params as $key => $value)
{
if (method_exists($this, $key))
{
$this->$key($value);
}
}
}
/**
* Processes the request, executing the controller action that handles this
* request, determined by the [Route].
*
* 1. Before the controller action is called, the [Controller::before] method
* will be called.
* 2. Next the controller action will be called.
* 3. After the controller action is called, the [Controller::after] method
* will be called.
*
* By default, the output from the controller is captured and returned, and
* no headers are sent.
*
* $request->execute();
*
* @param Request $request
* @param Response $response
* @return Response
* @throws Kohana_Exception
* @uses [Kohana::$profiling]
* @uses [Profiler]
*/
public function execute(Request $request)
{
// Prevent too much recursion of header callback requests
if ($this->callback_depth() > $this->max_callback_depth())
throw new Request_Client_Recursion_Exception(
"Could not execute request to :uri - too many recursions after :depth requests",
array(
':uri' => $request->uri(),
':depth' => $this->callback_depth() - 1,
));
// Execute the request
$orig_response = $response = Response::factory();
if (($cache = $this->cache()) instanceof HTTP_Cache)
return $cache->execute($this, $request, $response);
$response = $this->execute_request($request, $response);
// Execute response callbacks
foreach ($this->header_callbacks() as $header => $callback)
{
if ($response->headers($header))
{
$cb_result = call_user_func($callback, $request, $response, $this);
if ($cb_result instanceof Request)
{
// If the callback returns a request, automatically assign client params
$this->assign_client_properties($cb_result->client());
$cb_result->client()->callback_depth($this->callback_depth() + 1);
// Execute the request
$response = $cb_result->execute();
}
elseif ($cb_result instanceof Response)
{
// Assign the returned response
$response = $cb_result;
}
// If the callback has created a new response, do not process any further
if ($response !== $orig_response)
break;
}
}
return $response;
}
/**
* Processes the request passed to it and returns the response from
* the URI resource identified.
*
* This method must be implemented by all clients.
*
* @param Request $request request to execute by client
* @param Response $response
* @return Response
* @since 3.2.0
*/
abstract public function execute_request(Request $request, Response $response);
/**
* Getter and setter for the internal caching engine,
* used to cache responses if available and valid.
*
* @param HTTP_Cache $cache engine to use for caching
* @return HTTP_Cache
* @return Request_Client
*/
public function cache(HTTP_Cache $cache = NULL)
{
if ($cache === NULL)
return $this->_cache;
$this->_cache = $cache;
return $this;
}
/**
* Getter and setter for the follow redirects
* setting.
*
* @param bool $follow Boolean indicating if redirects should be followed
* @return bool
* @return Request_Client
*/
public function follow($follow = NULL)
{
if ($follow === NULL)
return $this->_follow;
$this->_follow = $follow;
return $this;
}
/**
* Getter and setter for the follow redirects
* headers array.
*
* @param array $follow_headers Array of headers to be re-used when following a Location header
* @return array
* @return Request_Client
*/
public function follow_headers($follow_headers = NULL)
{
if ($follow_headers === NULL)
return $this->_follow_headers;
$this->_follow_headers = $follow_headers;
return $this;
}
/**
* Getter and setter for the strict redirects setting
*
* [!!] HTTP/1.1 specifies that a 302 redirect should be followed using the
* original request method. However, the vast majority of clients and servers
* get this wrong, with 302 widely used for 'POST - 302 redirect - GET' patterns.
* By default, Kohana's client is fully compliant with the HTTP spec. Some
* non-compliant third party sites may require that strict_redirect is set
* FALSE to force the client to switch to GET following a 302 response.
*
* @param bool $strict_redirect Boolean indicating if 302 redirects should be followed with the original method
* @return Request_Client
*/
public function strict_redirect($strict_redirect = NULL)
{
if ($strict_redirect === NULL)
return $this->_strict_redirect;
$this->_strict_redirect = $strict_redirect;
return $this;
}
/**
* Getter and setter for the header callbacks array.
*
* Accepts an array with HTTP response headers as keys and a PHP callback
* function as values. These callbacks will be triggered if a response contains
* the given header and can either issue a subsequent request or manipulate
* the response as required.
*
* By default, the [Request_Client::on_header_location] callback is assigned
* to the Location header to support automatic redirect following.
*
* $client->header_callbacks(array(
* 'Location' => 'Request_Client::on_header_location',
* 'WWW-Authenticate' => function($request, $response, $client) {return $new_response;},
* );
*
* @param array $header_callbacks Array of callbacks to trigger on presence of given headers
* @return Request_Client
*/
public function header_callbacks($header_callbacks = NULL)
{
if ($header_callbacks === NULL)
return $this->_header_callbacks;
$this->_header_callbacks = $header_callbacks;
return $this;
}
/**
* Getter and setter for the maximum callback depth property.
*
* This protects the main execution from recursive callback execution (eg
* following infinite redirects, conflicts between callbacks causing loops
* etc). Requests will only be allowed to nest to the level set by this
* param before execution is aborted with a Request_Client_Recursion_Exception.
*
* @param int $depth Maximum number of callback requests to execute before aborting
* @return Request_Client|int
*/
public function max_callback_depth($depth = NULL)
{
if ($depth === NULL)
return $this->_max_callback_depth;
$this->_max_callback_depth = $depth;
return $this;
}
/**
* Getter/Setter for the callback depth property, which is used to track
* how many recursions have been executed within the current request execution.
*
* @param int $depth Current recursion depth
* @return Request_Client|int
*/
public function callback_depth($depth = NULL)
{
if ($depth === NULL)
return $this->_callback_depth;
$this->_callback_depth = $depth;
return $this;
}
/**
* Getter/Setter for the callback_params array, which allows additional
* application-specific parameters to be shared with callbacks.
*
* As with other Kohana setter/getters, usage is:
*
* // Set full array
* $client->callback_params(array('foo'=>'bar'));
*
* // Set single key
* $client->callback_params('foo','bar');
*
* // Get full array
* $params = $client->callback_params();
*
* // Get single key
* $foo = $client->callback_params('foo');
*
* @param string|array $param
* @param mixed $value
* @return Request_Client|mixed
*/
public function callback_params($param = NULL, $value = NULL)
{
// Getter for full array
if ($param === NULL)
return $this->_callback_params;
// Setter for full array
if (is_array($param))
{
$this->_callback_params = $param;
return $this;
}
// Getter for single value
elseif ($value === NULL)
{
return Arr::get($this->_callback_params, $param);
}
// Setter for single value
else
{
$this->_callback_params[$param] = $value;
return $this;
}
}
/**
* Assigns the properties of the current Request_Client to another
* Request_Client instance - used when setting up a subsequent request.
*
* @param Request_Client $client
*/
public function assign_client_properties(Request_Client $client)
{
$client->cache($this->cache());
$client->follow($this->follow());
$client->follow_headers($this->follow_headers());
$client->header_callbacks($this->header_callbacks());
$client->max_callback_depth($this->max_callback_depth());
$client->callback_params($this->callback_params());
}
/**
* The default handler for following redirects, triggered by the presence of
* a Location header in the response.
*
* The client's follow property must be set TRUE and the HTTP response status
* one of 201, 301, 302, 303 or 307 for the redirect to be followed.
*
* @param Request $request
* @param Response $response
* @param Request_Client $client
*/
public static function on_header_location(Request $request, Response $response, Request_Client $client)
{
// Do we need to follow a Location header ?
if ($client->follow() AND in_array($response->status(), array(201, 301, 302, 303, 307)))
{
// Figure out which method to use for the follow request
switch ($response->status())
{
default:
case 301:
case 307:
$follow_method = $request->method();
break;
case 201:
case 303:
$follow_method = Request::GET;
break;
case 302:
// Cater for sites with broken HTTP redirect implementations
if ($client->strict_redirect())
{
$follow_method = $request->method();
}
else
{
$follow_method = Request::GET;
}
break;
}
// Prepare the additional request
$follow_request = Request::factory($response->headers('Location'))
->method($follow_method)
->headers(Arr::extract($request->headers(), $client->follow_headers()));
if ($follow_method !== Request::GET)
{
$follow_request->body($request->body());
}
return $follow_request;
}
return NULL;
}
}

View File

@@ -0,0 +1,135 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* [Request_Client_External] Curl driver performs external requests using the
* php-curl extention. This is the default driver for all external requests.
*
* @package Kohana
* @category Base
* @author Kohana Team
* @copyright (c) 2008-2012 Kohana Team
* @license http://kohanaframework.org/license
* @uses [PHP cURL](http://php.net/manual/en/book.curl.php)
*/
class Kohana_Request_Client_Curl extends Request_Client_External {
/**
* Sends the HTTP message [Request] to a remote server and processes
* the response.
*
* @param Request $request request to send
* @param Response $request response to send
* @return Response
*/
public function _send_message(Request $request, Response $response)
{
// Response headers
$response_headers = array();
$options = array();
// Set the request method
$options = $this->_set_curl_request_method($request, $options);
// Set the request body. This is perfectly legal in CURL even
// if using a request other than POST. PUT does support this method
// and DOES NOT require writing data to disk before putting it, if
// reading the PHP docs you may have got that impression. SdF
$options[CURLOPT_POSTFIELDS] = $request->body();
// Process headers
if ($headers = $request->headers())
{
$http_headers = array();
foreach ($headers as $key => $value)
{
$http_headers[] = $key.': '.$value;
}
$options[CURLOPT_HTTPHEADER] = $http_headers;
}
// Process cookies
if ($cookies = $request->cookie())
{
$options[CURLOPT_COOKIE] = http_build_query($cookies, NULL, '; ');
}
// Get any exisiting response headers
$response_header = $response->headers();
// Implement the standard parsing parameters
$options[CURLOPT_HEADERFUNCTION] = array($response_header, 'parse_header_string');
$this->_options[CURLOPT_RETURNTRANSFER] = TRUE;
$this->_options[CURLOPT_HEADER] = FALSE;
// Apply any additional options set to
$options = Arr::merge($options, $this->_options);
$uri = $request->uri();
if ($query = $request->query())
{
$uri .= '?'.http_build_query($query, NULL, '&');
}
// Open a new remote connection
$curl = curl_init($uri);
// Set connection options
if ( ! curl_setopt_array($curl, $options))
{
throw new Request_Exception('Failed to set CURL options, check CURL documentation: :url',
array(':url' => 'http://php.net/curl_setopt_array'));
}
// Get the response body
$body = curl_exec($curl);
// Get the response information
$code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if ($body === FALSE)
{
$error = curl_error($curl);
}
// Close the connection
curl_close($curl);
if (isset($error))
{
throw new Request_Exception('Error fetching remote :url [ status :code ] :error',
array(':url' => $request->url(), ':code' => $code, ':error' => $error));
}
$response->status($code)
->body($body);
return $response;
}
/**
* Sets the appropriate curl request options. Uses the responding options
* for POST and PUT, uses CURLOPT_CUSTOMREQUEST otherwise
* @param Request $request
* @param array $options
* @return array
*/
public function _set_curl_request_method(Request $request, array $options)
{
switch ($request->method()) {
case Request::POST:
$options[CURLOPT_POST] = TRUE;
break;
case Request::PUT:
$options[CURLOPT_PUT] = TRUE;
break;
default:
$options[CURLOPT_CUSTOMREQUEST] = $request->method();
break;
}
return $options;
}
} // End Kohana_Request_Client_Curl

View File

@@ -0,0 +1,207 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* [Request_Client_External] provides a wrapper for all external request
* processing. This class should be extended by all drivers handling external
* requests.
*
* Supported out of the box:
* - Curl (default)
* - PECL HTTP
* - Streams
*
* To select a specific external driver to use as the default driver, set the
* following property within the Application bootstrap. Alternatively, the
* client can be injected into the request object.
*
* @example
*
* // In application bootstrap
* Request_Client_External::$client = 'Request_Client_Stream';
*
* // Add client to request
* $request = Request::factory('http://some.host.tld/foo/bar')
* ->client(Request_Client_External::factory('Request_Client_HTTP));
*
* @package Kohana
* @category Base
* @author Kohana Team
* @copyright (c) 2008-2012 Kohana Team
* @license http://kohanaframework.org/license
* @uses [PECL HTTP](http://php.net/manual/en/book.http.php)
*/
abstract class Kohana_Request_Client_External extends Request_Client {
/**
* Use:
* - Request_Client_Curl (default)
* - Request_Client_HTTP
* - Request_Client_Stream
*
* @var string defines the external client to use by default
*/
public static $client = 'Request_Client_Curl';
/**
* Factory method to create a new Request_Client_External object based on
* the client name passed, or defaulting to Request_Client_External::$client
* by default.
*
* Request_Client_External::$client can be set in the application bootstrap.
*
* @param array $params parameters to pass to the client
* @param string $client external client to use
* @return Request_Client_External
* @throws Request_Exception
*/
public static function factory(array $params = array(), $client = NULL)
{
if ($client === NULL)
{
$client = Request_Client_External::$client;
}
$client = new $client($params);
if ( ! $client instanceof Request_Client_External)
{
throw new Request_Exception('Selected client is not a Request_Client_External object.');
}
return $client;
}
/**
* @var array curl options
* @link http://www.php.net/manual/function.curl-setopt
* @link http://www.php.net/manual/http.request.options
*/
protected $_options = array();
/**
* Processes the request, executing the controller action that handles this
* request, determined by the [Route].
*
* 1. Before the controller action is called, the [Controller::before] method
* will be called.
* 2. Next the controller action will be called.
* 3. After the controller action is called, the [Controller::after] method
* will be called.
*
* By default, the output from the controller is captured and returned, and
* no headers are sent.
*
* $request->execute();
*
* @param Request $request A request object
* @param Response $response A response object
* @return Response
* @throws Kohana_Exception
* @uses [Kohana::$profiling]
* @uses [Profiler]
*/
public function execute_request(Request $request, Response $response)
{
if (Kohana::$profiling)
{
// Set the benchmark name
$benchmark = '"'.$request->uri().'"';
if ($request !== Request::$initial AND Request::$current)
{
// Add the parent request uri
$benchmark .= ' « "'.Request::$current->uri().'"';
}
// Start benchmarking
$benchmark = Profiler::start('Requests', $benchmark);
}
// Store the current active request and replace current with new request
$previous = Request::$current;
Request::$current = $request;
// Resolve the POST fields
if ($post = $request->post())
{
$request->body(http_build_query($post, NULL, '&'))
->headers('content-type', 'application/x-www-form-urlencoded');
}
// If Kohana expose, set the user-agent
if (Kohana::$expose)
{
$request->headers('user-agent', Kohana::version());
}
try
{
$response = $this->_send_message($request, $response);
}
catch (Exception $e)
{
// Restore the previous request
Request::$current = $previous;
if (isset($benchmark))
{
// Delete the benchmark, it is invalid
Profiler::delete($benchmark);
}
// Re-throw the exception
throw $e;
}
// Restore the previous request
Request::$current = $previous;
if (isset($benchmark))
{
// Stop the benchmark
Profiler::stop($benchmark);
}
// Return the response
return $response;
}
/**
* Set and get options for this request.
*
* @param mixed $key Option name, or array of options
* @param mixed $value Option value
* @return mixed
* @return Request_Client_External
*/
public function options($key = NULL, $value = NULL)
{
if ($key === NULL)
return $this->_options;
if (is_array($key))
{
$this->_options = $key;
}
elseif ($value === NULL)
{
return Arr::get($this->_options, $key);
}
else
{
$this->_options[$key] = $value;
}
return $this;
}
/**
* Sends the HTTP message [Request] to a remote server and processes
* the response.
*
* @param Request $request Request to send
* @param Response $response Response to send
* @return Response
*/
abstract protected function _send_message(Request $request, Response $response);
} // End Kohana_Request_Client_External

View File

@@ -0,0 +1,121 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* [Request_Client_External] HTTP driver performs external requests using the
* php-http extention. To use this driver, ensure the following is completed
* before executing an external request- ideally in the application bootstrap.
*
* @example
*
* // In application bootstrap
* Request_Client_External::$client = 'Request_Client_HTTP';
*
* @package Kohana
* @category Base
* @author Kohana Team
* @copyright (c) 2008-2012 Kohana Team
* @license http://kohanaframework.org/license
* @uses [PECL HTTP](http://php.net/manual/en/book.http.php)
*/
class Kohana_Request_Client_HTTP extends Request_Client_External {
/**
* Creates a new `Request_Client` object,
* allows for dependency injection.
*
* @param array $params Params
* @throws Request_Exception
*/
public function __construct(array $params = array())
{
// Check that PECL HTTP supports requests
if ( ! http_support(HTTP_SUPPORT_REQUESTS))
{
throw new Request_Exception('Need HTTP request support!');
}
// Carry on
parent::__construct($params);
}
/**
* @var array curl options
* @link http://www.php.net/manual/function.curl-setopt
*/
protected $_options = array();
/**
* Sends the HTTP message [Request] to a remote server and processes
* the response.
*
* @param Request $request request to send
* @param Response $request response to send
* @return Response
*/
public function _send_message(Request $request, Response $response)
{
$http_method_mapping = array(
HTTP_Request::GET => HTTPRequest::METH_GET,
HTTP_Request::HEAD => HTTPRequest::METH_HEAD,
HTTP_Request::POST => HTTPRequest::METH_POST,
HTTP_Request::PUT => HTTPRequest::METH_PUT,
HTTP_Request::DELETE => HTTPRequest::METH_DELETE,
HTTP_Request::OPTIONS => HTTPRequest::METH_OPTIONS,
HTTP_Request::TRACE => HTTPRequest::METH_TRACE,
HTTP_Request::CONNECT => HTTPRequest::METH_CONNECT,
);
// Create an http request object
$http_request = new HTTPRequest($request->uri(), $http_method_mapping[$request->method()]);
if ($this->_options)
{
// Set custom options
$http_request->setOptions($this->_options);
}
// Set headers
$http_request->setHeaders($request->headers()->getArrayCopy());
// Set cookies
$http_request->setCookies($request->cookie());
// Set query data (?foo=bar&bar=foo)
$http_request->setQueryData($request->query());
// Set the body
if ($request->method() == HTTP_Request::PUT)
{
$http_request->addPutData($request->body());
}
else
{
$http_request->setBody($request->body());
}
try
{
$http_request->send();
}
catch (HTTPRequestException $e)
{
throw new Request_Exception($e->getMessage());
}
catch (HTTPMalformedHeaderException $e)
{
throw new Request_Exception($e->getMessage());
}
catch (HTTPEncodingException $e)
{
throw new Request_Exception($e->getMessage());
}
// Build the response
$response->status($http_request->getResponseCode())
->headers($http_request->getResponseHeader())
->cookie($http_request->getResponseCookies())
->body($http_request->getResponseBody());
return $response;
}
} // End Kohana_Request_Client_HTTP

View File

@@ -0,0 +1,128 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* Request Client for internal execution
*
* @package Kohana
* @category Base
* @author Kohana Team
* @copyright (c) 2008-2012 Kohana Team
* @license http://kohanaframework.org/license
* @since 3.1.0
*/
class Kohana_Request_Client_Internal extends Request_Client {
/**
* @var array
*/
protected $_previous_environment;
/**
* Processes the request, executing the controller action that handles this
* request, determined by the [Route].
*
* $request->execute();
*
* @param Request $request
* @return Response
* @throws Kohana_Exception
* @uses [Kohana::$profiling]
* @uses [Profiler]
*/
public function execute_request(Request $request, Response $response)
{
// Create the class prefix
$prefix = 'Controller_';
// Directory
$directory = $request->directory();
// Controller
$controller = $request->controller();
if ($directory)
{
// Add the directory name to the class prefix
$prefix .= str_replace(array('\\', '/'), '_', trim($directory, '/')).'_';
}
if (Kohana::$profiling)
{
// Set the benchmark name
$benchmark = '"'.$request->uri().'"';
if ($request !== Request::$initial AND Request::$current)
{
// Add the parent request uri
$benchmark .= ' « "'.Request::$current->uri().'"';
}
// Start benchmarking
$benchmark = Profiler::start('Requests', $benchmark);
}
// Store the currently active request
$previous = Request::$current;
// Change the current request to this request
Request::$current = $request;
// Is this the initial request
$initial_request = ($request === Request::$initial);
try
{
if ( ! class_exists($prefix.$controller))
{
throw HTTP_Exception::factory(404,
'The requested URL :uri was not found on this server.',
array(':uri' => $request->uri())
)->request($request);
}
// Load the controller using reflection
$class = new ReflectionClass($prefix.$controller);
if ($class->isAbstract())
{
throw new Kohana_Exception(
'Cannot create instances of abstract :controller',
array(':controller' => $prefix.$controller)
);
}
// Create a new instance of the controller
$controller = $class->newInstance($request, $response);
// Run the controller's execute() method
$response = $class->getMethod('execute')->invoke($controller);
if ( ! $response instanceof Response)
{
// Controller failed to return a Response.
throw new Kohana_Exception('Controller failed to return a Response');
}
}
catch (HTTP_Exception $e)
{
// Get the response via the Exception
$response = $e->get_response();
}
catch (Exception $e)
{
// Generate an appropriate Response object
$response = Kohana_Exception::_handler($e);
}
// Restore the previous request
Request::$current = $previous;
if (isset($benchmark))
{
// Stop the benchmark
Profiler::stop($benchmark);
}
// Return the response
return $response;
}
} // End Kohana_Request_Client_Internal

View File

@@ -0,0 +1,10 @@
<?php
defined('SYSPATH') OR die('No direct script access.');
/**
* @package Kohana
* @category Exceptions
* @author Kohana Team
* @copyright (c) 2009-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
class Kohana_Request_Client_Recursion_Exception extends Kohana_Exception {}

View File

@@ -0,0 +1,109 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* [Request_Client_External] Stream driver performs external requests using php
* sockets. To use this driver, ensure the following is completed
* before executing an external request- ideally in the application bootstrap.
*
* @example
*
* // In application bootstrap
* Request_Client_External::$client = 'Request_Client_Stream';
*
* @package Kohana
* @category Base
* @author Kohana Team
* @copyright (c) 2008-2012 Kohana Team
* @license http://kohanaframework.org/license
* @uses [PHP Streams](http://php.net/manual/en/book.stream.php)
*/
class Kohana_Request_Client_Stream extends Request_Client_External {
/**
* Sends the HTTP message [Request] to a remote server and processes
* the response.
*
* @param Request $request request to send
* @param Response $request response to send
* @return Response
* @uses [PHP cURL](http://php.net/manual/en/book.curl.php)
*/
public function _send_message(Request $request, Response $response)
{
// Calculate stream mode
$mode = ($request->method() === HTTP_Request::GET) ? 'r' : 'r+';
// Process cookies
if ($cookies = $request->cookie())
{
$request->headers('cookie', http_build_query($cookies, NULL, '; '));
}
// Get the message body
$body = $request->body();
if (is_resource($body))
{
$body = stream_get_contents($body);
}
// Set the content length
$request->headers('content-length', (string) strlen($body));
list($protocol) = explode('/', $request->protocol());
// Create the context
$options = array(
strtolower($protocol) => array(
'method' => $request->method(),
'header' => (string) $request->headers(),
'content' => $body
)
);
// Create the context stream
$context = stream_context_create($options);
stream_context_set_option($context, $this->_options);
$uri = $request->uri();
if ($query = $request->query())
{
$uri .= '?'.http_build_query($query, NULL, '&');
}
$stream = fopen($uri, $mode, FALSE, $context);
$meta_data = stream_get_meta_data($stream);
// Get the HTTP response code
$http_response = array_shift($meta_data['wrapper_data']);
if (preg_match_all('/(\w+\/\d\.\d) (\d{3})/', $http_response, $matches) !== FALSE)
{
$protocol = $matches[1][0];
$status = (int) $matches[2][0];
}
else
{
$protocol = NULL;
$status = NULL;
}
// Get any exisiting response headers
$response_header = $response->headers();
// Process headers
array_map(array($response_header, 'parse_header_string'), array(), $meta_data['wrapper_data']);
$response->status($status)
->protocol($protocol)
->body(stream_get_contents($stream));
// Close the stream after use
fclose($stream);
return $response;
}
} // End Kohana_Request_Client_Stream

View File

@@ -0,0 +1,9 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* @package Kohana
* @category Exceptions
* @author Kohana Team
* @copyright (c) 2009-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
class Kohana_Request_Exception extends Kohana_Exception {}

View File

@@ -0,0 +1,713 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* Response wrapper. Created as the result of any [Request] execution
* or utility method (i.e. Redirect). Implements standard HTTP
* response format.
*
* @package Kohana
* @category Base
* @author Kohana Team
* @copyright (c) 2008-2012 Kohana Team
* @license http://kohanaphp.com/license
* @since 3.1.0
*/
class Kohana_Response implements HTTP_Response {
/**
* Factory method to create a new [Response]. Pass properties
* in using an associative array.
*
* // Create a new response
* $response = Response::factory();
*
* // Create a new response with headers
* $response = Response::factory(array('status' => 200));
*
* @param array $config Setup the response object
* @return Response
*/
public static function factory(array $config = array())
{
return new Response($config);
}
// HTTP status codes and messages
public static $messages = array(
// Informational 1xx
100 => 'Continue',
101 => 'Switching Protocols',
// Success 2xx
200 => 'OK',
201 => 'Created',
202 => 'Accepted',
203 => 'Non-Authoritative Information',
204 => 'No Content',
205 => 'Reset Content',
206 => 'Partial Content',
// Redirection 3xx
300 => 'Multiple Choices',
301 => 'Moved Permanently',
302 => 'Found', // 1.1
303 => 'See Other',
304 => 'Not Modified',
305 => 'Use Proxy',
// 306 is deprecated but reserved
307 => 'Temporary Redirect',
// Client Error 4xx
400 => 'Bad Request',
401 => 'Unauthorized',
402 => 'Payment Required',
403 => 'Forbidden',
404 => 'Not Found',
405 => 'Method Not Allowed',
406 => 'Not Acceptable',
407 => 'Proxy Authentication Required',
408 => 'Request Timeout',
409 => 'Conflict',
410 => 'Gone',
411 => 'Length Required',
412 => 'Precondition Failed',
413 => 'Request Entity Too Large',
414 => 'Request-URI Too Long',
415 => 'Unsupported Media Type',
416 => 'Requested Range Not Satisfiable',
417 => 'Expectation Failed',
// Server Error 5xx
500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Timeout',
505 => 'HTTP Version Not Supported',
509 => 'Bandwidth Limit Exceeded'
);
/**
* @var integer The response http status
*/
protected $_status = 200;
/**
* @var HTTP_Header Headers returned in the response
*/
protected $_header;
/**
* @var string The response body
*/
protected $_body = '';
/**
* @var array Cookies to be returned in the response
*/
protected $_cookies = array();
/**
* @var string The response protocol
*/
protected $_protocol;
/**
* Sets up the response object
*
* @param array $config Setup the response object
* @return void
*/
public function __construct(array $config = array())
{
$this->_header = new HTTP_Header;
foreach ($config as $key => $value)
{
if (property_exists($this, $key))
{
if ($key == '_header')
{
$this->headers($value);
}
else
{
$this->$key = $value;
}
}
}
}
/**
* Outputs the body when cast to string
*
* @return string
*/
public function __toString()
{
return $this->_body;
}
/**
* Gets or sets the body of the response
*
* @return mixed
*/
public function body($content = NULL)
{
if ($content === NULL)
return $this->_body;
$this->_body = (string) $content;
return $this;
}
/**
* Gets or sets the HTTP protocol. The standard protocol to use
* is `HTTP/1.1`.
*
* @param string $protocol Protocol to set to the request/response
* @return mixed
*/
public function protocol($protocol = NULL)
{
if ($protocol)
{
$this->_protocol = strtoupper($protocol);
return $this;
}
if ($this->_protocol === NULL)
{
$this->_protocol = HTTP::$protocol;
}
return $this->_protocol;
}
/**
* Sets or gets the HTTP status from this response.
*
* // Set the HTTP status to 404 Not Found
* $response = Response::factory()
* ->status(404);
*
* // Get the current status
* $status = $response->status();
*
* @param integer $status Status to set to this response
* @return mixed
*/
public function status($status = NULL)
{
if ($status === NULL)
{
return $this->_status;
}
elseif (array_key_exists($status, Response::$messages))
{
$this->_status = (int) $status;
return $this;
}
else
{
throw new Kohana_Exception(__METHOD__.' unknown status value : :value', array(':value' => $status));
}
}
/**
* Gets and sets headers to the [Response], allowing chaining
* of response methods. If chaining isn't required, direct
* access to the property should be used instead.
*
* // Get a header
* $accept = $response->headers('Content-Type');
*
* // Set a header
* $response->headers('Content-Type', 'text/html');
*
* // Get all headers
* $headers = $response->headers();
*
* // Set multiple headers
* $response->headers(array('Content-Type' => 'text/html', 'Cache-Control' => 'no-cache'));
*
* @param mixed $key
* @param string $value
* @return mixed
*/
public function headers($key = NULL, $value = NULL)
{
if ($key === NULL)
{
return $this->_header;
}
elseif (is_array($key))
{
$this->_header->exchangeArray($key);
return $this;
}
elseif ($value === NULL)
{
return Arr::get($this->_header, $key);
}
else
{
$this->_header[$key] = $value;
return $this;
}
}
/**
* Returns the length of the body for use with
* content header
*
* @return integer
*/
public function content_length()
{
return strlen($this->body());
}
/**
* Set and get cookies values for this response.
*
* // Get the cookies set to the response
* $cookies = $response->cookie();
*
* // Set a cookie to the response
* $response->cookie('session', array(
* 'value' => $value,
* 'expiration' => 12352234
* ));
*
* @param mixed $key cookie name, or array of cookie values
* @param string $value value to set to cookie
* @return string
* @return void
* @return [Response]
*/
public function cookie($key = NULL, $value = NULL)
{
// Handle the get cookie calls
if ($key === NULL)
return $this->_cookies;
elseif ( ! is_array($key) AND ! $value)
return Arr::get($this->_cookies, $key);
// Handle the set cookie calls
if (is_array($key))
{
reset($key);
while (list($_key, $_value) = each($key))
{
$this->cookie($_key, $_value);
}
}
else
{
if ( ! is_array($value))
{
$value = array(
'value' => $value,
'expiration' => Cookie::$expiration
);
}
elseif ( ! isset($value['expiration']))
{
$value['expiration'] = Cookie::$expiration;
}
$this->_cookies[$key] = $value;
}
return $this;
}
/**
* Deletes a cookie set to the response
*
* @param string $name
* @return Response
*/
public function delete_cookie($name)
{
unset($this->_cookies[$name]);
return $this;
}
/**
* Deletes all cookies from this response
*
* @return Response
*/
public function delete_cookies()
{
$this->_cookies = array();
return $this;
}
/**
* Sends the response status and all set headers.
*
* @param boolean $replace replace existing headers
* @param callback $callback function to handle header output
* @return mixed
*/
public function send_headers($replace = FALSE, $callback = NULL)
{
return $this->_header->send_headers($this, $replace, $callback);
}
/**
* Send file download as the response. All execution will be halted when
* this method is called! Use TRUE for the filename to send the current
* response as the file content. The third parameter allows the following
* options to be set:
*
* Type | Option | Description | Default Value
* ----------|-----------|------------------------------------|--------------
* `boolean` | inline | Display inline instead of download | `FALSE`
* `string` | mime_type | Manual mime type | Automatic
* `boolean` | delete | Delete the file after sending | `FALSE`
*
* Download a file that already exists:
*
* $request->send_file('media/packages/kohana.zip');
*
* Download generated content as a file:
*
* $request->response($content);
* $request->send_file(TRUE, $filename);
*
* [!!] No further processing can be done after this method is called!
*
* @param string $filename filename with path, or TRUE for the current response
* @param string $download downloaded file name
* @param array $options additional options
* @return void
* @throws Kohana_Exception
* @uses File::mime_by_ext
* @uses File::mime
* @uses Request::send_headers
*/
public function send_file($filename, $download = NULL, array $options = NULL)
{
if ( ! empty($options['mime_type']))
{
// The mime-type has been manually set
$mime = $options['mime_type'];
}
if ($filename === TRUE)
{
if (empty($download))
{
throw new Kohana_Exception('Download name must be provided for streaming files');
}
// Temporary files will automatically be deleted
$options['delete'] = FALSE;
if ( ! isset($mime))
{
// Guess the mime using the file extension
$mime = File::mime_by_ext(strtolower(pathinfo($download, PATHINFO_EXTENSION)));
}
// Force the data to be rendered if
$file_data = (string) $this->_body;
// Get the content size
$size = strlen($file_data);
// Create a temporary file to hold the current response
$file = tmpfile();
// Write the current response into the file
fwrite($file, $file_data);
// File data is no longer needed
unset($file_data);
}
else
{
// Get the complete file path
$filename = realpath($filename);
if (empty($download))
{
// Use the file name as the download file name
$download = pathinfo($filename, PATHINFO_BASENAME);
}
// Get the file size
$size = filesize($filename);
if ( ! isset($mime))
{
// Get the mime type from the extension of the download file
$mime = File::mime_by_ext(pathinfo($download, PATHINFO_EXTENSION));
}
// Open the file for reading
$file = fopen($filename, 'rb');
}
if ( ! is_resource($file))
{
throw new Kohana_Exception('Could not read file to send: :file', array(
':file' => $download,
));
}
// Inline or download?
$disposition = empty($options['inline']) ? 'attachment' : 'inline';
// Calculate byte range to download.
list($start, $end) = $this->_calculate_byte_range($size);
if ( ! empty($options['resumable']))
{
if ($start > 0 OR $end < ($size - 1))
{
// Partial Content
$this->_status = 206;
}
// Range of bytes being sent
$this->_header['content-range'] = 'bytes '.$start.'-'.$end.'/'.$size;
$this->_header['accept-ranges'] = 'bytes';
}
// Set the headers for a download
$this->_header['content-disposition'] = $disposition.'; filename="'.$download.'"';
$this->_header['content-type'] = $mime;
$this->_header['content-length'] = (string) (($end - $start) + 1);
if (Request::user_agent('browser') === 'Internet Explorer')
{
// Naturally, IE does not act like a real browser...
if (Request::$initial->secure())
{
// http://support.microsoft.com/kb/316431
$this->_header['pragma'] = $this->_header['cache-control'] = 'public';
}
if (version_compare(Request::user_agent('version'), '8.0', '>='))
{
// http://ajaxian.com/archives/ie-8-security
$this->_header['x-content-type-options'] = 'nosniff';
}
}
// Send all headers now
$this->send_headers();
while (ob_get_level())
{
// Flush all output buffers
ob_end_flush();
}
// Manually stop execution
ignore_user_abort(TRUE);
if ( ! Kohana::$safe_mode)
{
// Keep the script running forever
set_time_limit(0);
}
// Send data in 16kb blocks
$block = 1024 * 16;
fseek($file, $start);
while ( ! feof($file) AND ($pos = ftell($file)) <= $end)
{
if (connection_aborted())
break;
if ($pos + $block > $end)
{
// Don't read past the buffer.
$block = $end - $pos + 1;
}
// Output a block of the file
echo fread($file, $block);
// Send the data now
flush();
}
// Close the file
fclose($file);
if ( ! empty($options['delete']))
{
try
{
// Attempt to remove the file
unlink($filename);
}
catch (Exception $e)
{
// Create a text version of the exception
$error = Kohana_Exception::text($e);
if (is_object(Kohana::$log))
{
// Add this exception to the log
Kohana::$log->add(Log::ERROR, $error);
// Make sure the logs are written
Kohana::$log->write();
}
// Do NOT display the exception, it will corrupt the output!
}
}
// Stop execution
exit;
}
/**
* Renders the HTTP_Interaction to a string, producing
*
* - Protocol
* - Headers
* - Body
*
* @return string
*/
public function render()
{
if ( ! $this->_header->offsetExists('content-type'))
{
// Add the default Content-Type header if required
$this->_header['content-type'] = Kohana::$content_type.'; charset='.Kohana::$charset;
}
// Set the content length
$this->headers('content-length', (string) $this->content_length());
// If Kohana expose, set the user-agent
if (Kohana::$expose)
{
$this->headers('user-agent', Kohana::version());
}
// Prepare cookies
if ($this->_cookies)
{
if (extension_loaded('http'))
{
$this->_header['set-cookie'] = http_build_cookie($this->_cookies);
}
else
{
$cookies = array();
// Parse each
foreach ($this->_cookies as $key => $value)
{
$string = $key.'='.$value['value'].'; expires='.date('l, d M Y H:i:s T', $value['expiration']);
$cookies[] = $string;
}
// Create the cookie string
$this->_header['set-cookie'] = $cookies;
}
}
$output = $this->_protocol.' '.$this->_status.' '.Response::$messages[$this->_status]."\r\n";
$output .= (string) $this->_header;
$output .= $this->_body;
return $output;
}
/**
* Generate ETag
* Generates an ETag from the response ready to be returned
*
* @throws Request_Exception
* @return String Generated ETag
*/
public function generate_etag()
{
if ($this->_body === '')
{
throw new Request_Exception('No response yet associated with request - cannot auto generate resource ETag');
}
// Generate a unique hash for the response
return '"'.sha1($this->render()).'"';
}
/**
* Parse the byte ranges from the HTTP_RANGE header used for
* resumable downloads.
*
* @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35
* @return array|FALSE
*/
protected function _parse_byte_range()
{
if ( ! isset($_SERVER['HTTP_RANGE']))
{
return FALSE;
}
// TODO, speed this up with the use of string functions.
preg_match_all('/(-?[0-9]++(?:-(?![0-9]++))?)(?:-?([0-9]++))?/', $_SERVER['HTTP_RANGE'], $matches, PREG_SET_ORDER);
return $matches[0];
}
/**
* Calculates the byte range to use with send_file. If HTTP_RANGE doesn't
* exist then the complete byte range is returned
*
* @param integer $size
* @return array
*/
protected function _calculate_byte_range($size)
{
// Defaults to start with when the HTTP_RANGE header doesn't exist.
$start = 0;
$end = $size - 1;
if ($range = $this->_parse_byte_range())
{
// We have a byte range from HTTP_RANGE
$start = $range[1];
if ($start[0] === '-')
{
// A negative value means we start from the end, so -500 would be the
// last 500 bytes.
$start = $size - abs($start);
}
if (isset($range[2]))
{
// Set the end range
$end = $range[2];
}
}
// Normalize values.
$start = abs(intval($start));
// Keep the the end value in bounds and normalize it.
$end = min(abs(intval($end)), $size - 1);
// Keep the start in bounds.
$start = ($end < $start) ? 0 : max($start, 0);
return array($start, $end);
}
} // End Kohana_Response

View File

@@ -0,0 +1,629 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* Routes are used to determine the controller and action for a requested URI.
* Every route generates a regular expression which is used to match a URI
* and a route. Routes may also contain keys which can be used to set the
* controller, action, and parameters.
*
* Each <key> will be translated to a regular expression using a default
* regular expression pattern. You can override the default pattern by providing
* a pattern for the key:
*
* // This route will only match when <id> is a digit
* Route::set('user', 'user/<action>/<id>', array('id' => '\d+'));
*
* // This route will match when <path> is anything
* Route::set('file', '<path>', array('path' => '.*'));
*
* It is also possible to create optional segments by using parentheses in
* the URI definition:
*
* // This is the standard default route, and no keys are required
* Route::set('default', '(<controller>(/<action>(/<id>)))');
*
* // This route only requires the <file> key
* Route::set('file', '(<path>/)<file>(.<format>)', array('path' => '.*', 'format' => '\w+'));
*
* Routes also provide a way to generate URIs (called "reverse routing"), which
* makes them an extremely powerful and flexible way to generate internal links.
*
* @package Kohana
* @category Base
* @author Kohana Team
* @copyright (c) 2008-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
class Kohana_Route {
// Defines the pattern of a <segment>
const REGEX_KEY = '<([a-zA-Z0-9_]++)>';
// What can be part of a <segment> value
const REGEX_SEGMENT = '[^/.,;?\n]++';
// What must be escaped in the route regex
const REGEX_ESCAPE = '[.\\+*?[^\\]${}=!|]';
/**
* @var string default protocol for all routes
*
* @example 'http://'
*/
public static $default_protocol = 'http://';
/**
* @var array list of valid localhost entries
*/
public static $localhosts = array(FALSE, '', 'local', 'localhost');
/**
* @var string default action for all routes
*/
public static $default_action = 'index';
/**
* @var bool Indicates whether routes are cached
*/
public static $cache = FALSE;
/**
* @var array
*/
protected static $_routes = array();
/**
* Stores a named route and returns it. The "action" will always be set to
* "index" if it is not defined.
*
* Route::set('default', '(<controller>(/<action>(/<id>)))')
* ->defaults(array(
* 'controller' => 'welcome',
* ));
*
* @param string $name route name
* @param string $uri URI pattern
* @param array $regex regex patterns for route keys
* @return Route
*/
public static function set($name, $uri = NULL, $regex = NULL)
{
return Route::$_routes[$name] = new Route($uri, $regex);
}
/**
* Retrieves a named route.
*
* $route = Route::get('default');
*
* @param string $name route name
* @return Route
* @throws Kohana_Exception
*/
public static function get($name)
{
if ( ! isset(Route::$_routes[$name]))
{
throw new Kohana_Exception('The requested route does not exist: :route',
array(':route' => $name));
}
return Route::$_routes[$name];
}
/**
* Retrieves all named routes.
*
* $routes = Route::all();
*
* @return array routes by name
*/
public static function all()
{
return Route::$_routes;
}
/**
* Get the name of a route.
*
* $name = Route::name($route)
*
* @param Route $route instance
* @return string
*/
public static function name(Route $route)
{
return array_search($route, Route::$_routes);
}
/**
* Saves or loads the route cache. If your routes will remain the same for
* a long period of time, use this to reload the routes from the cache
* rather than redefining them on every page load.
*
* if ( ! Route::cache())
* {
* // Set routes here
* Route::cache(TRUE);
* }
*
* @param boolean $save cache the current routes
* @param boolean $append append, rather than replace, cached routes when loading
* @return void when saving routes
* @return boolean when loading routes
* @uses Kohana::cache
*/
public static function cache($save = FALSE, $append = FALSE)
{
if ($save === TRUE)
{
try
{
// Cache all defined routes
Kohana::cache('Route::cache()', Route::$_routes);
}
catch (Exception $e)
{
// We most likely have a lambda in a route, which cannot be cached
throw new Kohana_Exception('One or more routes could not be cached (:message)', array(
':message' => $e->getMessage(),
), 0, $e);
}
}
else
{
if ($routes = Kohana::cache('Route::cache()'))
{
if ($append)
{
// Append cached routes
Route::$_routes += $routes;
}
else
{
// Replace existing routes
Route::$_routes = $routes;
}
// Routes were cached
return Route::$cache = TRUE;
}
else
{
// Routes were not cached
return Route::$cache = FALSE;
}
}
}
/**
* Create a URL from a route name. This is a shortcut for:
*
* echo URL::site(Route::get($name)->uri($params), $protocol);
*
* @param string $name route name
* @param array $params URI parameters
* @param mixed $protocol protocol string or boolean, adds protocol and domain
* @return string
* @since 3.0.7
* @uses URL::site
*/
public static function url($name, array $params = NULL, $protocol = NULL)
{
$route = Route::get($name);
// Create a URI with the route and convert it to a URL
if ($route->is_external())
return Route::get($name)->uri($params);
else
return URL::site(Route::get($name)->uri($params), $protocol);
}
/**
* Returns the compiled regular expression for the route. This translates
* keys and optional groups to a proper PCRE regular expression.
*
* $compiled = Route::compile(
* '<controller>(/<action>(/<id>))',
* array(
* 'controller' => '[a-z]+',
* 'id' => '\d+',
* )
* );
*
* @return string
* @uses Route::REGEX_ESCAPE
* @uses Route::REGEX_SEGMENT
*/
public static function compile($uri, array $regex = NULL)
{
// The URI should be considered literal except for keys and optional parts
// Escape everything preg_quote would escape except for : ( ) < >
$expression = preg_replace('#'.Route::REGEX_ESCAPE.'#', '\\\\$0', $uri);
if (strpos($expression, '(') !== FALSE)
{
// Make optional parts of the URI non-capturing and optional
$expression = str_replace(array('(', ')'), array('(?:', ')?'), $expression);
}
// Insert default regex for keys
$expression = str_replace(array('<', '>'), array('(?P<', '>'.Route::REGEX_SEGMENT.')'), $expression);
if ($regex)
{
$search = $replace = array();
foreach ($regex as $key => $value)
{
$search[] = "<$key>".Route::REGEX_SEGMENT;
$replace[] = "<$key>$value";
}
// Replace the default regex with the user-specified regex
$expression = str_replace($search, $replace, $expression);
}
return '#^'.$expression.'$#uD';
}
/**
* @var array route filters
*/
protected $_filters = array();
/**
* @var string route URI
*/
protected $_uri = '';
/**
* @var array
*/
protected $_regex = array();
/**
* @var array
*/
protected $_defaults = array('action' => 'index', 'host' => FALSE);
/**
* @var string
*/
protected $_route_regex;
/**
* Creates a new route. Sets the URI and regular expressions for keys.
* Routes should always be created with [Route::set] or they will not
* be properly stored.
*
* $route = new Route($uri, $regex);
*
* The $uri parameter should be a string for basic regex matching.
*
*
* @param string $uri route URI pattern
* @param array $regex key patterns
* @return void
* @uses Route::_compile
*/
public function __construct($uri = NULL, $regex = NULL)
{
if ($uri === NULL)
{
// Assume the route is from cache
return;
}
if ( ! empty($uri))
{
$this->_uri = $uri;
}
if ( ! empty($regex))
{
$this->_regex = $regex;
}
// Store the compiled regex locally
$this->_route_regex = Route::compile($uri, $regex);
}
/**
* Provides default values for keys when they are not present. The default
* action will always be "index" unless it is overloaded here.
*
* $route->defaults(array(
* 'controller' => 'welcome',
* 'action' => 'index'
* ));
*
* If no parameter is passed, this method will act as a getter.
*
* @param array $defaults key values
* @return $this or array
*/
public function defaults(array $defaults = NULL)
{
if ($defaults === NULL)
{
return $this->_defaults;
}
$this->_defaults = $defaults;
return $this;
}
/**
* Filters to be run before route parameters are returned:
*
* $route->filter(
* function(Route $route, $params, Request $request)
* {
* if ($request->method() !== HTTP_Request::POST)
* {
* return FALSE; // This route only matches POST requests
* }
* if ($params AND $params['controller'] === 'welcome')
* {
* $params['controller'] = 'home';
* }
*
* return $params;
* }
* );
*
* To prevent a route from matching, return `FALSE`. To replace the route
* parameters, return an array.
*
* [!!] Default parameters are added before filters are called!
*
* @throws Kohana_Exception
* @param array $callback callback string, array, or closure
* @return $this
*/
public function filter($callback)
{
if ( ! is_callable($callback))
{
throw new Kohana_Exception('Invalid Route::callback specified');
}
$this->_filters[] = $callback;
return $this;
}
/**
* Tests if the route matches a given URI. A successful match will return
* all of the routed parameters as an array. A failed match will return
* boolean FALSE.
*
* // Params: controller = users, action = edit, id = 10
* $params = $route->matches('users/edit/10');
*
* This method should almost always be used within an if/else block:
*
* if ($params = $route->matches($uri))
* {
* // Parse the parameters
* }
*
* @param string $uri URI to match
* @return array on success
* @return FALSE on failure
*/
public function matches(Request $request)
{
// Get the URI from the Request
$uri = trim($request->uri(), '/');
if ( ! preg_match($this->_route_regex, $uri, $matches))
return FALSE;
$params = array();
foreach ($matches as $key => $value)
{
if (is_int($key))
{
// Skip all unnamed keys
continue;
}
// Set the value for all matched keys
$params[$key] = $value;
}
foreach ($this->_defaults as $key => $value)
{
if ( ! isset($params[$key]) OR $params[$key] === '')
{
// Set default values for any key that was not matched
$params[$key] = $value;
}
}
if ( ! empty($params['controller']))
{
// PSR-0: Replace underscores with spaces, run ucwords, then replace underscore
$params['controller'] = str_replace(' ', '_', ucwords(str_replace('_', ' ', $params['controller'])));
}
if ( ! empty($params['directory']))
{
// PSR-0: Replace underscores with spaces, run ucwords, then replace underscore
$params['directory'] = str_replace(' ', '_', ucwords(str_replace('_', ' ', $params['directory'])));
}
if ($this->_filters)
{
foreach ($this->_filters as $callback)
{
// Execute the filter giving it the route, params, and request
$return = call_user_func($callback, $this, $params, $request);
if ($return === FALSE)
{
// Filter has aborted the match
return FALSE;
}
elseif (is_array($return))
{
// Filter has modified the parameters
$params = $return;
}
}
}
return $params;
}
/**
* Returns whether this route is an external route
* to a remote controller.
*
* @return boolean
*/
public function is_external()
{
return ! in_array(Arr::get($this->_defaults, 'host', FALSE), Route::$localhosts);
}
/**
* Generates a URI for the current route based on the parameters given.
*
* // Using the "default" route: "users/profile/10"
* $route->uri(array(
* 'controller' => 'users',
* 'action' => 'profile',
* 'id' => '10'
* ));
*
* @param array $params URI parameters
* @return string
* @throws Kohana_Exception
* @uses Route::REGEX_Key
*/
public function uri(array $params = NULL)
{
// Start with the routed URI
$uri = $this->_uri;
if (strpos($uri, '<') === FALSE AND strpos($uri, '(') === FALSE)
{
// This is a static route, no need to replace anything
if ( ! $this->is_external())
return $uri;
// If the localhost setting does not have a protocol
if (strpos($this->_defaults['host'], '://') === FALSE)
{
// Use the default defined protocol
$params['host'] = Route::$default_protocol.$this->_defaults['host'];
}
else
{
// Use the supplied host with protocol
$params['host'] = $this->_defaults['host'];
}
// Compile the final uri and return it
return rtrim($params['host'], '/').'/'.$uri;
}
// Keep track of whether an optional param was replaced
$provided_optional = FALSE;
while (preg_match('#\([^()]++\)#', $uri, $match))
{
// Search for the matched value
$search = $match[0];
// Remove the parenthesis from the match as the replace
$replace = substr($match[0], 1, -1);
while (preg_match('#'.Route::REGEX_KEY.'#', $replace, $match))
{
list($key, $param) = $match;
if (isset($params[$param]) AND $params[$param] !== Arr::get($this->_defaults, $param))
{
// Future optional params should be required
$provided_optional = TRUE;
// Replace the key with the parameter value
$replace = str_replace($key, $params[$param], $replace);
}
elseif ($provided_optional)
{
// Look for a default
if (isset($this->_defaults[$param]))
{
$replace = str_replace($key, $this->_defaults[$param], $replace);
}
else
{
// Ungrouped parameters are required
throw new Kohana_Exception('Required route parameter not passed: :param', array(
':param' => $param,
));
}
}
else
{
// This group has missing parameters
$replace = '';
break;
}
}
// Replace the group in the URI
$uri = str_replace($search, $replace, $uri);
}
while (preg_match('#'.Route::REGEX_KEY.'#', $uri, $match))
{
list($key, $param) = $match;
if ( ! isset($params[$param]))
{
// Look for a default
if (isset($this->_defaults[$param]))
{
$params[$param] = $this->_defaults[$param];
}
else
{
// Ungrouped parameters are required
throw new Kohana_Exception('Required route parameter not passed: :param', array(
':param' => $param,
));
}
}
$uri = str_replace($key, $params[$param], $uri);
}
// Trim all extra slashes from the URI
$uri = preg_replace('#//+#', '/', rtrim($uri, '/'));
if ($this->is_external())
{
// Need to add the host to the URI
$host = $this->_defaults['host'];
if (strpos($host, '://') === FALSE)
{
// Use the default defined protocol
$host = Route::$default_protocol.$host;
}
// Clean up the host and prepend it to the URI
$uri = rtrim($host, '/').'/'.$uri;
}
return $uri;
}
} // End Route

View File

@@ -0,0 +1,103 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* Security helper class.
*
* @package Kohana
* @category Security
* @author Kohana Team
* @copyright (c) 2007-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
class Kohana_Security {
/**
* @var string key name used for token storage
*/
public static $token_name = 'security_token';
/**
* Generate and store a unique token which can be used to help prevent
* [CSRF](http://wikipedia.org/wiki/Cross_Site_Request_Forgery) attacks.
*
* $token = Security::token();
*
* You can insert this token into your forms as a hidden field:
*
* echo Form::hidden('csrf', Security::token());
*
* And then check it when using [Validation]:
*
* $array->rules('csrf', array(
* 'not_empty' => NULL,
* 'Security::check' => NULL,
* ));
*
* This provides a basic, but effective, method of preventing CSRF attacks.
*
* @param boolean $new force a new token to be generated?
* @return string
* @uses Session::instance
*/
public static function token($new = FALSE)
{
$session = Session::instance();
// Get the current token
$token = $session->get(Security::$token_name);
if ($new === TRUE OR ! $token)
{
// Generate a new unique token
$token = sha1(uniqid(NULL, TRUE));
// Store the new token
$session->set(Security::$token_name, $token);
}
return $token;
}
/**
* Check that the given token matches the currently stored security token.
*
* if (Security::check($token))
* {
* // Pass
* }
*
* @param string $token token to check
* @return boolean
* @uses Security::token
*/
public static function check($token)
{
return Security::token() === $token;
}
/**
* Remove image tags from a string.
*
* $str = Security::strip_image_tags($str);
*
* @param string $str string to sanitize
* @return string
*/
public static function strip_image_tags($str)
{
return preg_replace('#<img\s.*?(?:src\s*=\s*["\']?([^"\'<>\s]*)["\']?[^>]*)?>#is', '$1', $str);
}
/**
* Encodes PHP tags in a string.
*
* $str = Security::encode_php_tags($str);
*
* @param string $str string to sanitize
* @return string
*/
public static function encode_php_tags($str)
{
return str_replace(array('<?', '?>'), array('&lt;?', '?&gt;'), $str);
}
} // End security

View File

@@ -0,0 +1,505 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* Base session class.
*
* @package Kohana
* @category Session
* @author Kohana Team
* @copyright (c) 2008-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
abstract class Kohana_Session {
/**
* @var string default session adapter
*/
public static $default = 'native';
/**
* @var array session instances
*/
public static $instances = array();
/**
* Creates a singleton session of the given type. Some session types
* (native, database) also support restarting a session by passing a
* session id as the second parameter.
*
* $session = Session::instance();
*
* [!!] [Session::write] will automatically be called when the request ends.
*
* @param string $type type of session (native, cookie, etc)
* @param string $id session identifier
* @return Session
* @uses Kohana::$config
*/
public static function instance($type = NULL, $id = NULL)
{
if ($type === NULL)
{
// Use the default type
$type = Session::$default;
}
if ( ! isset(Session::$instances[$type]))
{
// Load the configuration for this type
$config = Kohana::$config->load('session')->get($type);
// Set the session class name
$class = 'Session_'.ucfirst($type);
// Create a new session instance
Session::$instances[$type] = $session = new $class($config, $id);
// Write the session at shutdown
register_shutdown_function(array($session, 'write'));
}
return Session::$instances[$type];
}
/**
* @var string cookie name
*/
protected $_name = 'session';
/**
* @var int cookie lifetime
*/
protected $_lifetime = 0;
/**
* @var bool encrypt session data?
*/
protected $_encrypted = FALSE;
/**
* @var array session data
*/
protected $_data = array();
/**
* @var bool session destroyed?
*/
protected $_destroyed = FALSE;
/**
* Overloads the name, lifetime, and encrypted session settings.
*
* [!!] Sessions can only be created using the [Session::instance] method.
*
* @param array $config configuration
* @param string $id session id
* @return void
* @uses Session::read
*/
public function __construct(array $config = NULL, $id = NULL)
{
if (isset($config['name']))
{
// Cookie name to store the session id in
$this->_name = (string) $config['name'];
}
if (isset($config['lifetime']))
{
// Cookie lifetime
$this->_lifetime = (int) $config['lifetime'];
}
if (isset($config['encrypted']))
{
if ($config['encrypted'] === TRUE)
{
// Use the default Encrypt instance
$config['encrypted'] = 'default';
}
// Enable or disable encryption of data
$this->_encrypted = $config['encrypted'];
}
// Load the session
$this->read($id);
}
/**
* Session object is rendered to a serialized string. If encryption is
* enabled, the session will be encrypted. If not, the output string will
* be encoded.
*
* echo $session;
*
* @return string
* @uses Encrypt::encode
*/
public function __toString()
{
// Serialize the data array
$data = $this->_serialize($this->_data);
if ($this->_encrypted)
{
// Encrypt the data using the default key
$data = Encrypt::instance($this->_encrypted)->encode($data);
}
else
{
// Encode the data
$data = $this->_encode($data);
}
return $data;
}
/**
* Returns the current session array. The returned array can also be
* assigned by reference.
*
* // Get a copy of the current session data
* $data = $session->as_array();
*
* // Assign by reference for modification
* $data =& $session->as_array();
*
* @return array
*/
public function & as_array()
{
return $this->_data;
}
/**
* Get the current session id, if the session supports it.
*
* $id = $session->id();
*
* [!!] Not all session types have ids.
*
* @return string
* @since 3.0.8
*/
public function id()
{
return NULL;
}
/**
* Get the current session cookie name.
*
* $name = $session->name();
*
* @return string
* @since 3.0.8
*/
public function name()
{
return $this->_name;
}
/**
* Get a variable from the session array.
*
* $foo = $session->get('foo');
*
* @param string $key variable name
* @param mixed $default default value to return
* @return mixed
*/
public function get($key, $default = NULL)
{
return array_key_exists($key, $this->_data) ? $this->_data[$key] : $default;
}
/**
* Get and delete a variable from the session array.
*
* $bar = $session->get_once('bar');
*
* @param string $key variable name
* @param mixed $default default value to return
* @return mixed
*/
public function get_once($key, $default = NULL)
{
$value = $this->get($key, $default);
unset($this->_data[$key]);
return $value;
}
/**
* Set a variable in the session array.
*
* $session->set('foo', 'bar');
*
* @param string $key variable name
* @param mixed $value value
* @return $this
*/
public function set($key, $value)
{
$this->_data[$key] = $value;
return $this;
}
/**
* Set a variable by reference.
*
* $session->bind('foo', $foo);
*
* @param string $key variable name
* @param mixed $value referenced value
* @return $this
*/
public function bind($key, & $value)
{
$this->_data[$key] =& $value;
return $this;
}
/**
* Removes a variable in the session array.
*
* $session->delete('foo');
*
* @param string $key,... variable name
* @return $this
*/
public function delete($key)
{
$args = func_get_args();
foreach ($args as $key)
{
unset($this->_data[$key]);
}
return $this;
}
/**
* Loads existing session data.
*
* $session->read();
*
* @param string $id session id
* @return void
*/
public function read($id = NULL)
{
$data = NULL;
try
{
if (is_string($data = $this->_read($id)))
{
if ($this->_encrypted)
{
// Decrypt the data using the default key
$data = Encrypt::instance($this->_encrypted)->decode($data);
}
else
{
// Decode the data
$data = $this->_decode($data);
}
// Unserialize the data
$data = $this->_unserialize($data);
}
else
{
// Ignore these, session is valid, likely no data though.
}
}
catch (Exception $e)
{
// Error reading the session, usually a corrupt session.
throw new Session_Exception('Error reading session data.', NULL, Session_Exception::SESSION_CORRUPT);
}
if (is_array($data))
{
// Load the data locally
$this->_data = $data;
}
}
/**
* Generates a new session id and returns it.
*
* $id = $session->regenerate();
*
* @return string
*/
public function regenerate()
{
return $this->_regenerate();
}
/**
* Sets the last_active timestamp and saves the session.
*
* $session->write();
*
* [!!] Any errors that occur during session writing will be logged,
* but not displayed, because sessions are written after output has
* been sent.
*
* @return boolean
* @uses Kohana::$log
*/
public function write()
{
if (headers_sent() OR $this->_destroyed)
{
// Session cannot be written when the headers are sent or when
// the session has been destroyed
return FALSE;
}
// Set the last active timestamp
$this->_data['last_active'] = time();
try
{
return $this->_write();
}
catch (Exception $e)
{
// Log & ignore all errors when a write fails
Kohana::$log->add(Log::ERROR, Kohana_Exception::text($e))->write();
return FALSE;
}
}
/**
* Completely destroy the current session.
*
* $success = $session->destroy();
*
* @return boolean
*/
public function destroy()
{
if ($this->_destroyed === FALSE)
{
if ($this->_destroyed = $this->_destroy())
{
// The session has been destroyed, clear all data
$this->_data = array();
}
}
return $this->_destroyed;
}
/**
* Restart the session.
*
* $success = $session->restart();
*
* @return boolean
*/
public function restart()
{
if ($this->_destroyed === FALSE)
{
// Wipe out the current session.
$this->destroy();
}
// Allow the new session to be saved
$this->_destroyed = FALSE;
return $this->_restart();
}
/**
* Serializes the session data.
*
* @param array $data data
* @return string
*/
protected function _serialize($data)
{
return serialize($data);
}
/**
* Unserializes the session data.
*
* @param string $data data
* @return array
*/
protected function _unserialize($data)
{
return unserialize($data);
}
/**
* Encodes the session data using [base64_encode].
*
* @param string $data data
* @return string
*/
protected function _encode($data)
{
return base64_encode($data);
}
/**
* Decodes the session data using [base64_decode].
*
* @param string $data data
* @return string
*/
protected function _decode($data)
{
return base64_decode($data);
}
/**
* Loads the raw session data string and returns it.
*
* @param string $id session id
* @return string
*/
abstract protected function _read($id = NULL);
/**
* Generate a new session id and return it.
*
* @return string
*/
abstract protected function _regenerate();
/**
* Writes the current session.
*
* @return boolean
*/
abstract protected function _write();
/**
* Destroys the current session.
*
* @return boolean
*/
abstract protected function _destroy();
/**
* Restarts the current session.
*
* @return boolean
*/
abstract protected function _restart();
} // End Session

View File

@@ -0,0 +1,55 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* Cookie-based session class.
*
* @package Kohana
* @category Session
* @author Kohana Team
* @copyright (c) 2008-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
class Kohana_Session_Cookie extends Session {
/**
* @param string $id session id
* @return string
*/
protected function _read($id = NULL)
{
return Cookie::get($this->_name, NULL);
}
/**
* @return null
*/
protected function _regenerate()
{
// Cookie sessions have no id
return NULL;
}
/**
* @return bool
*/
protected function _write()
{
return Cookie::set($this->_name, $this->__toString(), $this->_lifetime);
}
/**
* @return bool
*/
protected function _restart()
{
return TRUE;
}
/**
* @return bool
*/
protected function _destroy()
{
return Cookie::delete($this->_name);
}
} // End Session_Cookie

View File

@@ -0,0 +1,11 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* @package Kohana
* @category Exceptions
* @author Kohana Team
* @copyright (c) 2009-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
class Kohana_Session_Exception extends Kohana_Exception {
const SESSION_CORRUPT = 1;
}

View File

@@ -0,0 +1,107 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* Native PHP session class.
*
* @package Kohana
* @category Session
* @author Kohana Team
* @copyright (c) 2008-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
class Kohana_Session_Native extends Session {
/**
* @return string
*/
public function id()
{
return session_id();
}
/**
* @param string $id session id
* @return null
*/
protected function _read($id = NULL)
{
// Sync up the session cookie with Cookie parameters
session_set_cookie_params($this->_lifetime, Cookie::$path, Cookie::$domain, Cookie::$secure, Cookie::$httponly);
// Do not allow PHP to send Cache-Control headers
session_cache_limiter(FALSE);
// Set the session cookie name
session_name($this->_name);
if ($id)
{
// Set the session id
session_id($id);
}
// Start the session
session_start();
// Use the $_SESSION global for storing data
$this->_data =& $_SESSION;
return NULL;
}
/**
* @return string
*/
protected function _regenerate()
{
// Regenerate the session id
session_regenerate_id();
return session_id();
}
/**
* @return bool
*/
protected function _write()
{
// Write and close the session
session_write_close();
return TRUE;
}
/**
* @return bool
*/
protected function _restart()
{
// Fire up a new session
$status = session_start();
// Use the $_SESSION global for storing data
$this->_data =& $_SESSION;
return $status;
}
/**
* @return bool
*/
protected function _destroy()
{
// Destroy the current session
session_destroy();
// Did destruction work?
$status = ! session_id();
if ($status)
{
// Make sure the session cannot be restarted
Cookie::delete($this->_name);
}
return $status;
}
} // End Session_Native

View File

@@ -0,0 +1,686 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* Text helper class. Provides simple methods for working with text.
*
* @package Kohana
* @category Helpers
* @author Kohana Team
* @copyright (c) 2007-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
class Kohana_Text {
/**
* @var array number units and text equivalents
*/
public static $units = array(
1000000000 => 'billion',
1000000 => 'million',
1000 => 'thousand',
100 => 'hundred',
90 => 'ninety',
80 => 'eighty',
70 => 'seventy',
60 => 'sixty',
50 => 'fifty',
40 => 'fourty',
30 => 'thirty',
20 => 'twenty',
19 => 'nineteen',
18 => 'eighteen',
17 => 'seventeen',
16 => 'sixteen',
15 => 'fifteen',
14 => 'fourteen',
13 => 'thirteen',
12 => 'twelve',
11 => 'eleven',
10 => 'ten',
9 => 'nine',
8 => 'eight',
7 => 'seven',
6 => 'six',
5 => 'five',
4 => 'four',
3 => 'three',
2 => 'two',
1 => 'one',
);
/**
* Limits a phrase to a given number of words.
*
* $text = Text::limit_words($text);
*
* @param string $str phrase to limit words of
* @param integer $limit number of words to limit to
* @param string $end_char end character or entity
* @return string
*/
public static function limit_words($str, $limit = 100, $end_char = NULL)
{
$limit = (int) $limit;
$end_char = ($end_char === NULL) ? '…' : $end_char;
if (trim($str) === '')
return $str;
if ($limit <= 0)
return $end_char;
preg_match('/^\s*+(?:\S++\s*+){1,'.$limit.'}/u', $str, $matches);
// Only attach the end character if the matched string is shorter
// than the starting string.
return rtrim($matches[0]).((strlen($matches[0]) === strlen($str)) ? '' : $end_char);
}
/**
* Limits a phrase to a given number of characters.
*
* $text = Text::limit_chars($text);
*
* @param string $str phrase to limit characters of
* @param integer $limit number of characters to limit to
* @param string $end_char end character or entity
* @param boolean $preserve_words enable or disable the preservation of words while limiting
* @return string
* @uses UTF8::strlen
*/
public static function limit_chars($str, $limit = 100, $end_char = NULL, $preserve_words = FALSE)
{
$end_char = ($end_char === NULL) ? '…' : $end_char;
$limit = (int) $limit;
if (trim($str) === '' OR UTF8::strlen($str) <= $limit)
return $str;
if ($limit <= 0)
return $end_char;
if ($preserve_words === FALSE)
return rtrim(UTF8::substr($str, 0, $limit)).$end_char;
// Don't preserve words. The limit is considered the top limit.
// No strings with a length longer than $limit should be returned.
if ( ! preg_match('/^.{0,'.$limit.'}\s/us', $str, $matches))
return $end_char;
return rtrim($matches[0]).((strlen($matches[0]) === strlen($str)) ? '' : $end_char);
}
/**
* Alternates between two or more strings.
*
* echo Text::alternate('one', 'two'); // "one"
* echo Text::alternate('one', 'two'); // "two"
* echo Text::alternate('one', 'two'); // "one"
*
* Note that using multiple iterations of different strings may produce
* unexpected results.
*
* @param string $str,... strings to alternate between
* @return string
*/
public static function alternate()
{
static $i;
if (func_num_args() === 0)
{
$i = 0;
return '';
}
$args = func_get_args();
return $args[($i++ % count($args))];
}
/**
* Generates a random string of a given type and length.
*
*
* $str = Text::random(); // 8 character random string
*
* The following types are supported:
*
* alnum
* : Upper and lower case a-z, 0-9 (default)
*
* alpha
* : Upper and lower case a-z
*
* hexdec
* : Hexadecimal characters a-f, 0-9
*
* distinct
* : Uppercase characters and numbers that cannot be confused
*
* You can also create a custom type by providing the "pool" of characters
* as the type.
*
* @param string $type a type of pool, or a string of characters to use as the pool
* @param integer $length length of string to return
* @return string
* @uses UTF8::split
*/
public static function random($type = NULL, $length = 8)
{
if ($type === NULL)
{
// Default is to generate an alphanumeric string
$type = 'alnum';
}
$utf8 = FALSE;
switch ($type)
{
case 'alnum':
$pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
break;
case 'alpha':
$pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
break;
case 'hexdec':
$pool = '0123456789abcdef';
break;
case 'numeric':
$pool = '0123456789';
break;
case 'nozero':
$pool = '123456789';
break;
case 'distinct':
$pool = '2345679ACDEFHJKLMNPRSTUVWXYZ';
break;
default:
$pool = (string) $type;
$utf8 = ! UTF8::is_ascii($pool);
break;
}
// Split the pool into an array of characters
$pool = ($utf8 === TRUE) ? UTF8::str_split($pool, 1) : str_split($pool, 1);
// Largest pool key
$max = count($pool) - 1;
$str = '';
for ($i = 0; $i < $length; $i++)
{
// Select a random character from the pool and add it to the string
$str .= $pool[mt_rand(0, $max)];
}
// Make sure alnum strings contain at least one letter and one digit
if ($type === 'alnum' AND $length > 1)
{
if (ctype_alpha($str))
{
// Add a random digit
$str[mt_rand(0, $length - 1)] = chr(mt_rand(48, 57));
}
elseif (ctype_digit($str))
{
// Add a random letter
$str[mt_rand(0, $length - 1)] = chr(mt_rand(65, 90));
}
}
return $str;
}
/**
* Uppercase words that are not separated by spaces, using a custom
* delimiter or the default.
*
* $str = Text::ucfirst('content-type'); // returns "Content-Type"
*
* @param string $string string to transform
* @param string $delimiter delemiter to use
* @return string
*/
public static function ucfirst($string, $delimiter = '-')
{
// Put the keys back the Case-Convention expected
return implode($delimiter, array_map('ucfirst', explode($delimiter, $string)));
}
/**
* Reduces multiple slashes in a string to single slashes.
*
* $str = Text::reduce_slashes('foo//bar/baz'); // "foo/bar/baz"
*
* @param string $str string to reduce slashes of
* @return string
*/
public static function reduce_slashes($str)
{
return preg_replace('#(?<!:)//+#', '/', $str);
}
/**
* Replaces the given words with a string.
*
* // Displays "What the #####, man!"
* echo Text::censor('What the frick, man!', array(
* 'frick' => '#####',
* ));
*
* @param string $str phrase to replace words in
* @param array $badwords words to replace
* @param string $replacement replacement string
* @param boolean $replace_partial_words replace words across word boundries (space, period, etc)
* @return string
* @uses UTF8::strlen
*/
public static function censor($str, $badwords, $replacement = '#', $replace_partial_words = TRUE)
{
foreach ( (array) $badwords as $key => $badword)
{
$badwords[$key] = str_replace('\*', '\S*?', preg_quote( (string) $badword));
}
$regex = '('.implode('|', $badwords).')';
if ($replace_partial_words === FALSE)
{
// Just using \b isn't sufficient when we need to replace a badword that already contains word boundaries itself
$regex = '(?<=\b|\s|^)'.$regex.'(?=\b|\s|$)';
}
$regex = '!'.$regex.'!ui';
if (UTF8::strlen($replacement) == 1)
{
$regex .= 'e';
return preg_replace($regex, 'str_repeat($replacement, UTF8::strlen(\'$1\'))', $str);
}
return preg_replace($regex, $replacement, $str);
}
/**
* Finds the text that is similar between a set of words.
*
* $match = Text::similar(array('fred', 'fran', 'free'); // "fr"
*
* @param array $words words to find similar text of
* @return string
*/
public static function similar(array $words)
{
// First word is the word to match against
$word = current($words);
for ($i = 0, $max = strlen($word); $i < $max; ++$i)
{
foreach ($words as $w)
{
// Once a difference is found, break out of the loops
if ( ! isset($w[$i]) OR $w[$i] !== $word[$i])
break 2;
}
}
// Return the similar text
return substr($word, 0, $i);
}
/**
* Converts text email addresses and anchors into links. Existing links
* will not be altered.
*
* echo Text::auto_link($text);
*
* [!!] This method is not foolproof since it uses regex to parse HTML.
*
* @param string $text text to auto link
* @return string
* @uses Text::auto_link_urls
* @uses Text::auto_link_emails
*/
public static function auto_link($text)
{
// Auto link emails first to prevent problems with "www.domain.com@example.com"
return Text::auto_link_urls(Text::auto_link_emails($text));
}
/**
* Converts text anchors into links. Existing links will not be altered.
*
* echo Text::auto_link_urls($text);
*
* [!!] This method is not foolproof since it uses regex to parse HTML.
*
* @param string $text text to auto link
* @return string
* @uses HTML::anchor
*/
public static function auto_link_urls($text)
{
// Find and replace all http/https/ftp/ftps links that are not part of an existing html anchor
$text = preg_replace_callback('~\b(?<!href="|">)(?:ht|f)tps?://[^<\s]+(?:/|\b)~i', 'Text::_auto_link_urls_callback1', $text);
// Find and replace all naked www.links.com (without http://)
return preg_replace_callback('~\b(?<!://|">)www(?:\.[a-z0-9][-a-z0-9]*+)+\.[a-z]{2,6}[^<\s]*\b~i', 'Text::_auto_link_urls_callback2', $text);
}
protected static function _auto_link_urls_callback1($matches)
{
return HTML::anchor($matches[0]);
}
protected static function _auto_link_urls_callback2($matches)
{
return HTML::anchor('http://'.$matches[0], $matches[0]);
}
/**
* Converts text email addresses into links. Existing links will not
* be altered.
*
* echo Text::auto_link_emails($text);
*
* [!!] This method is not foolproof since it uses regex to parse HTML.
*
* @param string $text text to auto link
* @return string
* @uses HTML::mailto
*/
public static function auto_link_emails($text)
{
// Find and replace all email addresses that are not part of an existing html mailto anchor
// Note: The "58;" negative lookbehind prevents matching of existing encoded html mailto anchors
// The html entity for a colon (:) is &#58; or &#058; or &#0058; etc.
return preg_replace_callback('~\b(?<!href="mailto:|58;)(?!\.)[-+_a-z0-9.]++(?<!\.)@(?![-.])[-a-z0-9.]+(?<!\.)\.[a-z]{2,6}\b(?!</a>)~i', 'Text::_auto_link_emails_callback', $text);
}
protected static function _auto_link_emails_callback($matches)
{
return HTML::mailto($matches[0]);
}
/**
* Automatically applies "p" and "br" markup to text.
* Basically [nl2br](http://php.net/nl2br) on steroids.
*
* echo Text::auto_p($text);
*
* [!!] This method is not foolproof since it uses regex to parse HTML.
*
* @param string $str subject
* @param boolean $br convert single linebreaks to <br />
* @return string
*/
public static function auto_p($str, $br = TRUE)
{
// Trim whitespace
if (($str = trim($str)) === '')
return '';
// Standardize newlines
$str = str_replace(array("\r\n", "\r"), "\n", $str);
// Trim whitespace on each line
$str = preg_replace('~^[ \t]+~m', '', $str);
$str = preg_replace('~[ \t]+$~m', '', $str);
// The following regexes only need to be executed if the string contains html
if ($html_found = (strpos($str, '<') !== FALSE))
{
// Elements that should not be surrounded by p tags
$no_p = '(?:p|div|h[1-6r]|ul|ol|li|blockquote|d[dlt]|pre|t[dhr]|t(?:able|body|foot|head)|c(?:aption|olgroup)|form|s(?:elect|tyle)|a(?:ddress|rea)|ma(?:p|th))';
// Put at least two linebreaks before and after $no_p elements
$str = preg_replace('~^<'.$no_p.'[^>]*+>~im', "\n$0", $str);
$str = preg_replace('~</'.$no_p.'\s*+>$~im', "$0\n", $str);
}
// Do the <p> magic!
$str = '<p>'.trim($str).'</p>';
$str = preg_replace('~\n{2,}~', "</p>\n\n<p>", $str);
// The following regexes only need to be executed if the string contains html
if ($html_found !== FALSE)
{
// Remove p tags around $no_p elements
$str = preg_replace('~<p>(?=</?'.$no_p.'[^>]*+>)~i', '', $str);
$str = preg_replace('~(</?'.$no_p.'[^>]*+>)</p>~i', '$1', $str);
}
// Convert single linebreaks to <br />
if ($br === TRUE)
{
$str = preg_replace('~(?<!\n)\n(?!\n)~', "<br />\n", $str);
}
return $str;
}
/**
* Returns human readable sizes. Based on original functions written by
* [Aidan Lister](http://aidanlister.com/repos/v/function.size_readable.php)
* and [Quentin Zervaas](http://www.phpriot.com/d/code/strings/filesize-format/).
*
* echo Text::bytes(filesize($file));
*
* @param integer $bytes size in bytes
* @param string $force_unit a definitive unit
* @param string $format the return string format
* @param boolean $si whether to use SI prefixes or IEC
* @return string
*/
public static function bytes($bytes, $force_unit = NULL, $format = NULL, $si = TRUE)
{
// Format string
$format = ($format === NULL) ? '%01.2f %s' : (string) $format;
// IEC prefixes (binary)
if ($si == FALSE OR strpos($force_unit, 'i') !== FALSE)
{
$units = array('B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB');
$mod = 1024;
}
// SI prefixes (decimal)
else
{
$units = array('B', 'kB', 'MB', 'GB', 'TB', 'PB');
$mod = 1000;
}
// Determine unit to use
if (($power = array_search( (string) $force_unit, $units)) === FALSE)
{
$power = ($bytes > 0) ? floor(log($bytes, $mod)) : 0;
}
return sprintf($format, $bytes / pow($mod, $power), $units[$power]);
}
/**
* Format a number to human-readable text.
*
* // Display: one thousand and twenty-four
* echo Text::number(1024);
*
* // Display: five million, six hundred and thirty-two
* echo Text::number(5000632);
*
* @param integer $number number to format
* @return string
* @since 3.0.8
*/
public static function number($number)
{
// The number must always be an integer
$number = (int) $number;
// Uncompiled text version
$text = array();
// Last matched unit within the loop
$last_unit = NULL;
// The last matched item within the loop
$last_item = '';
foreach (Text::$units as $unit => $name)
{
if ($number / $unit >= 1)
{
// $value = the number of times the number is divisble by unit
$number -= $unit * ($value = (int) floor($number / $unit));
// Temporary var for textifying the current unit
$item = '';
if ($unit < 100)
{
if ($last_unit < 100 AND $last_unit >= 20)
{
$last_item .= '-'.$name;
}
else
{
$item = $name;
}
}
else
{
$item = Text::number($value).' '.$name;
}
// In the situation that we need to make a composite number (i.e. twenty-three)
// then we need to modify the previous entry
if (empty($item))
{
array_pop($text);
$item = $last_item;
}
$last_item = $text[] = $item;
$last_unit = $unit;
}
}
if (count($text) > 1)
{
$and = array_pop($text);
}
$text = implode(', ', $text);
if (isset($and))
{
$text .= ' and '.$and;
}
return $text;
}
/**
* Prevents [widow words](http://www.shauninman.com/archive/2006/08/22/widont_wordpress_plugin)
* by inserting a non-breaking space between the last two words.
*
* echo Text::widont($text);
*
* @param string $str text to remove widows from
* @return string
*/
public static function widont($str)
{
$str = rtrim($str);
$space = strrpos($str, ' ');
if ($space !== FALSE)
{
$str = substr($str, 0, $space).'&nbsp;'.substr($str, $space + 1);
}
return $str;
}
/**
* Returns information about the client user agent.
*
* // Returns "Chrome" when using Google Chrome
* $browser = Text::user_agent('browser');
*
* Multiple values can be returned at once by using an array:
*
* // Get the browser and platform with a single call
* $info = Text::user_agent(array('browser', 'platform'));
*
* When using an array for the value, an associative array will be returned.
*
* @param mixed $value array or string to return: browser, version, robot, mobile, platform
* @return mixed requested information, FALSE if nothing is found
* @uses Kohana::$config
*/
public static function user_agent($agent, $value)
{
if (is_array($value))
{
$data = array();
foreach ($value as $part)
{
// Add each part to the set
$data[$part] = Text::user_agent($agent, $part);
}
return $data;
}
if ($value === 'browser' OR $value == 'version')
{
// Extra data will be captured
$info = array();
// Load browsers
$browsers = Kohana::$config->load('user_agents')->browser;
foreach ($browsers as $search => $name)
{
if (stripos($agent, $search) !== FALSE)
{
// Set the browser name
$info['browser'] = $name;
if (preg_match('#'.preg_quote($search).'[^0-9.]*+([0-9.][0-9.a-z]*)#i', Request::$user_agent, $matches))
{
// Set the version number
$info['version'] = $matches[1];
}
else
{
// No version number found
$info['version'] = FALSE;
}
return $info[$value];
}
}
}
else
{
// Load the search group for this type
$group = Kohana::$config->load('user_agents')->$value;
foreach ($group as $search => $name)
{
if (stripos($agent, $search) !== FALSE)
{
// Set the value name
return $name;
}
}
}
// The value requested could not be found
return FALSE;
}
} // End text

View File

@@ -0,0 +1,213 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* URL helper class.
*
* @package Kohana
* @category Helpers
* @author Kohana Team
* @copyright (c) 2007-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
class Kohana_URL {
/**
* Gets the base URL to the application.
* To specify a protocol, provide the protocol as a string or request object.
* If a protocol is used, a complete URL will be generated using the
* `$_SERVER['HTTP_HOST']` variable.
*
* // Absolute URL path with no host or protocol
* echo URL::base();
*
* // Absolute URL path with host, https protocol and index.php if set
* echo URL::base('https', TRUE);
*
* // Absolute URL path with host and protocol from $request
* echo URL::base($request);
*
* @param mixed $protocol Protocol string, [Request], or boolean
* @param boolean $index Add index file to URL?
* @return string
* @uses Kohana::$index_file
* @uses Request::protocol()
*/
public static function base($protocol = NULL, $index = FALSE)
{
// Start with the configured base URL
$base_url = Kohana::$base_url;
if ($protocol === TRUE)
{
// Use the initial request to get the protocol
$protocol = Request::$initial;
}
if ($protocol instanceof Request)
{
if ( ! $protocol->secure())
{
// Use the current protocol
list($protocol) = explode('/', strtolower($protocol->protocol()));
}
else
{
$protocol = 'https';
}
}
if ( ! $protocol)
{
// Use the configured default protocol
$protocol = parse_url($base_url, PHP_URL_SCHEME);
}
if ($index === TRUE AND ! empty(Kohana::$index_file))
{
// Add the index file to the URL
$base_url .= Kohana::$index_file.'/';
}
if (is_string($protocol))
{
if ($port = parse_url($base_url, PHP_URL_PORT))
{
// Found a port, make it usable for the URL
$port = ':'.$port;
}
if ($domain = parse_url($base_url, PHP_URL_HOST))
{
// Remove everything but the path from the URL
$base_url = parse_url($base_url, PHP_URL_PATH);
}
else
{
// Attempt to use HTTP_HOST and fallback to SERVER_NAME
$domain = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME'];
}
// Add the protocol and domain to the base URL
$base_url = $protocol.'://'.$domain.$port.$base_url;
}
return $base_url;
}
/**
* Fetches an absolute site URL based on a URI segment.
*
* echo URL::site('foo/bar');
*
* @param string $uri Site URI to convert
* @param mixed $protocol Protocol string or [Request] class to use protocol from
* @param boolean $index Include the index_page in the URL
* @return string
* @uses URL::base
*/
public static function site($uri = '', $protocol = NULL, $index = TRUE)
{
// Chop off possible scheme, host, port, user and pass parts
$path = preg_replace('~^[-a-z0-9+.]++://[^/]++/?~', '', trim($uri, '/'));
if ( ! UTF8::is_ascii($path))
{
// Encode all non-ASCII characters, as per RFC 1738
$path = preg_replace_callback('~([^/]+)~', 'URL::_rawurlencode_callback', $path);
}
// Concat the URL
return URL::base($protocol, $index).$path;
}
/**
* Callback used for encoding all non-ASCII characters, as per RFC 1738
* Used by URL::site()
*
* @param array $matches Array of matches from preg_replace_callback()
* @return string Encoded string
*/
protected static function _rawurlencode_callback($matches)
{
return rawurlencode($matches[0]);
}
/**
* Merges the current GET parameters with an array of new or overloaded
* parameters and returns the resulting query string.
*
* // Returns "?sort=title&limit=10" combined with any existing GET values
* $query = URL::query(array('sort' => 'title', 'limit' => 10));
*
* Typically you would use this when you are sorting query results,
* or something similar.
*
* [!!] Parameters with a NULL value are left out.
*
* @param array $params Array of GET parameters
* @param boolean $use_get Include current request GET parameters
* @return string
*/
public static function query(array $params = NULL, $use_get = TRUE)
{
if ($use_get)
{
if ($params === NULL)
{
// Use only the current parameters
$params = $_GET;
}
else
{
// Merge the current and new parameters
$params = Arr::merge($_GET, $params);
}
}
if (empty($params))
{
// No query parameters
return '';
}
// Note: http_build_query returns an empty string for a params array with only NULL values
$query = http_build_query($params, '', '&');
// Don't prepend '?' to an empty string
return ($query === '') ? '' : ('?'.$query);
}
/**
* Convert a phrase to a URL-safe title.
*
* echo URL::title('My Blog Post'); // "my-blog-post"
*
* @param string $title Phrase to convert
* @param string $separator Word separator (any single character)
* @param boolean $ascii_only Transliterate to ASCII?
* @return string
* @uses UTF8::transliterate_to_ascii
*/
public static function title($title, $separator = '-', $ascii_only = FALSE)
{
if ($ascii_only === TRUE)
{
// Transliterate non-ASCII characters
$title = UTF8::transliterate_to_ascii($title);
// Remove all characters that are not the separator, a-z, 0-9, or whitespace
$title = preg_replace('![^'.preg_quote($separator).'a-z0-9\s]+!', '', strtolower($title));
}
else
{
// Remove all characters that are not the separator, letters, numbers, or whitespace
$title = preg_replace('![^'.preg_quote($separator).'\pL\pN\s]+!u', '', UTF8::strtolower($title));
}
// Replace all separator characters and whitespace by a single separator
$title = preg_replace('!['.preg_quote($separator).'\s]+!u', $separator, $title);
// Trim separators from the beginning and end
return trim($title, $separator);
}
} // End url

View File

@@ -0,0 +1,767 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* A port of [phputf8](http://phputf8.sourceforge.net/) to a unified set
* of files. Provides multi-byte aware replacement string functions.
*
* For UTF-8 support to work correctly, the following requirements must be met:
*
* - PCRE needs to be compiled with UTF-8 support (--enable-utf8)
* - Support for [Unicode properties](http://php.net/manual/reference.pcre.pattern.modifiers.php)
* is highly recommended (--enable-unicode-properties)
* - UTF-8 conversion will be much more reliable if the
* [iconv extension](http://php.net/iconv) is loaded
* - The [mbstring extension](http://php.net/mbstring) is highly recommended,
* but must not be overloading string functions
*
* [!!] This file is licensed differently from the rest of Kohana. As a port of
* [phputf8](http://phputf8.sourceforge.net/), this file is released under the LGPL.
*
* @package Kohana
* @category Base
* @author Kohana Team
* @copyright (c) 2007-2012 Kohana Team
* @copyright (c) 2005 Harry Fuecks
* @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
*/
class Kohana_UTF8 {
/**
* @var boolean Does the server support UTF-8 natively?
*/
public static $server_utf8 = NULL;
/**
* @var array List of called methods that have had their required file included.
*/
public static $called = array();
/**
* Recursively cleans arrays, objects, and strings. Removes ASCII control
* codes and converts to the requested charset while silently discarding
* incompatible characters.
*
* UTF8::clean($_GET); // Clean GET data
*
* [!!] This method requires [Iconv](http://php.net/iconv)
*
* @param mixed $var variable to clean
* @param string $charset character set, defaults to Kohana::$charset
* @return mixed
* @uses UTF8::strip_ascii_ctrl
* @uses UTF8::is_ascii
*/
public static function clean($var, $charset = NULL)
{
if ( ! $charset)
{
// Use the application character set
$charset = Kohana::$charset;
}
if (is_array($var) OR is_object($var))
{
foreach ($var as $key => $val)
{
// Recursion!
$var[self::clean($key)] = self::clean($val);
}
}
elseif (is_string($var) AND $var !== '')
{
// Remove control characters
$var = self::strip_ascii_ctrl($var);
if ( ! self::is_ascii($var))
{
// Disable notices
$error_reporting = error_reporting(~E_NOTICE);
// iconv is expensive, so it is only used when needed
$var = iconv($charset, $charset.'//IGNORE', $var);
// Turn notices back on
error_reporting($error_reporting);
}
}
return $var;
}
/**
* Tests whether a string contains only 7-bit ASCII bytes. This is used to
* determine when to use native functions or UTF-8 functions.
*
* $ascii = UTF8::is_ascii($str);
*
* @param mixed $str string or array of strings to check
* @return boolean
*/
public static function is_ascii($str)
{
if (is_array($str))
{
$str = implode($str);
}
return ! preg_match('/[^\x00-\x7F]/S', $str);
}
/**
* Strips out device control codes in the ASCII range.
*
* $str = UTF8::strip_ascii_ctrl($str);
*
* @param string $str string to clean
* @return string
*/
public static function strip_ascii_ctrl($str)
{
return preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S', '', $str);
}
/**
* Strips out all non-7bit ASCII bytes.
*
* $str = UTF8::strip_non_ascii($str);
*
* @param string $str string to clean
* @return string
*/
public static function strip_non_ascii($str)
{
return preg_replace('/[^\x00-\x7F]+/S', '', $str);
}
/**
* Replaces special/accented UTF-8 characters by ASCII-7 "equivalents".
*
* $ascii = UTF8::transliterate_to_ascii($utf8);
*
* @author Andreas Gohr <andi@splitbrain.org>
* @param string $str string to transliterate
* @param integer $case -1 lowercase only, +1 uppercase only, 0 both cases
* @return string
*/
public static function transliterate_to_ascii($str, $case = 0)
{
if ( ! isset(self::$called[__FUNCTION__]))
{
require Kohana::find_file('utf8', __FUNCTION__);
// Function has been called
self::$called[__FUNCTION__] = TRUE;
}
return _transliterate_to_ascii($str, $case);
}
/**
* Returns the length of the given string. This is a UTF8-aware version
* of [strlen](http://php.net/strlen).
*
* $length = UTF8::strlen($str);
*
* @param string $str string being measured for length
* @return integer
* @uses UTF8::$server_utf8
*/
public static function strlen($str)
{
if (UTF8::$server_utf8)
return mb_strlen($str, Kohana::$charset);
if ( ! isset(self::$called[__FUNCTION__]))
{
require Kohana::find_file('utf8', __FUNCTION__);
// Function has been called
self::$called[__FUNCTION__] = TRUE;
}
return _strlen($str);
}
/**
* Finds position of first occurrence of a UTF-8 string. This is a
* UTF8-aware version of [strpos](http://php.net/strpos).
*
* $position = UTF8::strpos($str, $search);
*
* @author Harry Fuecks <hfuecks@gmail.com>
* @param string $str haystack
* @param string $search needle
* @param integer $offset offset from which character in haystack to start searching
* @return integer position of needle
* @return boolean FALSE if the needle is not found
* @uses UTF8::$server_utf8
*/
public static function strpos($str, $search, $offset = 0)
{
if (UTF8::$server_utf8)
return mb_strpos($str, $search, $offset, Kohana::$charset);
if ( ! isset(self::$called[__FUNCTION__]))
{
require Kohana::find_file('utf8', __FUNCTION__);
// Function has been called
self::$called[__FUNCTION__] = TRUE;
}
return _strpos($str, $search, $offset);
}
/**
* Finds position of last occurrence of a char in a UTF-8 string. This is
* a UTF8-aware version of [strrpos](http://php.net/strrpos).
*
* $position = UTF8::strrpos($str, $search);
*
* @author Harry Fuecks <hfuecks@gmail.com>
* @param string $str haystack
* @param string $search needle
* @param integer $offset offset from which character in haystack to start searching
* @return integer position of needle
* @return boolean FALSE if the needle is not found
* @uses UTF8::$server_utf8
*/
public static function strrpos($str, $search, $offset = 0)
{
if (UTF8::$server_utf8)
return mb_strrpos($str, $search, $offset, Kohana::$charset);
if ( ! isset(self::$called[__FUNCTION__]))
{
require Kohana::find_file('utf8', __FUNCTION__);
// Function has been called
self::$called[__FUNCTION__] = TRUE;
}
return _strrpos($str, $search, $offset);
}
/**
* Returns part of a UTF-8 string. This is a UTF8-aware version
* of [substr](http://php.net/substr).
*
* $sub = UTF8::substr($str, $offset);
*
* @author Chris Smith <chris@jalakai.co.uk>
* @param string $str input string
* @param integer $offset offset
* @param integer $length length limit
* @return string
* @uses UTF8::$server_utf8
* @uses Kohana::$charset
*/
public static function substr($str, $offset, $length = NULL)
{
if (UTF8::$server_utf8)
return ($length === NULL)
? mb_substr($str, $offset, mb_strlen($str), Kohana::$charset)
: mb_substr($str, $offset, $length, Kohana::$charset);
if ( ! isset(self::$called[__FUNCTION__]))
{
require Kohana::find_file('utf8', __FUNCTION__);
// Function has been called
self::$called[__FUNCTION__] = TRUE;
}
return _substr($str, $offset, $length);
}
/**
* Replaces text within a portion of a UTF-8 string. This is a UTF8-aware
* version of [substr_replace](http://php.net/substr_replace).
*
* $str = UTF8::substr_replace($str, $replacement, $offset);
*
* @author Harry Fuecks <hfuecks@gmail.com>
* @param string $str input string
* @param string $replacement replacement string
* @param integer $offset offset
* @return string
*/
public static function substr_replace($str, $replacement, $offset, $length = NULL)
{
if ( ! isset(self::$called[__FUNCTION__]))
{
require Kohana::find_file('utf8', __FUNCTION__);
// Function has been called
self::$called[__FUNCTION__] = TRUE;
}
return _substr_replace($str, $replacement, $offset, $length);
}
/**
* Makes a UTF-8 string lowercase. This is a UTF8-aware version
* of [strtolower](http://php.net/strtolower).
*
* $str = UTF8::strtolower($str);
*
* @author Andreas Gohr <andi@splitbrain.org>
* @param string $str mixed case string
* @return string
* @uses UTF8::$server_utf8
*/
public static function strtolower($str)
{
if (UTF8::$server_utf8)
return mb_strtolower($str, Kohana::$charset);
if ( ! isset(self::$called[__FUNCTION__]))
{
require Kohana::find_file('utf8', __FUNCTION__);
// Function has been called
self::$called[__FUNCTION__] = TRUE;
}
return _strtolower($str);
}
/**
* Makes a UTF-8 string uppercase. This is a UTF8-aware version
* of [strtoupper](http://php.net/strtoupper).
*
* @author Andreas Gohr <andi@splitbrain.org>
* @param string $str mixed case string
* @return string
* @uses UTF8::$server_utf8
* @uses Kohana::$charset
*/
public static function strtoupper($str)
{
if (UTF8::$server_utf8)
return mb_strtoupper($str, Kohana::$charset);
if ( ! isset(self::$called[__FUNCTION__]))
{
require Kohana::find_file('utf8', __FUNCTION__);
// Function has been called
self::$called[__FUNCTION__] = TRUE;
}
return _strtoupper($str);
}
/**
* Makes a UTF-8 string's first character uppercase. This is a UTF8-aware
* version of [ucfirst](http://php.net/ucfirst).
*
* $str = UTF8::ucfirst($str);
*
* @author Harry Fuecks <hfuecks@gmail.com>
* @param string $str mixed case string
* @return string
*/
public static function ucfirst($str)
{
if ( ! isset(self::$called[__FUNCTION__]))
{
require Kohana::find_file('utf8', __FUNCTION__);
// Function has been called
self::$called[__FUNCTION__] = TRUE;
}
return _ucfirst($str);
}
/**
* Makes the first character of every word in a UTF-8 string uppercase.
* This is a UTF8-aware version of [ucwords](http://php.net/ucwords).
*
* $str = UTF8::ucwords($str);
*
* @author Harry Fuecks <hfuecks@gmail.com>
* @param string $str mixed case string
* @return string
* @uses UTF8::$server_utf8
*/
public static function ucwords($str)
{
if ( ! isset(self::$called[__FUNCTION__]))
{
require Kohana::find_file('utf8', __FUNCTION__);
// Function has been called
self::$called[__FUNCTION__] = TRUE;
}
return _ucwords($str);
}
/**
* Case-insensitive UTF-8 string comparison. This is a UTF8-aware version
* of [strcasecmp](http://php.net/strcasecmp).
*
* $compare = UTF8::strcasecmp($str1, $str2);
*
* @author Harry Fuecks <hfuecks@gmail.com>
* @param string $str1 string to compare
* @param string $str2 string to compare
* @return integer less than 0 if str1 is less than str2
* @return integer greater than 0 if str1 is greater than str2
* @return integer 0 if they are equal
*/
public static function strcasecmp($str1, $str2)
{
if ( ! isset(self::$called[__FUNCTION__]))
{
require Kohana::find_file('utf8', __FUNCTION__);
// Function has been called
self::$called[__FUNCTION__] = TRUE;
}
return _strcasecmp($str1, $str2);
}
/**
* Returns a string or an array with all occurrences of search in subject
* (ignoring case) and replaced with the given replace value. This is a
* UTF8-aware version of [str_ireplace](http://php.net/str_ireplace).
*
* [!!] This function is very slow compared to the native version. Avoid
* using it when possible.
*
* @author Harry Fuecks <hfuecks@gmail.com
* @param string|array $search text to replace
* @param string|array $replace replacement text
* @param string|array $str subject text
* @param integer $count number of matched and replaced needles will be returned via this parameter which is passed by reference
* @return string if the input was a string
* @return array if the input was an array
*/
public static function str_ireplace($search, $replace, $str, & $count = NULL)
{
if ( ! isset(self::$called[__FUNCTION__]))
{
require Kohana::find_file('utf8', __FUNCTION__);
// Function has been called
self::$called[__FUNCTION__] = TRUE;
}
return _str_ireplace($search, $replace, $str, $count);
}
/**
* Case-insenstive UTF-8 version of strstr. Returns all of input string
* from the first occurrence of needle to the end. This is a UTF8-aware
* version of [stristr](http://php.net/stristr).
*
* $found = UTF8::stristr($str, $search);
*
* @author Harry Fuecks <hfuecks@gmail.com>
* @param string $str input string
* @param string $search needle
* @return string matched substring if found
* @return FALSE if the substring was not found
*/
public static function stristr($str, $search)
{
if ( ! isset(self::$called[__FUNCTION__]))
{
require Kohana::find_file('utf8', __FUNCTION__);
// Function has been called
self::$called[__FUNCTION__] = TRUE;
}
return _stristr($str, $search);
}
/**
* Finds the length of the initial segment matching mask. This is a
* UTF8-aware version of [strspn](http://php.net/strspn).
*
* $found = UTF8::strspn($str, $mask);
*
* @author Harry Fuecks <hfuecks@gmail.com>
* @param string $str input string
* @param string $mask mask for search
* @param integer $offset start position of the string to examine
* @param integer $length length of the string to examine
* @return integer length of the initial segment that contains characters in the mask
*/
public static function strspn($str, $mask, $offset = NULL, $length = NULL)
{
if ( ! isset(self::$called[__FUNCTION__]))
{
require Kohana::find_file('utf8', __FUNCTION__);
// Function has been called
self::$called[__FUNCTION__] = TRUE;
}
return _strspn($str, $mask, $offset, $length);
}
/**
* Finds the length of the initial segment not matching mask. This is a
* UTF8-aware version of [strcspn](http://php.net/strcspn).
*
* $found = UTF8::strcspn($str, $mask);
*
* @author Harry Fuecks <hfuecks@gmail.com>
* @param string $str input string
* @param string $mask mask for search
* @param integer $offset start position of the string to examine
* @param integer $length length of the string to examine
* @return integer length of the initial segment that contains characters not in the mask
*/
public static function strcspn($str, $mask, $offset = NULL, $length = NULL)
{
if ( ! isset(self::$called[__FUNCTION__]))
{
require Kohana::find_file('utf8', __FUNCTION__);
// Function has been called
self::$called[__FUNCTION__] = TRUE;
}
return _strcspn($str, $mask, $offset, $length);
}
/**
* Pads a UTF-8 string to a certain length with another string. This is a
* UTF8-aware version of [str_pad](http://php.net/str_pad).
*
* $str = UTF8::str_pad($str, $length);
*
* @author Harry Fuecks <hfuecks@gmail.com>
* @param string $str input string
* @param integer $final_str_length desired string length after padding
* @param string $pad_str string to use as padding
* @param string $pad_type padding type: STR_PAD_RIGHT, STR_PAD_LEFT, or STR_PAD_BOTH
* @return string
*/
public static function str_pad($str, $final_str_length, $pad_str = ' ', $pad_type = STR_PAD_RIGHT)
{
if ( ! isset(self::$called[__FUNCTION__]))
{
require Kohana::find_file('utf8', __FUNCTION__);
// Function has been called
self::$called[__FUNCTION__] = TRUE;
}
return _str_pad($str, $final_str_length, $pad_str, $pad_type);
}
/**
* Converts a UTF-8 string to an array. This is a UTF8-aware version of
* [str_split](http://php.net/str_split).
*
* $array = UTF8::str_split($str);
*
* @author Harry Fuecks <hfuecks@gmail.com>
* @param string $str input string
* @param integer $split_length maximum length of each chunk
* @return array
*/
public static function str_split($str, $split_length = 1)
{
if ( ! isset(self::$called[__FUNCTION__]))
{
require Kohana::find_file('utf8', __FUNCTION__);
// Function has been called
self::$called[__FUNCTION__] = TRUE;
}
return _str_split($str, $split_length);
}
/**
* Reverses a UTF-8 string. This is a UTF8-aware version of [strrev](http://php.net/strrev).
*
* $str = UTF8::strrev($str);
*
* @author Harry Fuecks <hfuecks@gmail.com>
* @param string $str string to be reversed
* @return string
*/
public static function strrev($str)
{
if ( ! isset(self::$called[__FUNCTION__]))
{
require Kohana::find_file('utf8', __FUNCTION__);
// Function has been called
self::$called[__FUNCTION__] = TRUE;
}
return _strrev($str);
}
/**
* Strips whitespace (or other UTF-8 characters) from the beginning and
* end of a string. This is a UTF8-aware version of [trim](http://php.net/trim).
*
* $str = UTF8::trim($str);
*
* @author Andreas Gohr <andi@splitbrain.org>
* @param string $str input string
* @param string $charlist string of characters to remove
* @return string
*/
public static function trim($str, $charlist = NULL)
{
if ( ! isset(self::$called[__FUNCTION__]))
{
require Kohana::find_file('utf8', __FUNCTION__);
// Function has been called
self::$called[__FUNCTION__] = TRUE;
}
return _trim($str, $charlist);
}
/**
* Strips whitespace (or other UTF-8 characters) from the beginning of
* a string. This is a UTF8-aware version of [ltrim](http://php.net/ltrim).
*
* $str = UTF8::ltrim($str);
*
* @author Andreas Gohr <andi@splitbrain.org>
* @param string $str input string
* @param string $charlist string of characters to remove
* @return string
*/
public static function ltrim($str, $charlist = NULL)
{
if ( ! isset(self::$called[__FUNCTION__]))
{
require Kohana::find_file('utf8', __FUNCTION__);
// Function has been called
self::$called[__FUNCTION__] = TRUE;
}
return _ltrim($str, $charlist);
}
/**
* Strips whitespace (or other UTF-8 characters) from the end of a string.
* This is a UTF8-aware version of [rtrim](http://php.net/rtrim).
*
* $str = UTF8::rtrim($str);
*
* @author Andreas Gohr <andi@splitbrain.org>
* @param string $str input string
* @param string $charlist string of characters to remove
* @return string
*/
public static function rtrim($str, $charlist = NULL)
{
if ( ! isset(self::$called[__FUNCTION__]))
{
require Kohana::find_file('utf8', __FUNCTION__);
// Function has been called
self::$called[__FUNCTION__] = TRUE;
}
return _rtrim($str, $charlist);
}
/**
* Returns the unicode ordinal for a character. This is a UTF8-aware
* version of [ord](http://php.net/ord).
*
* $digit = UTF8::ord($character);
*
* @author Harry Fuecks <hfuecks@gmail.com>
* @param string $chr UTF-8 encoded character
* @return integer
*/
public static function ord($chr)
{
if ( ! isset(self::$called[__FUNCTION__]))
{
require Kohana::find_file('utf8', __FUNCTION__);
// Function has been called
self::$called[__FUNCTION__] = TRUE;
}
return _ord($chr);
}
/**
* Takes an UTF-8 string and returns an array of ints representing the Unicode characters.
* Astral planes are supported i.e. the ints in the output can be > 0xFFFF.
* Occurrences of the BOM are ignored. Surrogates are not allowed.
*
* $array = UTF8::to_unicode($str);
*
* The Original Code is Mozilla Communicator client code.
* The Initial Developer of the Original Code is Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1998 the Initial Developer.
* Ported to PHP by Henri Sivonen <hsivonen@iki.fi>, see <http://hsivonen.iki.fi/php-utf8/>
* Slight modifications to fit with phputf8 library by Harry Fuecks <hfuecks@gmail.com>
*
* @param string $str UTF-8 encoded string
* @return array unicode code points
* @return FALSE if the string is invalid
*/
public static function to_unicode($str)
{
if ( ! isset(self::$called[__FUNCTION__]))
{
require Kohana::find_file('utf8', __FUNCTION__);
// Function has been called
self::$called[__FUNCTION__] = TRUE;
}
return _to_unicode($str);
}
/**
* Takes an array of ints representing the Unicode characters and returns a UTF-8 string.
* Astral planes are supported i.e. the ints in the input can be > 0xFFFF.
* Occurrances of the BOM are ignored. Surrogates are not allowed.
*
* $str = UTF8::to_unicode($array);
*
* The Original Code is Mozilla Communicator client code.
* The Initial Developer of the Original Code is Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1998 the Initial Developer.
* Ported to PHP by Henri Sivonen <hsivonen@iki.fi>, see http://hsivonen.iki.fi/php-utf8/
* Slight modifications to fit with phputf8 library by Harry Fuecks <hfuecks@gmail.com>.
*
* @param array $str unicode code points representing a string
* @return string utf8 string of characters
* @return boolean FALSE if a code point cannot be found
*/
public static function from_unicode($arr)
{
if ( ! isset(self::$called[__FUNCTION__]))
{
require Kohana::find_file('utf8', __FUNCTION__);
// Function has been called
self::$called[__FUNCTION__] = TRUE;
}
return _from_unicode($arr);
}
} // End UTF8
if (Kohana_UTF8::$server_utf8 === NULL)
{
// Determine if this server supports UTF-8 natively
Kohana_UTF8::$server_utf8 = extension_loaded('mbstring');
}

View File

@@ -0,0 +1,9 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* @package Kohana
* @category Exceptions
* @author Kohana Team
* @copyright (c) 2009-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
class Kohana_UTF8_Exception extends Kohana_Exception {}

View File

@@ -0,0 +1,256 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* Upload helper class for working with uploaded files and [Validation].
*
* $array = Validation::factory($_FILES);
*
* [!!] Remember to define your form with "enctype=multipart/form-data" or file
* uploading will not work!
*
* The following configuration properties can be set:
*
* - [Upload::$remove_spaces]
* - [Upload::$default_directory]
*
* @package Kohana
* @category Helpers
* @author Kohana Team
* @copyright (c) 2007-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
class Kohana_Upload {
/**
* @var boolean remove spaces in uploaded files
*/
public static $remove_spaces = TRUE;
/**
* @var string default upload directory
*/
public static $default_directory = 'upload';
/**
* Save an uploaded file to a new location. If no filename is provided,
* the original filename will be used, with a unique prefix added.
*
* This method should be used after validating the $_FILES array:
*
* if ($array->check())
* {
* // Upload is valid, save it
* Upload::save($array['file']);
* }
*
* @param array $file uploaded file data
* @param string $filename new filename
* @param string $directory new directory
* @param integer $chmod chmod mask
* @return string on success, full path to new file
* @return FALSE on failure
*/
public static function save(array $file, $filename = NULL, $directory = NULL, $chmod = 0644)
{
if ( ! isset($file['tmp_name']) OR ! is_uploaded_file($file['tmp_name']))
{
// Ignore corrupted uploads
return FALSE;
}
if ($filename === NULL)
{
// Use the default filename, with a timestamp pre-pended
$filename = uniqid().$file['name'];
}
if (Upload::$remove_spaces === TRUE)
{
// Remove spaces from the filename
$filename = preg_replace('/\s+/u', '_', $filename);
}
if ($directory === NULL)
{
// Use the pre-configured upload directory
$directory = Upload::$default_directory;
}
if ( ! is_dir($directory) OR ! is_writable(realpath($directory)))
{
throw new Kohana_Exception('Directory :dir must be writable',
array(':dir' => Debug::path($directory)));
}
// Make the filename into a complete path
$filename = realpath($directory).DIRECTORY_SEPARATOR.$filename;
if (move_uploaded_file($file['tmp_name'], $filename))
{
if ($chmod !== FALSE)
{
// Set permissions on filename
chmod($filename, $chmod);
}
// Return new file path
return $filename;
}
return FALSE;
}
/**
* Tests if upload data is valid, even if no file was uploaded. If you
* _do_ require a file to be uploaded, add the [Upload::not_empty] rule
* before this rule.
*
* $array->rule('file', 'Upload::valid')
*
* @param array $file $_FILES item
* @return bool
*/
public static function valid($file)
{
return (isset($file['error'])
AND isset($file['name'])
AND isset($file['type'])
AND isset($file['tmp_name'])
AND isset($file['size']));
}
/**
* Tests if a successful upload has been made.
*
* $array->rule('file', 'Upload::not_empty');
*
* @param array $file $_FILES item
* @return bool
*/
public static function not_empty(array $file)
{
return (isset($file['error'])
AND isset($file['tmp_name'])
AND $file['error'] === UPLOAD_ERR_OK
AND is_uploaded_file($file['tmp_name']));
}
/**
* Test if an uploaded file is an allowed file type, by extension.
*
* $array->rule('file', 'Upload::type', array(':value', array('jpg', 'png', 'gif')));
*
* @param array $file $_FILES item
* @param array $allowed allowed file extensions
* @return bool
*/
public static function type(array $file, array $allowed)
{
if ($file['error'] !== UPLOAD_ERR_OK)
return TRUE;
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
return in_array($ext, $allowed);
}
/**
* Validation rule to test if an uploaded file is allowed by file size.
* File sizes are defined as: SB, where S is the size (1, 8.5, 300, etc.)
* and B is the byte unit (K, MiB, GB, etc.). All valid byte units are
* defined in Num::$byte_units
*
* $array->rule('file', 'Upload::size', array(':value', '1M'))
* $array->rule('file', 'Upload::size', array(':value', '2.5KiB'))
*
* @param array $file $_FILES item
* @param string $size maximum file size allowed
* @return bool
*/
public static function size(array $file, $size)
{
if ($file['error'] === UPLOAD_ERR_INI_SIZE)
{
// Upload is larger than PHP allowed size (upload_max_filesize)
return FALSE;
}
if ($file['error'] !== UPLOAD_ERR_OK)
{
// The upload failed, no size to check
return TRUE;
}
// Convert the provided size to bytes for comparison
$size = Num::bytes($size);
// Test that the file is under or equal to the max size
return ($file['size'] <= $size);
}
/**
* Validation rule to test if an upload is an image and, optionally, is the correct size.
*
* // The "image" file must be an image
* $array->rule('image', 'Upload::image')
*
* // The "photo" file has a maximum size of 640x480 pixels
* $array->rule('photo', 'Upload::image', array(':value', 640, 480));
*
* // The "image" file must be exactly 100x100 pixels
* $array->rule('image', 'Upload::image', array(':value', 100, 100, TRUE));
*
*
* @param array $file $_FILES item
* @param integer $max_width maximum width of image
* @param integer $max_height maximum height of image
* @param boolean $exact match width and height exactly?
* @return boolean
*/
public static function image(array $file, $max_width = NULL, $max_height = NULL, $exact = FALSE)
{
if (Upload::not_empty($file))
{
try
{
// Get the width and height from the uploaded image
list($width, $height) = getimagesize($file['tmp_name']);
}
catch (ErrorException $e)
{
// Ignore read errors
}
if (empty($width) OR empty($height))
{
// Cannot get image size, cannot validate
return FALSE;
}
if ( ! $max_width)
{
// No limit, use the image width
$max_width = $width;
}
if ( ! $max_height)
{
// No limit, use the image height
$max_height = $height;
}
if ($exact)
{
// Check if dimensions match exactly
return ($width === $max_width AND $height === $max_height);
}
else
{
// Check if size is within maximum dimensions
return ($width <= $max_width AND $height <= $max_height);
}
}
return FALSE;
}
} // End upload

View File

@@ -0,0 +1,551 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* Validation rules.
*
* @package Kohana
* @category Security
* @author Kohana Team
* @copyright (c) 2008-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
class Kohana_Valid {
/**
* Checks if a field is not empty.
*
* @return boolean
*/
public static function not_empty($value)
{
if (is_object($value) AND $value instanceof ArrayObject)
{
// Get the array from the ArrayObject
$value = $value->getArrayCopy();
}
// Value cannot be NULL, FALSE, '', or an empty array
return ! in_array($value, array(NULL, FALSE, '', array()), TRUE);
}
/**
* Checks a field against a regular expression.
*
* @param string $value value
* @param string $expression regular expression to match (including delimiters)
* @return boolean
*/
public static function regex($value, $expression)
{
return (bool) preg_match($expression, (string) $value);
}
/**
* Checks that a field is long enough.
*
* @param string $value value
* @param integer $length minimum length required
* @return boolean
*/
public static function min_length($value, $length)
{
return UTF8::strlen($value) >= $length;
}
/**
* Checks that a field is short enough.
*
* @param string $value value
* @param integer $length maximum length required
* @return boolean
*/
public static function max_length($value, $length)
{
return UTF8::strlen($value) <= $length;
}
/**
* Checks that a field is exactly the right length.
*
* @param string $value value
* @param integer|array $length exact length required, or array of valid lengths
* @return boolean
*/
public static function exact_length($value, $length)
{
if (is_array($length))
{
foreach ($length as $strlen)
{
if (UTF8::strlen($value) === $strlen)
return TRUE;
}
return FALSE;
}
return UTF8::strlen($value) === $length;
}
/**
* Checks that a field is exactly the value required.
*
* @param string $value value
* @param string $required required value
* @return boolean
*/
public static function equals($value, $required)
{
return ($value === $required);
}
/**
* Check an email address for correct format.
*
* @link http://www.iamcal.com/publish/articles/php/parsing_email/
* @link http://www.w3.org/Protocols/rfc822/
*
* @param string $email email address
* @param boolean $strict strict RFC compatibility
* @return boolean
*/
public static function email($email, $strict = FALSE)
{
if (UTF8::strlen($email) > 254)
{
return FALSE;
}
if ($strict === TRUE)
{
$qtext = '[^\\x0d\\x22\\x5c\\x80-\\xff]';
$dtext = '[^\\x0d\\x5b-\\x5d\\x80-\\xff]';
$atom = '[^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-\\x3c\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+';
$pair = '\\x5c[\\x00-\\x7f]';
$domain_literal = "\\x5b($dtext|$pair)*\\x5d";
$quoted_string = "\\x22($qtext|$pair)*\\x22";
$sub_domain = "($atom|$domain_literal)";
$word = "($atom|$quoted_string)";
$domain = "$sub_domain(\\x2e$sub_domain)*";
$local_part = "$word(\\x2e$word)*";
$expression = "/^$local_part\\x40$domain$/D";
}
else
{
$expression = '/^[-_a-z0-9\'+*$^&%=~!?{}]++(?:\.[-_a-z0-9\'+*$^&%=~!?{}]+)*+@(?:(?![-.])[-a-z0-9.]+(?<![-.])\.[a-z]{2,6}|\d{1,3}(?:\.\d{1,3}){3})$/iD';
}
return (bool) preg_match($expression, (string) $email);
}
/**
* Validate the domain of an email address by checking if the domain has a
* valid MX record.
*
* @link http://php.net/checkdnsrr not added to Windows until PHP 5.3.0
*
* @param string $email email address
* @return boolean
*/
public static function email_domain($email)
{
if ( ! Valid::not_empty($email))
return FALSE; // Empty fields cause issues with checkdnsrr()
// Check if the email domain has a valid MX record
return (bool) checkdnsrr(preg_replace('/^[^@]++@/', '', $email), 'MX');
}
/**
* Validate a URL.
*
* @param string $url URL
* @return boolean
*/
public static function url($url)
{
// Based on http://www.apps.ietf.org/rfc/rfc1738.html#sec-5
if ( ! preg_match(
'~^
# scheme
[-a-z0-9+.]++://
# username:password (optional)
(?:
[-a-z0-9$_.+!*\'(),;?&=%]++ # username
(?::[-a-z0-9$_.+!*\'(),;?&=%]++)? # password (optional)
@
)?
(?:
# ip address
\d{1,3}+(?:\.\d{1,3}+){3}+
| # or
# hostname (captured)
(
(?!-)[-a-z0-9]{1,63}+(?<!-)
(?:\.(?!-)[-a-z0-9]{1,63}+(?<!-)){0,126}+
)
)
# port (optional)
(?::\d{1,5}+)?
# path (optional)
(?:/.*)?
$~iDx', $url, $matches))
return FALSE;
// We matched an IP address
if ( ! isset($matches[1]))
return TRUE;
// Check maximum length of the whole hostname
// http://en.wikipedia.org/wiki/Domain_name#cite_note-0
if (strlen($matches[1]) > 253)
return FALSE;
// An extra check for the top level domain
// It must start with a letter
$tld = ltrim(substr($matches[1], (int) strrpos($matches[1], '.')), '.');
return ctype_alpha($tld[0]);
}
/**
* Validate an IP.
*
* @param string $ip IP address
* @param boolean $allow_private allow private IP networks
* @return boolean
*/
public static function ip($ip, $allow_private = TRUE)
{
// Do not allow reserved addresses
$flags = FILTER_FLAG_NO_RES_RANGE;
if ($allow_private === FALSE)
{
// Do not allow private or reserved addresses
$flags = $flags | FILTER_FLAG_NO_PRIV_RANGE;
}
return (bool) filter_var($ip, FILTER_VALIDATE_IP, $flags);
}
/**
* Validates a credit card number, with a Luhn check if possible.
*
* @param integer $number credit card number
* @param string|array $type card type, or an array of card types
* @return boolean
* @uses Valid::luhn
*/
public static function credit_card($number, $type = NULL)
{
// Remove all non-digit characters from the number
if (($number = preg_replace('/\D+/', '', $number)) === '')
return FALSE;
if ($type == NULL)
{
// Use the default type
$type = 'default';
}
elseif (is_array($type))
{
foreach ($type as $t)
{
// Test each type for validity
if (Valid::credit_card($number, $t))
return TRUE;
}
return FALSE;
}
$cards = Kohana::$config->load('credit_cards');
// Check card type
$type = strtolower($type);
if ( ! isset($cards[$type]))
return FALSE;
// Check card number length
$length = strlen($number);
// Validate the card length by the card type
if ( ! in_array($length, preg_split('/\D+/', $cards[$type]['length'])))
return FALSE;
// Check card number prefix
if ( ! preg_match('/^'.$cards[$type]['prefix'].'/', $number))
return FALSE;
// No Luhn check required
if ($cards[$type]['luhn'] == FALSE)
return TRUE;
return Valid::luhn($number);
}
/**
* Validate a number against the [Luhn](http://en.wikipedia.org/wiki/Luhn_algorithm)
* (mod10) formula.
*
* @param string $number number to check
* @return boolean
*/
public static function luhn($number)
{
// Force the value to be a string as this method uses string functions.
// Converting to an integer may pass PHP_INT_MAX and result in an error!
$number = (string) $number;
if ( ! ctype_digit($number))
{
// Luhn can only be used on numbers!
return FALSE;
}
// Check number length
$length = strlen($number);
// Checksum of the card number
$checksum = 0;
for ($i = $length - 1; $i >= 0; $i -= 2)
{
// Add up every 2nd digit, starting from the right
$checksum += substr($number, $i, 1);
}
for ($i = $length - 2; $i >= 0; $i -= 2)
{
// Add up every 2nd digit doubled, starting from the right
$double = substr($number, $i, 1) * 2;
// Subtract 9 from the double where value is greater than 10
$checksum += ($double >= 10) ? ($double - 9) : $double;
}
// If the checksum is a multiple of 10, the number is valid
return ($checksum % 10 === 0);
}
/**
* Checks if a phone number is valid.
*
* @param string $number phone number to check
* @param array $lengths
* @return boolean
*/
public static function phone($number, $lengths = NULL)
{
if ( ! is_array($lengths))
{
$lengths = array(7,10,11);
}
// Remove all non-digit characters from the number
$number = preg_replace('/\D+/', '', $number);
// Check if the number is within range
return in_array(strlen($number), $lengths);
}
/**
* Tests if a string is a valid date string.
*
* @param string $str date to check
* @return boolean
*/
public static function date($str)
{
return (strtotime($str) !== FALSE);
}
/**
* Checks whether a string consists of alphabetical characters only.
*
* @param string $str input string
* @param boolean $utf8 trigger UTF-8 compatibility
* @return boolean
*/
public static function alpha($str, $utf8 = FALSE)
{
$str = (string) $str;
if ($utf8 === TRUE)
{
return (bool) preg_match('/^\pL++$/uD', $str);
}
else
{
return ctype_alpha($str);
}
}
/**
* Checks whether a string consists of alphabetical characters and numbers only.
*
* @param string $str input string
* @param boolean $utf8 trigger UTF-8 compatibility
* @return boolean
*/
public static function alpha_numeric($str, $utf8 = FALSE)
{
if ($utf8 === TRUE)
{
return (bool) preg_match('/^[\pL\pN]++$/uD', $str);
}
else
{
return ctype_alnum($str);
}
}
/**
* Checks whether a string consists of alphabetical characters, numbers, underscores and dashes only.
*
* @param string $str input string
* @param boolean $utf8 trigger UTF-8 compatibility
* @return boolean
*/
public static function alpha_dash($str, $utf8 = FALSE)
{
if ($utf8 === TRUE)
{
$regex = '/^[-\pL\pN_]++$/uD';
}
else
{
$regex = '/^[-a-z0-9_]++$/iD';
}
return (bool) preg_match($regex, $str);
}
/**
* Checks whether a string consists of digits only (no dots or dashes).
*
* @param string $str input string
* @param boolean $utf8 trigger UTF-8 compatibility
* @return boolean
*/
public static function digit($str, $utf8 = FALSE)
{
if ($utf8 === TRUE)
{
return (bool) preg_match('/^\pN++$/uD', $str);
}
else
{
return (is_int($str) AND $str >= 0) OR ctype_digit($str);
}
}
/**
* Checks whether a string is a valid number (negative and decimal numbers allowed).
*
* Uses {@link http://www.php.net/manual/en/function.localeconv.php locale conversion}
* to allow decimal point to be locale specific.
*
* @param string $str input string
* @return boolean
*/
public static function numeric($str)
{
// Get the decimal point for the current locale
list($decimal) = array_values(localeconv());
// A lookahead is used to make sure the string contains at least one digit (before or after the decimal point)
return (bool) preg_match('/^-?+(?=.*[0-9])[0-9]*+'.preg_quote($decimal).'?+[0-9]*+$/D', (string) $str);
}
/**
* Tests if a number is within a range.
*
* @param string $number number to check
* @param integer $min minimum value
* @param integer $max maximum value
* @param integer $step increment size
* @return boolean
*/
public static function range($number, $min, $max, $step = NULL)
{
if ($number <= $min OR $number >= $max)
{
// Number is outside of range
return FALSE;
}
if ( ! $step)
{
// Default to steps of 1
$step = 1;
}
// Check step requirements
return (($number - $min) % $step === 0);
}
/**
* Checks if a string is a proper decimal format. Optionally, a specific
* number of digits can be checked too.
*
* @param string $str number to check
* @param integer $places number of decimal places
* @param integer $digits number of digits
* @return boolean
*/
public static function decimal($str, $places = 2, $digits = NULL)
{
if ($digits > 0)
{
// Specific number of digits
$digits = '{'.( (int) $digits).'}';
}
else
{
// Any number of digits
$digits = '+';
}
// Get the decimal point for the current locale
list($decimal) = array_values(localeconv());
return (bool) preg_match('/^[+-]?[0-9]'.$digits.preg_quote($decimal).'[0-9]{'.( (int) $places).'}$/D', $str);
}
/**
* Checks if a string is a proper hexadecimal HTML color value. The validation
* is quite flexible as it does not require an initial "#" and also allows for
* the short notation using only three instead of six hexadecimal characters.
*
* @param string $str input string
* @return boolean
*/
public static function color($str)
{
return (bool) preg_match('/^#?+[0-9a-f]{3}(?:[0-9a-f]{3})?$/iD', $str);
}
/**
* Checks if a field matches the value of another field.
*
* @param array $array array of values
* @param string $field field name
* @param string $match field name to match
* @return boolean
*/
public static function matches($array, $field, $match)
{
return ($array[$field] === $array[$match]);
}
} // End Valid

View File

@@ -0,0 +1,612 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* Array and variable validation.
*
* @package Kohana
* @category Security
* @author Kohana Team
* @copyright (c) 2008-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
class Kohana_Validation implements ArrayAccess {
/**
* Creates a new Validation instance.
*
* @param array $array array to use for validation
* @return Validation
*/
public static function factory(array $array)
{
return new Validation($array);
}
// Bound values
protected $_bound = array();
// Field rules
protected $_rules = array();
// Field labels
protected $_labels = array();
// Rules that are executed even when the value is empty
protected $_empty_rules = array('not_empty', 'matches');
// Error list, field => rule
protected $_errors = array();
// Array to validate
protected $_data = array();
/**
* Sets the unique "any field" key and creates an ArrayObject from the
* passed array.
*
* @param array $array array to validate
* @return void
*/
public function __construct(array $array)
{
$this->_data = $array;
}
/**
* Throws an exception because Validation is read-only.
* Implements ArrayAccess method.
*
* @throws Kohana_Exception
* @param string $offset key to set
* @param mixed $value value to set
* @return void
*/
public function offsetSet($offset, $value)
{
throw new Kohana_Exception('Validation objects are read-only.');
}
/**
* Checks if key is set in array data.
* Implements ArrayAccess method.
*
* @param string $offset key to check
* @return bool whether the key is set
*/
public function offsetExists($offset)
{
return isset($this->_data[$offset]);
}
/**
* Throws an exception because Validation is read-only.
* Implements ArrayAccess method.
*
* @throws Kohana_Exception
* @param string $offset key to unset
* @return void
*/
public function offsetUnset($offset)
{
throw new Kohana_Exception('Validation objects are read-only.');
}
/**
* Gets a value from the array data.
* Implements ArrayAccess method.
*
* @param string $offset key to return
* @return mixed value from array
*/
public function offsetGet($offset)
{
return $this->_data[$offset];
}
/**
* Copies the current rules to a new array.
*
* $copy = $array->copy($new_data);
*
* @param array $array new data set
* @return Validation
* @since 3.0.5
*/
public function copy(array $array)
{
// Create a copy of the current validation set
$copy = clone $this;
// Replace the data set
$copy->_data = $array;
return $copy;
}
/**
* Returns the array representation of the current object.
* Deprecated in favor of [Validation::data]
*
* @deprecated
* @return array
*/
public function as_array()
{
return $this->_data;
}
/**
* Returns the array of data to be validated.
*
* @return array
*/
public function data()
{
return $this->_data;
}
/**
* Sets or overwrites the label name for a field.
*
* @param string $field field name
* @param string $label label
* @return $this
*/
public function label($field, $label)
{
// Set the label for this field
$this->_labels[$field] = $label;
return $this;
}
/**
* Sets labels using an array.
*
* @param array $labels list of field => label names
* @return $this
*/
public function labels(array $labels)
{
$this->_labels = $labels + $this->_labels;
return $this;
}
/**
* Overwrites or appends rules to a field. Each rule will be executed once.
* All rules must be string names of functions method names. Parameters must
* match the parameters of the callback function exactly
*
* Aliases you can use in callback parameters:
* - :validation - the validation object
* - :field - the field name
* - :value - the value of the field
*
* // The "username" must not be empty and have a minimum length of 4
* $validation->rule('username', 'not_empty')
* ->rule('username', 'min_length', array(':value', 4));
*
* // The "password" field must match the "password_repeat" field
* $validation->rule('password', 'matches', array(':validation', 'password', 'password_repeat'));
*
* // Using closure (anonymous function)
* $validation->rule('index',
* function(Validation $array, $field, $value)
* {
* if ($value > 6 AND $value < 10)
* {
* $array->error($field, 'custom');
* }
* }
* , array(':validation', ':field', ':value')
* );
*
* [!!] Errors must be added manually when using closures!
*
* @param string $field field name
* @param callback $rule valid PHP callback or closure
* @param array $params extra parameters for the rule
* @return $this
*/
public function rule($field, $rule, array $params = NULL)
{
if ($params === NULL)
{
// Default to array(':value')
$params = array(':value');
}
if ($field !== TRUE AND ! isset($this->_labels[$field]))
{
// Set the field label to the field name
$this->_labels[$field] = preg_replace('/[^\pL]+/u', ' ', $field);
}
// Store the rule and params for this rule
$this->_rules[$field][] = array($rule, $params);
return $this;
}
/**
* Add rules using an array.
*
* @param string $field field name
* @param array $rules list of callbacks
* @return $this
*/
public function rules($field, array $rules)
{
foreach ($rules as $rule)
{
$this->rule($field, $rule[0], Arr::get($rule, 1));
}
return $this;
}
/**
* Bind a value to a parameter definition.
*
* // This allows you to use :model in the parameter definition of rules
* $validation->bind(':model', $model)
* ->rule('status', 'valid_status', array(':model'));
*
* @param string $key variable name or an array of variables
* @param mixed $value value
* @return $this
*/
public function bind($key, $value = NULL)
{
if (is_array($key))
{
foreach ($key as $name => $value)
{
$this->_bound[$name] = $value;
}
}
else
{
$this->_bound[$key] = $value;
}
return $this;
}
/**
* Executes all validation rules. This should
* typically be called within an if/else block.
*
* if ($validation->check())
* {
* // The data is valid, do something here
* }
*
* @return boolean
*/
public function check()
{
if (Kohana::$profiling === TRUE)
{
// Start a new benchmark
$benchmark = Profiler::start('Validation', __FUNCTION__);
}
// New data set
$data = $this->_errors = array();
// Store the original data because this class should not modify it post-validation
$original = $this->_data;
// Get a list of the expected fields
$expected = Arr::merge(array_keys($original), array_keys($this->_labels));
// Import the rules locally
$rules = $this->_rules;
foreach ($expected as $field)
{
// Use the submitted value or NULL if no data exists
$data[$field] = Arr::get($this, $field);
if (isset($rules[TRUE]))
{
if ( ! isset($rules[$field]))
{
// Initialize the rules for this field
$rules[$field] = array();
}
// Append the rules
$rules[$field] = array_merge($rules[$field], $rules[TRUE]);
}
}
// Overload the current array with the new one
$this->_data = $data;
// Remove the rules that apply to every field
unset($rules[TRUE]);
// Bind the validation object to :validation
$this->bind(':validation', $this);
// Bind the data to :data
$this->bind(':data', $this->_data);
// Execute the rules
foreach ($rules as $field => $set)
{
// Get the field value
$value = $this[$field];
// Bind the field name and value to :field and :value respectively
$this->bind(array
(
':field' => $field,
':value' => $value,
));
foreach ($set as $array)
{
// Rules are defined as array($rule, $params)
list($rule, $params) = $array;
foreach ($params as $key => $param)
{
if (is_string($param) AND array_key_exists($param, $this->_bound))
{
// Replace with bound value
$params[$key] = $this->_bound[$param];
}
}
// Default the error name to be the rule (except array and lambda rules)
$error_name = $rule;
if (is_array($rule))
{
// Allows rule('field', array(':model', 'some_rule'));
if (is_string($rule[0]) AND array_key_exists($rule[0], $this->_bound))
{
// Replace with bound value
$rule[0] = $this->_bound[$rule[0]];
}
// This is an array callback, the method name is the error name
$error_name = $rule[1];
$passed = call_user_func_array($rule, $params);
}
elseif ( ! is_string($rule))
{
// This is a lambda function, there is no error name (errors must be added manually)
$error_name = FALSE;
$passed = call_user_func_array($rule, $params);
}
elseif (method_exists('Valid', $rule))
{
// Use a method in this object
$method = new ReflectionMethod('Valid', $rule);
// Call static::$rule($this[$field], $param, ...) with Reflection
$passed = $method->invokeArgs(NULL, $params);
}
elseif (strpos($rule, '::') === FALSE)
{
// Use a function call
$function = new ReflectionFunction($rule);
// Call $function($this[$field], $param, ...) with Reflection
$passed = $function->invokeArgs($params);
}
else
{
// Split the class and method of the rule
list($class, $method) = explode('::', $rule, 2);
// Use a static method call
$method = new ReflectionMethod($class, $method);
// Call $Class::$method($this[$field], $param, ...) with Reflection
$passed = $method->invokeArgs(NULL, $params);
}
// Ignore return values from rules when the field is empty
if ( ! in_array($rule, $this->_empty_rules) AND ! Valid::not_empty($value))
continue;
if ($passed === FALSE AND $error_name !== FALSE)
{
// Add the rule to the errors
$this->error($field, $error_name, $params);
// This field has an error, stop executing rules
break;
}
elseif (isset($this->_errors[$field]))
{
// The callback added the error manually, stop checking rules
break;
}
}
}
// Restore the data to its original form
$this->_data = $original;
if (isset($benchmark))
{
// Stop benchmarking
Profiler::stop($benchmark);
}
return empty($this->_errors);
}
/**
* Add an error to a field.
*
* @param string $field field name
* @param string $error error message
* @param array $params
* @return $this
*/
public function error($field, $error, array $params = NULL)
{
$this->_errors[$field] = array($error, $params);
return $this;
}
/**
* Returns the error messages. If no file is specified, the error message
* will be the name of the rule that failed. When a file is specified, the
* message will be loaded from "field/rule", or if no rule-specific message
* exists, "field/default" will be used. If neither is set, the returned
* message will be "file/field/rule".
*
* By default all messages are translated using the default language.
* A string can be used as the second parameter to specified the language
* that the message was written in.
*
* // Get errors from messages/forms/login.php
* $errors = $Validation->errors('forms/login');
*
* @uses Kohana::message
* @param string $file file to load error messages from
* @param mixed $translate translate the message
* @return array
*/
public function errors($file = NULL, $translate = TRUE)
{
if ($file === NULL)
{
// Return the error list
return $this->_errors;
}
// Create a new message list
$messages = array();
foreach ($this->_errors as $field => $set)
{
list($error, $params) = $set;
// Get the label for this field
$label = $this->_labels[$field];
if ($translate)
{
if (is_string($translate))
{
// Translate the label using the specified language
$label = __($label, NULL, $translate);
}
else
{
// Translate the label
$label = __($label);
}
}
// Start the translation values list
$values = array(
':field' => $label,
':value' => Arr::get($this, $field),
);
if (is_array($values[':value']))
{
// All values must be strings
$values[':value'] = implode(', ', Arr::flatten($values[':value']));
}
if ($params)
{
foreach ($params as $key => $value)
{
if (is_array($value))
{
// All values must be strings
$value = implode(', ', Arr::flatten($value));
}
elseif (is_object($value))
{
// Objects cannot be used in message files
continue;
}
// Check if a label for this parameter exists
if (isset($this->_labels[$value]))
{
// Use the label as the value, eg: related field name for "matches"
$value = $this->_labels[$value];
if ($translate)
{
if (is_string($translate))
{
// Translate the value using the specified language
$value = __($value, NULL, $translate);
}
else
{
// Translate the value
$value = __($value);
}
}
}
// Add each parameter as a numbered value, starting from 1
$values[':param'.($key + 1)] = $value;
}
}
if ($message = Kohana::message($file, "{$field}.{$error}") AND is_string($message))
{
// Found a message for this field and error
}
elseif ($message = Kohana::message($file, "{$field}.default") AND is_string($message))
{
// Found a default message for this field
}
elseif ($message = Kohana::message($file, $error) AND is_string($message))
{
// Found a default message for this error
}
elseif ($message = Kohana::message('validation', $error) AND is_string($message))
{
// Found a default message for this error
}
else
{
// No message exists, display the path expected
$message = "{$file}.{$field}.{$error}";
}
if ($translate)
{
if (is_string($translate))
{
// Translate the message using specified language
$message = __($message, $values, $translate);
}
else
{
// Translate the message using the default language
$message = __($message, $values);
}
}
else
{
// Do not translate, just replace the values
$message = strtr($message, $values);
}
// Set the message for this field
$messages[$field] = $message;
}
return $messages;
}
} // End Validation

View File

@@ -0,0 +1,29 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* @package Kohana
* @category Exceptions
* @author Kohana Team
* @copyright (c) 2009-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
class Kohana_Validation_Exception extends Kohana_Exception {
/**
* @var object Validation instance
*/
public $array;
/**
* @param Validation $array Validation object
* @param string $message error message
* @param array $values translation variables
* @param int $code the exception code
*/
public function __construct(Validation $array, $message = 'Failed to validate array', array $values = NULL, $code = 0, Exception $previous = NULL)
{
$this->array = $array;
parent::__construct($message, $values, $code, $previous);
}
} // End Kohana_Validation_Exception

View File

@@ -0,0 +1,351 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* Acts as an object wrapper for HTML pages with embedded PHP, called "views".
* Variables can be assigned with the view object and referenced locally within
* the view.
*
* @package Kohana
* @category Base
* @author Kohana Team
* @copyright (c) 2008-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
class Kohana_View {
// Array of global variables
protected static $_global_data = array();
/**
* Returns a new View object. If you do not define the "file" parameter,
* you must call [View::set_filename].
*
* $view = View::factory($file);
*
* @param string $file view filename
* @param array $data array of values
* @return View
*/
public static function factory($file = NULL, array $data = NULL)
{
return new View($file, $data);
}
/**
* Captures the output that is generated when a view is included.
* The view data will be extracted to make local variables. This method
* is static to prevent object scope resolution.
*
* $output = View::capture($file, $data);
*
* @param string $kohana_view_filename filename
* @param array $kohana_view_data variables
* @return string
*/
protected static function capture($kohana_view_filename, array $kohana_view_data)
{
// Import the view variables to local namespace
extract($kohana_view_data, EXTR_SKIP);
if (View::$_global_data)
{
// Import the global view variables to local namespace
extract(View::$_global_data, EXTR_SKIP | EXTR_REFS);
}
// Capture the view output
ob_start();
try
{
// Load the view within the current scope
include $kohana_view_filename;
}
catch (Exception $e)
{
// Delete the output buffer
ob_end_clean();
// Re-throw the exception
throw $e;
}
// Get the captured output and close the buffer
return ob_get_clean();
}
/**
* Sets a global variable, similar to [View::set], except that the
* variable will be accessible to all views.
*
* View::set_global($name, $value);
*
* @param string $key variable name or an array of variables
* @param mixed $value value
* @return void
*/
public static function set_global($key, $value = NULL)
{
if (is_array($key))
{
foreach ($key as $key2 => $value)
{
View::$_global_data[$key2] = $value;
}
}
else
{
View::$_global_data[$key] = $value;
}
}
/**
* Assigns a global variable by reference, similar to [View::bind], except
* that the variable will be accessible to all views.
*
* View::bind_global($key, $value);
*
* @param string $key variable name
* @param mixed $value referenced variable
* @return void
*/
public static function bind_global($key, & $value)
{
View::$_global_data[$key] =& $value;
}
// View filename
protected $_file;
// Array of local variables
protected $_data = array();
/**
* Sets the initial view filename and local data. Views should almost
* always only be created using [View::factory].
*
* $view = new View($file);
*
* @param string $file view filename
* @param array $data array of values
* @return void
* @uses View::set_filename
*/
public function __construct($file = NULL, array $data = NULL)
{
if ($file !== NULL)
{
$this->set_filename($file);
}
if ($data !== NULL)
{
// Add the values to the current data
$this->_data = $data + $this->_data;
}
}
/**
* Magic method, searches for the given variable and returns its value.
* Local variables will be returned before global variables.
*
* $value = $view->foo;
*
* [!!] If the variable has not yet been set, an exception will be thrown.
*
* @param string $key variable name
* @return mixed
* @throws Kohana_Exception
*/
public function & __get($key)
{
if (array_key_exists($key, $this->_data))
{
return $this->_data[$key];
}
elseif (array_key_exists($key, View::$_global_data))
{
return View::$_global_data[$key];
}
else
{
throw new Kohana_Exception('View variable is not set: :var',
array(':var' => $key));
}
}
/**
* Magic method, calls [View::set] with the same parameters.
*
* $view->foo = 'something';
*
* @param string $key variable name
* @param mixed $value value
* @return void
*/
public function __set($key, $value)
{
$this->set($key, $value);
}
/**
* Magic method, determines if a variable is set.
*
* isset($view->foo);
*
* [!!] `NULL` variables are not considered to be set by [isset](http://php.net/isset).
*
* @param string $key variable name
* @return boolean
*/
public function __isset($key)
{
return (isset($this->_data[$key]) OR isset(View::$_global_data[$key]));
}
/**
* Magic method, unsets a given variable.
*
* unset($view->foo);
*
* @param string $key variable name
* @return void
*/
public function __unset($key)
{
unset($this->_data[$key], View::$_global_data[$key]);
}
/**
* Magic method, returns the output of [View::render].
*
* @return string
* @uses View::render
*/
public function __toString()
{
try
{
return $this->render();
}
catch (Exception $e)
{
/**
* Display the exception message.
*
* We use this method here because it's impossible to throw and
* exception from __toString().
*/
$error_response = Kohana_exception::_handler($e);
return $error_response->body();
}
}
/**
* Sets the view filename.
*
* $view->set_filename($file);
*
* @param string $file view filename
* @return View
* @throws View_Exception
*/
public function set_filename($file)
{
if (($path = Kohana::find_file('views', $file)) === FALSE)
{
throw new View_Exception('The requested view :file could not be found', array(
':file' => $file,
));
}
// Store the file path locally
$this->_file = $path;
return $this;
}
/**
* Assigns a variable by name. Assigned values will be available as a
* variable within the view file:
*
* // This value can be accessed as $foo within the view
* $view->set('foo', 'my value');
*
* You can also use an array to set several values at once:
*
* // Create the values $food and $beverage in the view
* $view->set(array('food' => 'bread', 'beverage' => 'water'));
*
* @param string $key variable name or an array of variables
* @param mixed $value value
* @return $this
*/
public function set($key, $value = NULL)
{
if (is_array($key))
{
foreach ($key as $name => $value)
{
$this->_data[$name] = $value;
}
}
else
{
$this->_data[$key] = $value;
}
return $this;
}
/**
* Assigns a value by reference. The benefit of binding is that values can
* be altered without re-setting them. It is also possible to bind variables
* before they have values. Assigned values will be available as a
* variable within the view file:
*
* // This reference can be accessed as $ref within the view
* $view->bind('ref', $bar);
*
* @param string $key variable name
* @param mixed $value referenced variable
* @return $this
*/
public function bind($key, & $value)
{
$this->_data[$key] =& $value;
return $this;
}
/**
* Renders the view object to a string. Global and local data are merged
* and extracted to create local variables within the view file.
*
* $output = $view->render();
*
* [!!] Global variables with the same key name as local variables will be
* overwritten by the local variable.
*
* @param string $file view filename
* @return string
* @throws View_Exception
* @uses View::capture
*/
public function render($file = NULL)
{
if ($file !== NULL)
{
$this->set_filename($file);
}
if (empty($this->_file))
{
throw new View_Exception('You must set the file to use within your view before rendering');
}
// Combine local and global data and capture the output
return View::capture($this->_file, $this->_data);
}
} // End View

View File

@@ -0,0 +1,9 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* @package Kohana
* @category Exceptions
* @author Kohana Team
* @copyright (c) 2009-2012 Kohana Team
* @license http://kohanaframework.org/license
*/
class Kohana_View_Exception extends Kohana_Exception { }