summaryrefslogtreecommitdiff
path: root/system/helpers
diff options
context:
space:
mode:
authorTristan Zur <tzur@webserver.ccwn.org>2015-06-10 20:55:53 +0200
committerTristan Zur <tzur@webserver.ccwn.org>2015-06-10 20:55:53 +0200
commit406abd7c4df1ace2cd3e4e17159e8941a2e8c0c4 (patch)
treea324be16021f44f2fd6d55e609f47024e945b1db /system/helpers
Initial import
Diffstat (limited to 'system/helpers')
-rw-r--r--system/helpers/arr.php273
-rw-r--r--system/helpers/cookie.php149
-rw-r--r--system/helpers/date.php395
-rw-r--r--system/helpers/db.php47
-rw-r--r--system/helpers/download.php135
-rw-r--r--system/helpers/expires.php129
-rw-r--r--system/helpers/feed.php120
-rw-r--r--system/helpers/file.php184
-rw-r--r--system/helpers/form.php466
-rw-r--r--system/helpers/format.php112
-rw-r--r--system/helpers/html.php364
-rw-r--r--system/helpers/inflector.php252
-rw-r--r--system/helpers/num.php24
-rw-r--r--system/helpers/remote.php64
-rw-r--r--system/helpers/request.php618
-rw-r--r--system/helpers/security.php35
-rw-r--r--system/helpers/text.php596
-rw-r--r--system/helpers/upload.php157
-rw-r--r--system/helpers/url.php264
-rw-r--r--system/helpers/utf8.php744
-rw-r--r--system/helpers/valid.php453
21 files changed, 5581 insertions, 0 deletions
diff --git a/system/helpers/arr.php b/system/helpers/arr.php
new file mode 100644
index 0000000..19026fa
--- /dev/null
+++ b/system/helpers/arr.php
@@ -0,0 +1,273 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Array helper class.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class arr_Core {
+
+ /**
+ * Return a callback array from a string, eg: limit[10,20] would become
+ * array('limit', array('10', '20'))
+ *
+ * @param string callback string
+ * @return array
+ */
+ public static function callback_string($str)
+ {
+ // command[param,param]
+ if (preg_match('/([^\[]*+)\[(.+)\]/', (string) $str, $match))
+ {
+ // command
+ $command = $match[1];
+
+ // param,param
+ $params = preg_split('/(?<!\\\\),/', $match[2]);
+ $params = str_replace('\,', ',', $params);
+ }
+ else
+ {
+ // command
+ $command = $str;
+
+ // No params
+ $params = NULL;
+ }
+
+ return array($command, $params);
+ }
+
+ /**
+ * Rotates a 2D array clockwise.
+ * Example, turns a 2x3 array into a 3x2 array.
+ *
+ * @param array array to rotate
+ * @param boolean keep the keys in the final rotated array. the sub arrays of the source array need to have the same key values.
+ * if your subkeys might not match, you need to pass FALSE here!
+ * @return array
+ */
+ public static function rotate($source_array, $keep_keys = TRUE)
+ {
+ $new_array = array();
+ foreach ($source_array as $key => $value)
+ {
+ $value = ($keep_keys === TRUE) ? $value : array_values($value);
+ foreach ($value as $k => $v)
+ {
+ $new_array[$k][$key] = $v;
+ }
+ }
+
+ return $new_array;
+ }
+
+ /**
+ * Removes a key from an array and returns the value.
+ *
+ * @param string key to return
+ * @param array array to work on
+ * @return mixed value of the requested array key
+ */
+ public static function remove($key, & $array)
+ {
+ if ( ! array_key_exists($key, $array))
+ return NULL;
+
+ $val = $array[$key];
+ unset($array[$key]);
+
+ return $val;
+ }
+
+
+ /**
+ * Extract one or more keys from an array. Each key given after the first
+ * argument (the array) will be extracted. Keys that do not exist in the
+ * search array will be NULL in the extracted data.
+ *
+ * @param array array to search
+ * @param string key name
+ * @return array
+ */
+ public static function extract(array $search, $keys)
+ {
+ // Get the keys, removing the $search array
+ $keys = array_slice(func_get_args(), 1);
+
+ $found = array();
+ foreach ($keys as $key)
+ {
+ $found[$key] = isset($search[$key]) ? $search[$key] : NULL;
+ }
+
+ return $found;
+ }
+
+ /**
+ * Get the value of array[key]. If it doesn't exist, return default.
+ *
+ * @param array array to search
+ * @param string key name
+ * @param mixed default value
+ * @return mixed
+ */
+ public static function get(array $array, $key, $default = NULL)
+ {
+ return isset($array[$key]) ? $array[$key] : $default;
+ }
+
+ /**
+ * Because PHP does not have this function.
+ *
+ * @param array array to unshift
+ * @param string key to unshift
+ * @param mixed value to unshift
+ * @return array
+ */
+ public static function unshift_assoc( array & $array, $key, $val)
+ {
+ $array = array_reverse($array, TRUE);
+ $array[$key] = $val;
+ $array = array_reverse($array, TRUE);
+
+ return $array;
+ }
+
+ /**
+ * Because PHP does not have this function, and array_walk_recursive creates
+ * references in arrays and is not truly recursive.
+ *
+ * @param mixed callback to apply to each member of the array
+ * @param array array to map to
+ * @return array
+ */
+ public static function map_recursive($callback, array $array)
+ {
+ foreach ($array as $key => $val)
+ {
+ // Map the callback to the key
+ $array[$key] = is_array($val) ? arr::map_recursive($callback, $val) : call_user_func($callback, $val);
+ }
+
+ return $array;
+ }
+
+ /**
+ * Emulates array_merge_recursive, but appends numeric keys and replaces
+ * associative keys, instead of appending all keys.
+ *
+ * @param array any number of arrays
+ * @return array
+ */
+ public static function merge()
+ {
+ $total = func_num_args();
+
+ $result = array();
+ for ($i = 0; $i < $total; $i++)
+ {
+ foreach (func_get_arg($i) as $key => $val)
+ {
+ if (isset($result[$key]))
+ {
+ if (is_array($val))
+ {
+ // Arrays are merged recursively
+ $result[$key] = arr::merge($result[$key], $val);
+ }
+ elseif (is_int($key))
+ {
+ // Indexed arrays are appended
+ array_push($result, $val);
+ }
+ else
+ {
+ // Associative arrays are replaced
+ $result[$key] = $val;
+ }
+ }
+ else
+ {
+ // New values are added
+ $result[$key] = $val;
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Overwrites an array with values from input array(s).
+ * Non-existing keys will not be appended!
+ *
+ * @param array key array
+ * @param array input array(s) that will overwrite key array 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;
+ }
+
+ /**
+ * Recursively convert an array to an object.
+ *
+ * @param array array to convert
+ * @return object
+ */
+ public static function to_object(array $array, $class = 'stdClass')
+ {
+ $object = new $class;
+
+ foreach ($array as $key => $value)
+ {
+ if (is_array($value))
+ {
+ // Convert the array to an object
+ $value = arr::to_object($value, $class);
+ }
+
+ // Add the value to the object
+ $object->{$key} = $value;
+ }
+
+ return $object;
+ }
+
+ /**
+ * Returns specific key/column from an array of objects.
+ *
+ * @param string|integer $key The key or column number to pluck from each object.
+ * @param array $array The array of objects to pluck from.
+ * @return array
+ */
+ public static function pluck($key, $array)
+ {
+ $result = array();
+ foreach ($array as $i => $object)
+ {
+ $result[$i] = isset($object[$key]) ? $object[$key] : NULL;
+ }
+ return $result;
+ }
+} // End arr
diff --git a/system/helpers/cookie.php b/system/helpers/cookie.php
new file mode 100644
index 0000000..3680ae3
--- /dev/null
+++ b/system/helpers/cookie.php
@@ -0,0 +1,149 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Cookie helper class.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class cookie_Core {
+
+ /**
+ * Sets a cookie with the given parameters.
+ *
+ * @param string cookie name or array of config options
+ * @param string cookie value
+ * @param integer number of seconds before the cookie expires
+ * @param string URL path to allow
+ * @param string URL domain to allow
+ * @param boolean HTTPS only
+ * @param boolean HTTP only (requires PHP 5.2 or higher)
+ * @return boolean
+ */
+ public static function set($name, $value = NULL, $expire = NULL, $path = NULL, $domain = NULL, $secure = NULL, $httponly = NULL)
+ {
+ if (headers_sent())
+ return FALSE;
+
+ // If the name param is an array, we import it
+ is_array($name) and extract($name, EXTR_OVERWRITE);
+
+ // Fetch default options
+ $config = Kohana::config('cookie');
+
+ foreach (array('value', 'expire', 'domain', 'path', 'secure', 'httponly') as $item)
+ {
+ if ($$item === NULL AND isset($config[$item]))
+ {
+ $$item = $config[$item];
+ }
+ }
+
+ if ($expire !== 0)
+ {
+ // The expiration is expected to be a UNIX timestamp
+ $expire += time();
+ }
+
+ $value = cookie::salt($name, $value).'~'.$value;
+
+ return setcookie($name, $value, $expire, $path, $domain, $secure, $httponly);
+ }
+
+ /**
+ * Fetch a cookie value, using the Input library.
+ *
+ * @param string cookie name
+ * @param mixed default value
+ * @param boolean use XSS cleaning on the value
+ * @return string
+ */
+ public static function get($name = NULL, $default = NULL, $xss_clean = FALSE)
+ {
+ // Return an array of all the cookies if we don't have a name
+ if ($name === NULL)
+ {
+ $cookies = array();
+
+ foreach($_COOKIE AS $key => $value)
+ {
+ $cookies[$key] = cookie::get($key, $default, $xss_clean);
+ }
+ return $cookies;
+ }
+
+ if ( ! isset($_COOKIE[$name]))
+ {
+ return $default;
+ }
+
+ // Get the cookie value
+ $cookie = $_COOKIE[$name];
+
+ // Find the position of the split between salt and contents
+ $split = strlen(cookie::salt($name, NULL));
+
+ if (isset($cookie[$split]) AND $cookie[$split] === '~')
+ {
+ // Separate the salt and the value
+ list ($hash, $value) = explode('~', $cookie, 2);
+
+ if (cookie::salt($name, $value) === $hash)
+ {
+ if ($xss_clean === TRUE AND Kohana::config('core.global_xss_filtering') === FALSE)
+ {
+ return Input::instance()->xss_clean($value);
+ }
+ // Cookie signature is valid
+ return $value;
+ }
+
+ // The cookie signature is invalid, delete it
+ cookie::delete($name);
+ }
+
+ return $default;
+ }
+
+ /**
+ * Nullify and unset a cookie.
+ *
+ * @param string cookie name
+ * @param string URL path
+ * @param string URL domain
+ * @return boolean
+ */
+ public static function delete($name, $path = NULL, $domain = NULL)
+ {
+ // Delete the cookie from globals
+ unset($_COOKIE[$name]);
+
+ // Sets the cookie value to an empty string, and the expiration to 24 hours ago
+ return cookie::set($name, '', -86400, $path, $domain, FALSE, FALSE);
+ }
+
+ /**
+ * Generates a salt string for a cookie based on the name and value.
+ *
+ * @param string $name name of cookie
+ * @param string $value value of cookie
+ * @return string sha1 hash
+ */
+ public static function salt($name, $value)
+ {
+ // Determine the user agent
+ $agent = isset($_SERVER['HTTP_USER_AGENT']) ? strtolower($_SERVER['HTTP_USER_AGENT']) : 'unknown';
+
+ // Cookie salt.
+ $salt = Kohana::config('cookie.salt');
+
+ return sha1($agent.$name.$value.$salt);
+ }
+
+ final private function __construct()
+ {
+ // Static class.
+ }
+
+} // End cookie \ No newline at end of file
diff --git a/system/helpers/date.php b/system/helpers/date.php
new file mode 100644
index 0000000..0b29d28
--- /dev/null
+++ b/system/helpers/date.php
@@ -0,0 +1,395 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Date helper class.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class date_Core {
+
+ /**
+ * Converts a UNIX timestamp to DOS format.
+ *
+ * @param integer 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.
+ *
+ * @param integer 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 the offset (in seconds) between two time zones.
+ * @see http://php.net/timezones
+ *
+ * @param string timezone to find the offset of
+ * @param string|boolean timezone used as the baseline
+ * @param string time at which to calculate
+ * @return integer
+ */
+ public static function offset($remote, $local = TRUE, $when = 'now')
+ {
+ if ($local === TRUE)
+ {
+ $local = date_default_timezone_get();
+ }
+
+ // Create timezone objects
+ $remote = new DateTimeZone($remote);
+ $local = new DateTimeZone($local);
+
+ // Create date objects from timezones
+ $time_there = new DateTime($when, $remote);
+ $time_here = new DateTime($when, $local);
+
+ // Find the offset
+ return $remote->getOffset($time_there) - $local->getOffset($time_here);
+ }
+
+ /**
+ * Number of seconds in a minute, incrementing by a step.
+ *
+ * @param integer amount to increment each step by, 1 to 30
+ * @param integer start value
+ * @param integer 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] = ($i < 10) ? '0'.$i : $i;
+ }
+
+ return $seconds;
+ }
+
+ /**
+ * Number of minutes in an hour, incrementing by a step.
+ *
+ * @param integer 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.
+ *
+ * @param integer amount to increment each step by
+ * @param boolean use 24-hour time
+ * @param integer 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] = $i;
+ }
+
+ return $hours;
+ }
+
+ /**
+ * Returns AM or PM, based on a given hour.
+ *
+ * @param integer 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.
+ *
+ * @param integer hour to adjust
+ * @param string 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('%02s', $hour);
+ }
+
+ /**
+ * Number of days in month.
+ *
+ * @param integer number of month
+ * @param integer 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;
+
+ // Always integers
+ $month = (int) $month;
+ $year = (int) $year;
+
+ // Use the current year by default
+ $year = ($year == FALSE) ? date('Y') : $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] = $i;
+ }
+ }
+
+ return $months[$year][$month];
+ }
+
+ /**
+ * Number of months in a year
+ *
+ * @return array A mirrored (foo => foo) array from 1-12.
+ */
+ public static function months()
+ {
+ return date::hours();
+ }
+
+ /**
+ * Returns an array of years between a starting and ending year.
+ * Uses the current year +/- 5 as the max/min.
+ *
+ * @param integer starting year
+ * @param integer ending year
+ * @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();
+
+ // Add one, so that "less than" works
+ $end += 1;
+
+ for ($i = $start; $i < $end; $i++)
+ {
+ $years[$i] = $i;
+ }
+
+ return $years;
+ }
+
+ /**
+ * Returns time difference between two timestamps, in human readable format.
+ *
+ * @param integer timestamp
+ * @param integer timestamp, defaults to the current time
+ * @param string formatting string
+ * @return string|array
+ */
+ public static function timespan($time1, $time2 = NULL, $output = 'years,months,weeks,days,hours,minutes,seconds')
+ {
+ // Array with the output formats
+ $output = preg_split('/[^a-z]+/', strtolower((string) $output));
+
+ // Invalid output
+ if (empty($output))
+ return FALSE;
+
+ // Make the output values into keys
+ extract(array_flip($output), EXTR_SKIP);
+
+ // Default values
+ $time1 = max(0, (int) $time1);
+ $time2 = empty($time2) ? time() : max(0, (int) $time2);
+
+ // Calculate timespan (seconds)
+ $timespan = abs($time1 - $time2);
+
+ // All values found using Google Calculator.
+ // Years and months do not match the formula exactly, due to leap years.
+
+ // Years ago, 60 * 60 * 24 * 365
+ isset($years) and $timespan -= 31556926 * ($years = (int) floor($timespan / 31556926));
+
+ // Months ago, 60 * 60 * 24 * 30
+ isset($months) and $timespan -= 2629744 * ($months = (int) floor($timespan / 2629743.83));
+
+ // Weeks ago, 60 * 60 * 24 * 7
+ isset($weeks) and $timespan -= 604800 * ($weeks = (int) floor($timespan / 604800));
+
+ // Days ago, 60 * 60 * 24
+ isset($days) and $timespan -= 86400 * ($days = (int) floor($timespan / 86400));
+
+ // Hours ago, 60 * 60
+ isset($hours) and $timespan -= 3600 * ($hours = (int) floor($timespan / 3600));
+
+ // Minutes ago, 60
+ isset($minutes) and $timespan -= 60 * ($minutes = (int) floor($timespan / 60));
+
+ // Seconds ago, 1
+ isset($seconds) and $seconds = $timespan;
+
+ // Remove the variables that cannot be accessed
+ unset($timespan, $time1, $time2);
+
+ // Deny access to these variables
+ $deny = array_flip(array('deny', 'key', 'difference', 'output'));
+
+ // Return the difference
+ $difference = array();
+ foreach ($output as $key)
+ {
+ if (isset($$key) AND ! isset($deny[$key]))
+ {
+ // Add requested key to the output
+ $difference[$key] = $$key;
+ }
+ }
+
+ // Invalid output formats string
+ if (empty($difference))
+ return FALSE;
+
+ // If only one output format was asked, don't put it in an array
+ if (count($difference) === 1)
+ return current($difference);
+
+ // Return array
+ return $difference;
+ }
+
+ /**
+ * Returns time difference between two timestamps, in the format:
+ * N year, N months, N weeks, N days, N hours, N minutes, and N seconds ago
+ *
+ * @param integer timestamp
+ * @param integer timestamp, defaults to the current time
+ * @param string formatting string
+ * @return string
+ */
+ public static function timespan_string($time1, $time2 = NULL, $output = 'years,months,weeks,days,hours,minutes,seconds')
+ {
+ if ($difference = date::timespan($time1, $time2, $output) AND is_array($difference))
+ {
+ // Determine the key of the last item in the array
+ $last = end($difference);
+ $last = key($difference);
+
+ $span = array();
+ foreach ($difference as $name => $amount)
+ {
+ if ($amount === 0)
+ {
+ // Skip empty amounts
+ continue;
+ }
+
+ // Add the amount to the span
+ $span[] = ($name === $last ? ' and ' : ', ').$amount.' '.($amount === 1 ? inflector::singular($name) : $name);
+ }
+
+ // If the difference is less than 60 seconds, remove the preceding and.
+ if (count($span) === 1)
+ {
+ $span[0] = ltrim($span[0], 'and ');
+ }
+
+ // Replace difference by making the span into a string
+ $difference = trim(implode('', $span), ',');
+ }
+ elseif (is_int($difference))
+ {
+ // Single-value return
+ $difference = $difference.' '.($difference === 1 ? inflector::singular($output) : $output);
+ }
+
+ return $difference;
+ }
+
+} // End date \ No newline at end of file
diff --git a/system/helpers/db.php b/system/helpers/db.php
new file mode 100644
index 0000000..941bb2e
--- /dev/null
+++ b/system/helpers/db.php
@@ -0,0 +1,47 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Database helper class.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+ class db_Core {
+
+ public static function query($sql)
+ {
+ return new Database_Query($sql);
+ }
+
+ public static function build($database = 'default')
+ {
+ return new Database_Builder($database);
+ }
+
+ public static function select($columns = NULL)
+ {
+ return db::build()->select($columns);
+ }
+
+ public static function insert($table = NULL, $set = NULL)
+ {
+ return db::build()->insert($table, $set);
+ }
+
+ public static function update($table = NULL, $set = NULL, $where = NULL)
+ {
+ return db::build()->update($table, $set, $where);
+ }
+
+ public static function delete($table = NULL, $where = NULL)
+ {
+ return db::build()->delete($table, $where);
+ }
+
+ public static function expr($expression)
+ {
+ return new Database_Expression($expression);
+ }
+
+} // End db
diff --git a/system/helpers/download.php b/system/helpers/download.php
new file mode 100644
index 0000000..0bdb04d
--- /dev/null
+++ b/system/helpers/download.php
@@ -0,0 +1,135 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Download helper class.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class download_Core {
+
+ /**
+ * Send headers necessary to invoke a "Save As" dialog
+ *
+ * @link http://support.microsoft.com/kb/260519
+ * @link http://greenbytes.de/tech/tc2231/
+ *
+ * @param string file name
+ * @return string file name as it was sent
+ */
+ public static function dialog($filename)
+ {
+ $filename = basename($filename);
+
+ header('Content-Disposition: attachment; filename="'.$filename.'"');
+
+ return $filename;
+ }
+
+ /**
+ * Send the contents of a file or a data string with the proper MIME type and exit.
+ *
+ * @uses exit()
+ * @uses Kohana::close_buffers()
+ *
+ * @param string a file path or file name
+ * @param string optional data to send
+ * @return void
+ */
+ public static function send($filename, $data = NULL)
+ {
+ if ($data === NULL)
+ {
+ $filepath = realpath($filename);
+
+ $filename = basename($filepath);
+ $filesize = filesize($filepath);
+ }
+ else
+ {
+ $filename = basename($filename);
+ $filesize = strlen($data);
+ }
+
+ // Retrieve MIME type by extension
+ $mime = Kohana::config('mimes.'.strtolower(substr(strrchr($filename, '.'), 1)));
+ $mime = empty($mime) ? 'application/octet-stream' : $mime[0];
+
+ // Close output buffers
+ Kohana::close_buffers(FALSE);
+
+ // Clear any output
+ Event::add('system.display', create_function('', 'Kohana::$output = "";'));
+
+ // Send headers
+ header("Content-Type: $mime");
+ header('Content-Length: '.sprintf('%d', $filesize));
+ header('Content-Transfer-Encoding: binary');
+
+ // Send data
+ if ($data === NULL)
+ {
+ $handle = fopen($filepath, 'rb');
+
+ fpassthru($handle);
+ fclose($handle);
+ }
+ else
+ {
+ echo $data;
+ }
+
+ exit;
+ }
+
+ /**
+ * Force the download of a file by the user's browser by preventing any
+ * caching. Contains a workaround for Internet Explorer.
+ *
+ * @link http://support.microsoft.com/kb/316431
+ * @link http://support.microsoft.com/kb/812935
+ *
+ * @uses download::dialog()
+ * @uses download::send()
+ *
+ * @param string a file path or file name
+ * @param mixed data to be sent if the filename does not exist
+ * @param string suggested filename to display in the download
+ * @return void
+ */
+ public static function force($filename = NULL, $data = NULL, $nicename = NULL)
+ {
+ download::dialog(empty($nicename) ? $filename : $nicename);
+
+ // Prevent caching
+ header('Expires: Thu, 01 Jan 1970 00:00:00 GMT');
+
+ if (request::user_agent('browser') === 'Internet Explorer' AND request::user_agent('version') <= '6.0')
+ {
+ // HTTP 1.0
+ header('Pragma:');
+
+ // HTTP 1.1 with IE extensions
+ header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
+ }
+ else
+ {
+ // HTTP 1.0
+ header('Pragma: no-cache');
+
+ // HTTP 1.1
+ header('Cache-Control: no-cache, max-age=0');
+ }
+
+ if (is_file($filename))
+ {
+ download::send($filename);
+ }
+ else
+ {
+ download::send($filename, $data);
+ }
+ }
+
+} // End download
diff --git a/system/helpers/expires.php b/system/helpers/expires.php
new file mode 100644
index 0000000..a3e01b8
--- /dev/null
+++ b/system/helpers/expires.php
@@ -0,0 +1,129 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Controls headers that effect client caching of pages
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class expires_Core {
+
+ /**
+ * Sets the amount of time before content expires
+ *
+ * @param integer Seconds before the content expires
+ * @param integer Last modified timestamp in seconds(optional)
+ * @return integer Timestamp when the content expires
+ */
+ public static function set($seconds = 60, $last_modified=null)
+ {
+ $now = time();
+ $expires = $now + $seconds;
+ if (empty($last_modified))
+ {
+ $last_modified = $now;
+ }
+
+ header('Last-Modified: '.gmdate('D, d M Y H:i:s T', $last_modified));
+
+ // HTTP 1.0
+ header('Expires: '.gmdate('D, d M Y H:i:s T', $expires));
+
+ // HTTP 1.1
+ header('Cache-Control: public,max-age='.$seconds);
+
+ return $expires;
+ }
+
+ /**
+ * Parses the If-Modified-Since header
+ *
+ * @return integer|boolean Timestamp or FALSE when header is lacking or malformed
+ */
+ public static function get()
+ {
+ if ( ! empty($_SERVER['HTTP_IF_MODIFIED_SINCE']))
+ {
+ // Some versions of IE6 append "; length=####"
+ if (($strpos = strpos($_SERVER['HTTP_IF_MODIFIED_SINCE'], ';')) !== FALSE)
+ {
+ $mod_time = substr($_SERVER['HTTP_IF_MODIFIED_SINCE'], 0, $strpos);
+ }
+ else
+ {
+ $mod_time = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
+ }
+
+ return strtotime($mod_time);
+ }
+
+ return FALSE;
+ }
+
+ /**
+ * Checks to see if content should be updated otherwise sends Not Modified status
+ * and exits.
+ *
+ * @uses exit()
+ * @uses expires::get()
+ *
+ * @param integer Maximum age of the content in seconds
+ * @param integer Last modified timestamp in seconds(optional)
+ * @return integer|boolean Timestamp of the If-Modified-Since header or FALSE when header is lacking or malformed
+ */
+ public static function check($seconds = 60, $modified=null)
+ {
+ if ($last_modified = expires::get())
+ {
+ $now = time();
+
+ if (empty($last_modified))
+ {
+ $last_modified = $now;
+ }
+
+ if ($modified <= $last_modified)
+ {
+ // Content has not expired
+ header($_SERVER['SERVER_PROTOCOL'].' 304 Not Modified');
+ header('Last-Modified: '.gmdate('D, d M Y H:i:s T', $last_modified));
+
+ $expires = $now + $seconds;
+ // HTTP 1.0
+ header('Expires: '.gmdate('D, d M Y H:i:s T', $expires));
+
+ // HTTP 1.1
+ header('Cache-Control: public,max-age='.$seconds);
+
+ // Clear any output
+ Event::add('system.display', create_function('', 'Kohana::$output = "";'));
+
+ exit;
+ }
+ }
+
+ return $last_modified;
+ }
+
+ /**
+ * Check if expiration headers are already set
+ *
+ * @return boolean
+ */
+ public static function headers_set()
+ {
+ foreach (headers_list() as $header)
+ {
+ if (strncasecmp($header, 'Expires:', 8) === 0
+ OR strncasecmp($header, 'Cache-Control:', 14) === 0
+ OR strncasecmp($header, 'Last-Modified:', 14) === 0)
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+ }
+
+} // End expires
diff --git a/system/helpers/feed.php b/system/helpers/feed.php
new file mode 100644
index 0000000..9831812
--- /dev/null
+++ b/system/helpers/feed.php
@@ -0,0 +1,120 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Feed helper class.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class feed_Core {
+
+ /**
+ * Parses a remote feed into an array.
+ *
+ * @param string remote feed URL
+ * @param integer 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_User_Exception('Feed Error', 'SimpleXML must be installed!');
+
+ // Make limit an integer
+ $limit = (int) $limit;
+
+ // Disable error reporting while opening the feed
+ $ER = error_reporting(0);
+
+ // Allow loading by filename or raw XML string
+ $load = (is_file($feed) OR valid::url($feed)) ? 'simplexml_load_file' : 'simplexml_load_string';
+
+ // Load the feed
+ $feed = $load($feed, 'SimpleXMLElement', LIBXML_NOCDATA);
+
+ // Restore error reporting
+ error_reporting($ER);
+
+ // Feed could not be loaded
+ if ($feed === FALSE)
+ return array();
+
+ // 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;
+
+ $items[] = (array) $item;
+ }
+
+ return $items;
+ }
+
+ /**
+ * Creates a feed from the given parameters.
+ *
+ * @param array feed information
+ * @param array items to add to the feed
+ * @param string define which format to use
+ * @param string define which encoding to use
+ * @return string
+ */
+ public static function create($info, $items, $format = 'rss2', $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 === 'pubDate' OR $name === 'lastBuildDate') AND (is_int($value) OR ctype_digit($value)))
+ {
+ // Convert timestamps to RFC 822 formatted dates
+ $value = date(DATE_RFC822, $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(DATE_RFC822, $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);
+ }
+ }
+
+ return $feed->asXML();
+ }
+
+} // End feed \ No newline at end of file
diff --git a/system/helpers/file.php b/system/helpers/file.php
new file mode 100644
index 0000000..48fbfc1
--- /dev/null
+++ b/system/helpers/file.php
@@ -0,0 +1,184 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * File helper class.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class file_Core {
+
+ /**
+ * 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.
+ *
+ * @param string filename
+ * @return string mime-type, if found
+ * @return boolean FALSE, if not found
+ */
+ public static function mime($filename)
+ {
+ // Make sure the file is readable
+ if ( ! (is_file($filename) AND is_readable($filename)))
+ return FALSE;
+
+ // Get the extension from the filename
+ $extension = strtolower(substr(strrchr($filename, '.'), 1));
+
+ if (preg_match('/^(?:jpe?g|png|[gt]if|bmp|swf)$/', $extension))
+ {
+ // Disable error reporting
+ $ER = error_reporting(0);
+
+ // Use getimagesize() to find the mime type on images
+ $mime = getimagesize($filename);
+
+ // Turn error reporting back on
+ error_reporting($ER);
+
+ // Return the mime type
+ if (isset($mime['mime']))
+ return $mime['mime'];
+ }
+
+ if (function_exists('finfo_open'))
+ {
+ // Use the fileinfo extension
+ $finfo = finfo_open(FILEINFO_MIME);
+ $mime = finfo_file($finfo, $filename);
+ finfo_close($finfo);
+
+ // Return the mime type
+ return $mime;
+ }
+
+ if (ini_get('mime_magic.magicfile') AND function_exists('mime_content_type'))
+ {
+ // Return the mime type using mime_content_type
+ return mime_content_type($filename);
+ }
+
+ if ( ! KOHANA_IS_WIN)
+ {
+ // Attempt to locate use the file command, checking the return value
+ if ($command = trim(exec('which file', $output, $return)) AND $return === 0)
+ {
+ return trim(exec($command.' -bi '.escapeshellarg($filename)));
+ }
+ }
+
+ if ( ! empty($extension) AND is_array($mime = Kohana::config('mimes.'.$extension)))
+ {
+ // Return the mime-type guess, based on the extension
+ return $mime[0];
+ }
+
+ // Unable to find the mime-type
+ return FALSE;
+ }
+
+ /**
+ * Split a file into pieces matching a specific size.
+ *
+ * @param string file to be split
+ * @param string directory to output to, defaults to the same directory as the file
+ * @param integer size, in MB, for each chunk to be
+ * @return integer The number of pieces that were created.
+ */
+ public static function split($filename, $output_dir = FALSE, $piece_size = 10)
+ {
+ // Find output dir
+ $output_dir = ($output_dir == FALSE) ? pathinfo(str_replace('\\', '/', realpath($filename)), PATHINFO_DIRNAME) : str_replace('\\', '/', realpath($output_dir));
+ $output_dir = rtrim($output_dir, '/').'/';
+
+ // Open files for writing
+ $input_file = fopen($filename, 'rb');
+
+ // Change the piece size to bytes
+ $piece_size = 1024 * 1024 * (int) $piece_size; // Size in bytes
+
+ // Set up reading variables
+ $read = 0; // Number of bytes read
+ $piece = 1; // Current piece
+ $chunk = 1024 * 8; // Chunk size to read
+
+ // Split the file
+ while ( ! feof($input_file))
+ {
+ // Open a new piece
+ $piece_name = $filename.'.'.str_pad($piece, 3, '0', STR_PAD_LEFT);
+ $piece_open = @fopen($piece_name, 'wb+') or die('Could not write piece '.$piece_name);
+
+ // Fill the current piece
+ while ($read < $piece_size AND $data = fread($input_file, $chunk))
+ {
+ fwrite($piece_open, $data) or die('Could not write to open piece '.$piece_name);
+ $read += $chunk;
+ }
+
+ // Close the current piece
+ fclose($piece_open);
+
+ // Prepare to open a new piece
+ $read = 0;
+ $piece++;
+
+ // Make sure that piece is valid
+ ($piece < 999) or die('Maximum of 999 pieces exceeded, try a larger piece size');
+ }
+
+ // Close input file
+ fclose($input_file);
+
+ // Returns the number of pieces that were created
+ return ($piece - 1);
+ }
+
+ /**
+ * Join a split file into a whole file.
+ *
+ * @param string split filename, without .000 extension
+ * @param string output filename, if different then an the filename
+ * @return integer The number of pieces that were joined.
+ */
+ public static function join($filename, $output = FALSE)
+ {
+ if ($output == FALSE)
+ $output = $filename;
+
+ // Set up reading variables
+ $piece = 1; // Current piece
+ $chunk = 1024 * 8; // Chunk size to read
+
+ // Open output file
+ $output_file = @fopen($output, 'wb+') or die('Could not open output file '.$output);
+
+ // Read each piece
+ while ($piece_open = @fopen(($piece_name = $filename.'.'.str_pad($piece, 3, '0', STR_PAD_LEFT)), 'rb'))
+ {
+ // Write the piece into the output file
+ while ( ! feof($piece_open))
+ {
+ fwrite($output_file, fread($piece_open, $chunk));
+ }
+
+ // Close the current piece
+ fclose($piece_open);
+
+ // Prepare for a new piece
+ $piece++;
+
+ // Make sure piece is valid
+ ($piece < 999) or die('Maximum of 999 pieces exceeded');
+ }
+
+ // Close the output file
+ fclose($output_file);
+
+ // Return the number of pieces joined
+ return ($piece - 1);
+ }
+
+} // End file \ No newline at end of file
diff --git a/system/helpers/form.php b/system/helpers/form.php
new file mode 100644
index 0000000..3d4f8ad
--- /dev/null
+++ b/system/helpers/form.php
@@ -0,0 +1,466 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Form helper class.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class form_Core {
+
+ /**
+ * Generates an opening HTML form tag.
+ *
+ * @param string form action attribute
+ * @param array extra attributes
+ * @param array hidden fields to be created immediately after the form tag
+ * @param string non-default protocol, eg: https
+ * @return string
+ */
+ public static function open($action = NULL, $attr = array(), $hidden = NULL, $protocol = NULL)
+ {
+ // Make sure that the method is always set
+ empty($attr['method']) and $attr['method'] = 'post';
+
+ if ($attr['method'] !== 'post' AND $attr['method'] !== 'get')
+ {
+ // If the method is invalid, use post
+ $attr['method'] = 'post';
+ }
+
+ if ($action === NULL)
+ {
+ // Use the current URL as the default action
+ $action = url::site(Router::$complete_uri, $protocol);
+ }
+ elseif (strpos($action, '://') === FALSE)
+ {
+ // Make the action URI into a URL
+ $action = url::site($action, $protocol);
+ }
+
+ // Set action
+ $attr['action'] = $action;
+
+ // Form opening tag
+ $form = '<form'.form::attributes($attr).'>'."\n";
+
+ // Add hidden fields immediate after opening tag
+ empty($hidden) or $form .= form::hidden($hidden);
+
+ return $form;
+ }
+
+ /**
+ * Generates an opening HTML form tag that can be used for uploading files.
+ *
+ * @param string form action attribute
+ * @param array extra attributes
+ * @param array hidden fields to be created immediately after the form tag
+ * @return string
+ */
+ public static function open_multipart($action = NULL, $attr = array(), $hidden = array())
+ {
+ // Set multi-part form type
+ $attr['enctype'] = 'multipart/form-data';
+
+ return form::open($action, $attr, $hidden);
+ }
+
+ /**
+ * Creates a HTML form hidden input tag.
+ *
+ * @param string|array input name or an array of HTML attributes
+ * @param string input value, when using a name
+ * @param string a string to be attached to the end of the attributes
+ * @return string
+ */
+ public static function hidden($data, $value = '', $extra = '')
+ {
+ if ( ! is_array($data))
+ {
+ $data = array('name' => $data);
+ }
+
+ $data['type'] = 'hidden';
+
+ return form::input($data, $value, $extra);
+ }
+
+ /**
+ * Creates an HTML form input tag. Defaults to a text type.
+ *
+ * @param string|array input name or an array of HTML attributes
+ * @param string input value, when using a name
+ * @param string a string to be attached to the end of the attributes
+ * @return string
+ */
+ public static function input($data, $value = '', $extra = '')
+ {
+ if ( ! is_array($data))
+ {
+ $data = array('name' => $data);
+ }
+
+ // Type and value are required attributes
+ $data += array
+ (
+ 'type' => 'text',
+ 'value' => $value
+ );
+
+ return '<input'.form::attributes($data).' '.$extra.' />';
+ }
+
+ /**
+ * Creates a HTML form password input tag.
+ *
+ * @param string|array input name or an array of HTML attributes
+ * @param string input value, when using a name
+ * @param string a string to be attached to the end of the attributes
+ * @return string
+ */
+ public static function password($data, $value = '', $extra = '')
+ {
+ if ( ! is_array($data))
+ {
+ $data = array('name' => $data);
+ }
+
+ $data['type'] = 'password';
+
+ return form::input($data, $value, $extra);
+ }
+
+ /**
+ * Creates an HTML form upload input tag.
+ *
+ * @param string|array input name or an array of HTML attributes
+ * @param string input value, when using a name
+ * @param string a string to be attached to the end of the attributes
+ * @return string
+ */
+ public static function upload($data, $value = '', $extra = '')
+ {
+ if ( ! is_array($data))
+ {
+ $data = array('name' => $data);
+ }
+
+ $data['type'] = 'file';
+
+ return form::input($data, $value, $extra);
+ }
+
+ /**
+ * Creates an HTML form textarea tag.
+ *
+ * @param string|array input name or an array of HTML attributes
+ * @param string input value, when using a name
+ * @param string a string to be attached to the end of the attributes
+ * @param boolean encode existing entities
+ * @return string
+ */
+ public static function textarea($data, $value = '', $extra = '', $double_encode = TRUE)
+ {
+ if ( ! is_array($data))
+ {
+ $data = array('name' => $data);
+ }
+
+ if ( ! isset($data['rows']))
+ {
+ $data['rows'] = '';
+ }
+
+ if ( ! isset($data['cols']))
+ {
+ $data['cols'] = '';
+ }
+
+ // Use the value from $data if possible, or use $value
+ $value = isset($data['value']) ? $data['value'] : $value;
+
+ // Value is not part of the attributes
+ unset($data['value']);
+
+ return '<textarea'.form::attributes($data, 'textarea').' '.$extra.'>'.htmlspecialchars($value, ENT_QUOTES, Kohana::CHARSET, $double_encode).'</textarea>';
+ }
+
+ /**
+ * Creates an HTML form select tag, or "dropdown menu".
+ *
+ * @param string|array input name or an array of HTML attributes
+ * @param array select options, when using a name
+ * @param string|array option key(s) that should be selected by default
+ * @param string a string to be attached to the end of the attributes
+ * @return string
+ */
+ public static function dropdown($data, $options = NULL, $selected = NULL, $extra = '')
+ {
+ if ( ! is_array($data))
+ {
+ $data = array('name' => $data);
+ }
+ else
+ {
+ if (isset($data['options']))
+ {
+ // Use data options
+ $options = $data['options'];
+ }
+
+ if (isset($data['selected']))
+ {
+ // Use data selected
+ $selected = $data['selected'];
+ }
+ }
+
+ if (is_array($selected))
+ {
+ // Multi-select box
+ $data['multiple'] = 'multiple';
+ }
+ else
+ {
+ // Single selection (but converted to an array)
+ $selected = array($selected);
+ }
+
+ $input = '<select'.form::attributes($data, 'select').' '.$extra.'>'."\n";
+ foreach ((array) $options as $key => $val)
+ {
+ // Key should always be a string
+ $key = (string) $key;
+
+ if (is_array($val))
+ {
+ $input .= '<optgroup label="'.$key.'">'."\n";
+ foreach ($val as $inner_key => $inner_val)
+ {
+ // Inner key should always be a string
+ $inner_key = (string) $inner_key;
+
+ $sel = in_array($inner_key, $selected) ? ' selected="selected"' : '';
+ $input .= '<option value="'.$inner_key.'"'.$sel.'>'.htmlspecialchars($inner_val, ENT_QUOTES, Kohana::CHARSET, FALSE).'</option>'."\n";
+ }
+ $input .= '</optgroup>'."\n";
+ }
+ else
+ {
+ $sel = in_array($key, $selected) ? ' selected="selected"' : '';
+ $input .= '<option value="'.$key.'"'.$sel.'>'.htmlspecialchars($val, ENT_QUOTES, Kohana::CHARSET, FALSE).'</option>'."\n";
+ }
+ }
+ $input .= '</select>';
+
+ return $input;
+ }
+
+ /**
+ * Creates an HTML form checkbox input tag.
+ *
+ * @param string|array input name or an array of HTML attributes
+ * @param string input value, when using a name
+ * @param boolean make the checkbox checked by default
+ * @param string a string to be attached to the end of the attributes
+ * @return string
+ */
+ public static function checkbox($data, $value = '', $checked = FALSE, $extra = '')
+ {
+ if ( ! is_array($data))
+ {
+ $data = array('name' => $data);
+ }
+
+ $data['type'] = 'checkbox';
+
+ if ($checked == TRUE OR (isset($data['checked']) AND $data['checked'] == TRUE))
+ {
+ $data['checked'] = 'checked';
+ }
+ else
+ {
+ unset($data['checked']);
+ }
+
+ return form::input($data, $value, $extra);
+ }
+
+ /**
+ * Creates an HTML form radio input tag.
+ *
+ * @param string|array input name or an array of HTML attributes
+ * @param string input value, when using a name
+ * @param boolean make the radio selected by default
+ * @param string a string to be attached to the end of the attributes
+ * @return string
+ */
+ public static function radio($data = '', $value = '', $checked = FALSE, $extra = '')
+ {
+ if ( ! is_array($data))
+ {
+ $data = array('name' => $data);
+ }
+
+ $data['type'] = 'radio';
+
+ if ($checked == TRUE OR (isset($data['checked']) AND $data['checked'] == TRUE))
+ {
+ $data['checked'] = 'checked';
+ }
+ else
+ {
+ unset($data['checked']);
+ }
+
+ return form::input($data, $value, $extra);
+ }
+
+ /**
+ * Creates an HTML form submit input tag.
+ *
+ * @param string|array input name or an array of HTML attributes
+ * @param string input value, when using a name
+ * @param string a string to be attached to the end of the attributes
+ * @return string
+ */
+ public static function submit($data = '', $value = '', $extra = '')
+ {
+ if ( ! is_array($data))
+ {
+ $data = array('name' => $data);
+ }
+
+ if (empty($data['name']))
+ {
+ // Remove the name if it is empty
+ unset($data['name']);
+ }
+
+ $data['type'] = 'submit';
+
+ return form::input($data, $value, $extra);
+ }
+
+ /**
+ * Creates an HTML form button input tag.
+ *
+ * @param string|array input name or an array of HTML attributes
+ * @param string input value, when using a name
+ * @param string a string to be attached to the end of the attributes
+ * @return string
+ */
+ public static function button($data = '', $value = '', $extra = '')
+ {
+ if ( ! is_array($data))
+ {
+ $data = array('name' => $data);
+ }
+
+ if (empty($data['name']))
+ {
+ // Remove the name if it is empty
+ unset($data['name']);
+ }
+
+ if (isset($data['value']) AND empty($value))
+ {
+ $value = arr::remove('value', $data);
+ }
+
+ return '<button'.form::attributes($data, 'button').' '.$extra.'>'.$value.'</button>';
+ }
+
+ /**
+ * Creates an HTML form label tag.
+ *
+ * @param string|array label "for" name or an array of HTML attributes
+ * @param string label text or HTML
+ * @param string a string to be attached to the end of the attributes
+ * @return string
+ */
+ public static function label($data = '', $text = NULL, $extra = '')
+ {
+ if ( ! is_array($data))
+ {
+ if (is_string($data))
+ {
+ // Specify the input this label is for
+ $data = array('for' => $data);
+ }
+ else
+ {
+ // No input specified
+ $data = array();
+ }
+ }
+
+ if ($text === NULL AND isset($data['for']))
+ {
+ // Make the text the human-readable input name
+ $text = ucwords(inflector::humanize($data['for']));
+ }
+
+ return '<label'.form::attributes($data).' '.$extra.'>'.$text.'</label>';
+ }
+
+ /**
+ * Sorts a key/value array of HTML attributes, putting form attributes first,
+ * and returns an attribute string.
+ *
+ * @param array HTML attributes array
+ * @return string
+ */
+ public static function attributes($attr, $type = NULL)
+ {
+ if (empty($attr))
+ return '';
+
+ $order = array
+ (
+ 'action',
+ 'method',
+ 'type',
+ 'id',
+ 'name',
+ 'value',
+ 'src',
+ 'size',
+ 'maxlength',
+ 'rows',
+ 'cols',
+ 'accept',
+ 'tabindex',
+ 'accesskey',
+ 'align',
+ 'alt',
+ 'title',
+ 'class',
+ 'style',
+ 'selected',
+ 'checked',
+ 'readonly',
+ 'disabled'
+ );
+
+ $sorted = array();
+ foreach ($order as $key)
+ {
+ if (isset($attr[$key]))
+ {
+ // Move the attribute to the sorted array
+ $sorted[$key] = $attr[$key];
+
+ // Remove the attribute from unsorted array
+ unset($attr[$key]);
+ }
+ }
+
+ // Combine the sorted and unsorted attributes and create an HTML string
+ return html::attributes(array_merge($sorted, $attr));
+ }
+
+} // End form
diff --git a/system/helpers/format.php b/system/helpers/format.php
new file mode 100644
index 0000000..7494046
--- /dev/null
+++ b/system/helpers/format.php
@@ -0,0 +1,112 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Format helper class.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class format_Core {
+
+ /**
+ * Formats a number according to the current locale.
+ *
+ * @param float
+ * @param int|boolean number of fractional digits or TRUE to use the locale default
+ * @return string
+ */
+ public static function number($number, $decimals = 0)
+ {
+ $locale = localeconv();
+
+ if ($decimals === TRUE)
+ return number_format($number, $locale['frac_digits'], $locale['decimal_point'], $locale['thousands_sep']);
+
+ return number_format($number, $decimals, $locale['decimal_point'], $locale['thousands_sep']);
+ }
+
+ /**
+ * Formats a phone number according to the specified format.
+ *
+ * @param string phone number
+ * @param string format string
+ * @return string
+ */
+ public static function phone($number, $format = '3-3-4')
+ {
+ // Get rid of all non-digit characters in number string
+ $number_clean = preg_replace('/\D+/', '', (string) $number);
+
+ // Array of digits we need for a valid format
+ $format_parts = preg_split('/[^1-9][^0-9]*/', $format, -1, PREG_SPLIT_NO_EMPTY);
+
+ // Number must match digit count of a valid format
+ if (strlen($number_clean) !== array_sum($format_parts))
+ return $number;
+
+ // Build regex
+ $regex = '(\d{'.implode('})(\d{', $format_parts).'})';
+
+ // Build replace string
+ for ($i = 1, $c = count($format_parts); $i <= $c; $i++)
+ {
+ $format = preg_replace('/(?<!\$)[1-9][0-9]*/', '\$'.$i, $format, 1);
+ }
+
+ // Hocus pocus!
+ return preg_replace('/^'.$regex.'$/', $format, $number_clean);
+ }
+
+ /**
+ * Formats a URL to contain a protocol at the beginning.
+ *
+ * @param string possibly incomplete URL
+ * @return string
+ */
+ public static function url($str = '')
+ {
+ // Clear protocol-only strings like "http://"
+ if ($str === '' OR substr($str, -3) === '://')
+ return '';
+
+ // If no protocol given, prepend "http://" by default
+ if (strpos($str, '://') === FALSE)
+ return 'http://'.$str;
+
+ // Return the original URL
+ return $str;
+ }
+
+ /**
+ * Normalizes a hexadecimal HTML color value. All values will be converted
+ * to lowercase, have a "#" prepended and contain six characters.
+ *
+ * @param string hexadecimal HTML color value
+ * @return string
+ */
+ public static function color($str = '')
+ {
+ // Reject invalid values
+ if ( ! valid::color($str))
+ return '';
+
+ // Convert to lowercase
+ $str = strtolower($str);
+
+ // Prepend "#"
+ if ($str[0] !== '#')
+ {
+ $str = '#'.$str;
+ }
+
+ // Expand short notation
+ if (strlen($str) === 4)
+ {
+ $str = '#'.$str[1].$str[1].$str[2].$str[2].$str[3].$str[3];
+ }
+
+ return $str;
+ }
+
+} // End format \ No newline at end of file
diff --git a/system/helpers/html.php b/system/helpers/html.php
new file mode 100644
index 0000000..c16031d
--- /dev/null
+++ b/system/helpers/html.php
@@ -0,0 +1,364 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * HTML helper class.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class html_Core {
+
+ // Enable or disable automatic setting of target="_blank"
+ public static $windowed_urls = FALSE;
+
+ /**
+ * Convert special characters to HTML entities
+ *
+ * @param string string to convert
+ * @param boolean encode existing entities
+ * @return string
+ */
+ public static function chars($str, $double_encode = TRUE)
+ {
+ // Return HTML entities using the Kohana charset
+ return htmlspecialchars($str, ENT_QUOTES, Kohana::CHARSET, $double_encode);
+ }
+
+ /**
+ * Create HTML link anchors.
+ *
+ * @param string URL or URI string
+ * @param string link text
+ * @param array HTML anchor attributes
+ * @param string non-default protocol, eg: https
+ * @param boolean option to escape the title that is output
+ * @return string
+ */
+ public static function anchor($uri, $title = NULL, $attributes = NULL, $protocol = NULL, $escape_title = FALSE)
+ {
+ if ($uri === '')
+ {
+ $site_url = url::base(FALSE);
+ }
+ elseif (strpos($uri, '#') === 0)
+ {
+ // This is an id target link, not a URL
+ $site_url = $uri;
+ }
+ elseif (strpos($uri, '://') === FALSE)
+ {
+ $site_url = url::site($uri, $protocol);
+ }
+ else
+ {
+ if (html::$windowed_urls === TRUE AND empty($attributes['target']))
+ {
+ $attributes['target'] = '_blank';
+ }
+
+ $site_url = $uri;
+ }
+
+ return
+ // Parsed URL
+ '<a href="'.htmlspecialchars($site_url, ENT_QUOTES, Kohana::CHARSET, FALSE).'"'
+ // Attributes empty? Use an empty string
+ .(is_array($attributes) ? html::attributes($attributes) : '').'>'
+ // Title empty? Use the parsed URL
+ .($escape_title ? htmlspecialchars((($title === NULL) ? $site_url : $title), ENT_QUOTES, Kohana::CHARSET, FALSE) : (($title === NULL) ? $site_url : $title)).'</a>';
+ }
+
+ /**
+ * Creates an HTML anchor to a file.
+ *
+ * @param string name of file to link to
+ * @param string link text
+ * @param array HTML anchor attributes
+ * @param string non-default protocol, eg: ftp
+ * @return string
+ */
+ public static function file_anchor($file, $title = NULL, $attributes = NULL, $protocol = NULL)
+ {
+ return
+ // Base URL + URI = full URL
+ '<a href="'.htmlspecialchars(url::base(FALSE, $protocol).$file, ENT_QUOTES, Kohana::CHARSET, FALSE).'"'
+ // Attributes empty? Use an empty string
+ .(is_array($attributes) ? html::attributes($attributes) : '').'>'
+ // Title empty? Use the filename part of the URI
+ .(($title === NULL) ? end(explode('/', $file)) : $title) .'</a>';
+ }
+
+ /**
+ * Generates an obfuscated version of an email address.
+ *
+ * @param string email address
+ * @return string
+ */
+ public static function email($email)
+ {
+ $safe = '';
+ foreach (str_split($email) as $letter)
+ {
+ switch (($letter === '@') ? rand(1, 2) : rand(1, 3))
+ {
+ // HTML entity code
+ case 1: $safe .= '&#'.ord($letter).';'; break;
+ // Hex character code
+ case 2: $safe .= '&#x'.dechex(ord($letter)).';'; break;
+ // Raw (no) encoding
+ case 3: $safe .= $letter;
+ }
+ }
+
+ return $safe;
+ }
+
+ /**
+ * Creates an email anchor.
+ *
+ * @param string email address to send to
+ * @param string link text
+ * @param array HTML anchor attributes
+ * @return string
+ */
+ public static function mailto($email, $title = NULL, $attributes = NULL)
+ {
+ if (empty($email))
+ return $title;
+
+ // Remove the subject or other parameters that do not need to be encoded
+ if (strpos($email, '?') !== FALSE)
+ {
+ // Extract the parameters from the email address
+ list ($email, $params) = explode('?', $email, 2);
+
+ // Make the params into a query string, replacing spaces
+ $params = '?'.str_replace(' ', '%20', $params);
+ }
+ else
+ {
+ // No parameters
+ $params = '';
+ }
+
+ // Obfuscate email address
+ $safe = html::email($email);
+
+ // Title defaults to the encoded email address
+ empty($title) and $title = $safe;
+
+ // Parse attributes
+ empty($attributes) or $attributes = html::attributes($attributes);
+
+ // Encoded start of the href="" is a static encoded version of 'mailto:'
+ return '<a href="&#109;&#097;&#105;&#108;&#116;&#111;&#058;'.$safe.$params.'"'.$attributes.'>'.$title.'</a>';
+ }
+
+ /**
+ * Generate a "breadcrumb" list of anchors representing the URI.
+ *
+ * @param array segments to use as breadcrumbs, defaults to using Router::$segments
+ * @return string
+ */
+ public static function breadcrumb($segments = NULL)
+ {
+ empty($segments) and $segments = Router::$segments;
+
+ $array = array();
+ while ($segment = array_pop($segments))
+ {
+ $array[] = html::anchor
+ (
+ // Complete URI for the URL
+ implode('/', $segments).'/'.$segment,
+ // Title for the current segment
+ ucwords(inflector::humanize($segment))
+ );
+ }
+
+ // Retrun the array of all the segments
+ return array_reverse($array);
+ }
+
+ /**
+ * Creates a meta tag.
+ *
+ * @param string|array tag name, or an array of tags
+ * @param string tag "content" value
+ * @return string
+ */
+ public static function meta($tag, $value = NULL)
+ {
+ if (is_array($tag))
+ {
+ $tags = array();
+ foreach ($tag as $t => $v)
+ {
+ // Build each tag and add it to the array
+ $tags[] = html::meta($t, $v);
+ }
+
+ // Return all of the tags as a string
+ return implode("\n", $tags);
+ }
+
+ // Set the meta attribute value
+ $attr = in_array(strtolower($tag), Kohana::config('http.meta_equiv')) ? 'http-equiv' : 'name';
+
+ return '<meta '.$attr.'="'.$tag.'" content="'.$value.'" />';
+ }
+
+ /**
+ * Creates a stylesheet link.
+ *
+ * @param string|array filename, or array of filenames to match to array of medias
+ * @param string|array media type of stylesheet, or array to match filenames
+ * @param boolean include the index_page in the link
+ * @return string
+ */
+ public static function stylesheet($style, $media = FALSE, $index = FALSE)
+ {
+ return html::link($style, 'stylesheet', 'text/css', $media, $index);
+ }
+
+ /**
+ * Creates a link tag.
+ *
+ * @param string|array filename
+ * @param string|array relationship
+ * @param string|array mimetype
+ * @param string|array specifies on what device the document will be displayed
+ * @param boolean include the index_page in the link
+ * @return string
+ */
+ public static function link($href, $rel, $type, $media = FALSE, $index = FALSE)
+ {
+ $compiled = '';
+
+ if (is_array($href))
+ {
+ foreach ($href as $_href)
+ {
+ $_rel = is_array($rel) ? array_shift($rel) : $rel;
+ $_type = is_array($type) ? array_shift($type) : $type;
+ $_media = is_array($media) ? array_shift($media) : $media;
+
+ $compiled .= html::link($_href, $_rel, $_type, $_media, $index);
+ }
+ }
+ else
+ {
+ if (strpos($href, '://') === FALSE)
+ {
+ // Make the URL absolute
+ $href = url::base($index).$href;
+ }
+
+ $attr = array
+ (
+ 'rel' => $rel,
+ 'type' => $type,
+ 'href' => $href,
+ );
+
+ if ( ! empty($media))
+ {
+ // Add the media type to the attributes
+ $attr['media'] = $media;
+ }
+
+ $compiled = '<link'.html::attributes($attr).' />';
+ }
+
+ return $compiled."\n";
+ }
+
+ /**
+ * Creates a script link.
+ *
+ * @param string|array filename
+ * @param boolean include the index_page in the link
+ * @return string
+ */
+ public static function script($script, $index = FALSE)
+ {
+ $compiled = '';
+
+ if (is_array($script))
+ {
+ foreach ($script as $name)
+ {
+ $compiled .= html::script($name, $index);
+ }
+ }
+ else
+ {
+ if (strpos($script, '://') === FALSE)
+ {
+ // Add the suffix only when it's not already present
+ $script = url::base((bool) $index).$script;
+ }
+
+ $compiled = '<script type="text/javascript" src="'.$script.'"></script>';
+ }
+
+ return $compiled."\n";
+ }
+
+ /**
+ * Creates a image link.
+ *
+ * @param string image source, or an array of attributes
+ * @param string|array image alt attribute, or an array of attributes
+ * @param boolean include the index_page in the link
+ * @return string
+ */
+ public static function image($src = NULL, $alt = NULL, $index = FALSE)
+ {
+ // Create attribute list
+ $attributes = is_array($src) ? $src : array('src' => $src);
+
+ if (is_array($alt))
+ {
+ $attributes += $alt;
+ }
+ elseif ( ! empty($alt))
+ {
+ // Add alt to attributes
+ $attributes['alt'] = $alt;
+ }
+
+ if (strpos($attributes['src'], '://') === FALSE)
+ {
+ // Make the src attribute into an absolute URL
+ $attributes['src'] = url::base($index).$attributes['src'];
+ }
+
+ return '<img'.html::attributes($attributes).' />';
+ }
+
+ /**
+ * Compiles an array of HTML attributes into an attribute string.
+ *
+ * @param string|array array of attributes
+ * @return string
+ */
+ public static function attributes($attrs)
+ {
+ if (empty($attrs))
+ return '';
+
+ if (is_string($attrs))
+ return ' '.$attrs;
+
+ $compiled = '';
+ foreach ($attrs as $key => $val)
+ {
+ $compiled .= ' '.$key.'="'.htmlspecialchars($val, ENT_QUOTES, Kohana::CHARSET).'"';
+ }
+
+ return $compiled;
+ }
+
+} // End html
diff --git a/system/helpers/inflector.php b/system/helpers/inflector.php
new file mode 100644
index 0000000..e723707
--- /dev/null
+++ b/system/helpers/inflector.php
@@ -0,0 +1,252 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Inflector helper class.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class inflector_Core {
+
+ // Cached inflections
+ protected static $cache = array();
+
+ // Uncountable and irregular words
+ protected static $uncountable;
+ protected static $irregular;
+
+ /**
+ * Checks if a word is defined as uncountable.
+ *
+ * @param string word to check
+ * @return boolean
+ */
+ public static function uncountable($str)
+ {
+ if (inflector::$uncountable === NULL)
+ {
+ // Cache uncountables
+ inflector::$uncountable = Kohana::config('inflector.uncountable');
+
+ // Make uncountables mirroed
+ inflector::$uncountable = array_combine(inflector::$uncountable, inflector::$uncountable);
+ }
+
+ return isset(inflector::$uncountable[strtolower($str)]);
+ }
+
+ /**
+ * Makes a plural word singular.
+ *
+ * @param string word to singularize
+ * @param integer number of things
+ * @return string
+ */
+ public static function singular($str, $count = NULL) {
+ $parts = explode('_', $str);
+
+ $last = inflector::_singular(array_pop($parts), $count);
+
+ $pre = implode('_', $parts);
+ if (strlen($pre))
+ $pre .= '_';
+
+ return $pre.$last;
+ }
+
+
+ /**
+ * Makes a plural word singular.
+ *
+ * @param string word to singularize
+ * @param integer number of things
+ * @return string
+ */
+ public static function _singular($str, $count = NULL)
+ {
+ // Remove garbage
+ $str = strtolower(trim($str));
+
+ if (is_string($count))
+ {
+ // Convert to integer when using a digit string
+ $count = (int) $count;
+ }
+
+ // Do nothing with a single count
+ if ($count === 0 OR $count > 1)
+ return $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('inflector.irregular');
+ }
+
+ if ($irregular = array_search($str, inflector::$irregular))
+ {
+ $str = $irregular;
+ }
+ 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))
+ {
+ $str = substr($str, 0, -3).'y';
+ }
+ elseif (substr($str, -1) === 's' AND substr($str, -2) !== 'ss')
+ {
+ $str = substr($str, 0, -1);
+ }
+
+ return inflector::$cache[$key] = $str;
+ }
+
+ /**
+ * Makes a singular word plural.
+ *
+ * @param string word to pluralize
+ * @return string
+ */
+ public static function plural($str, $count = NULL)
+ {
+ if ( ! $str)
+ return $str;
+
+ $parts = explode('_', $str);
+
+ $last = inflector::_plural(array_pop($parts), $count);
+
+ $pre = implode('_', $parts);
+ if (strlen($pre))
+ $pre .= '_';
+
+ return $pre.$last;
+ }
+
+
+ /**
+ * Makes a singular word plural.
+ *
+ * @param string word to pluralize
+ * @return string
+ */
+ public static function _plural($str, $count = NULL)
+ {
+ // Remove garbage
+ $str = strtolower(trim($str));
+
+ if (is_string($count))
+ {
+ // Convert to integer when using a digit string
+ $count = (int) $count;
+ }
+
+ // Do nothing with singular
+ if ($count === 1)
+ return $str;
+
+ // Cache key name
+ $key = 'plural_'.$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('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';
+ }
+
+ // Set the cache and return
+ return inflector::$cache[$key] = $str;
+ }
+
+ /**
+ * Makes a word possessive.
+ *
+ * @param string word to to make possessive
+ * @return string
+ */
+ public static function possessive($string)
+ {
+ $length = strlen($string);
+
+ if (substr($string, $length - 1, $length) == 's')
+ {
+ return $string.'\'';
+ }
+
+ return $string.'\'s';
+ }
+
+ /**
+ * Makes a phrase camel case.
+ *
+ * @param string 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);
+ }
+
+ /**
+ * Makes a phrase underscored instead of spaced.
+ *
+ * @param string phrase to underscore
+ * @return string
+ */
+ public static function underscore($str)
+ {
+ return trim(preg_replace('/[\s_]+/', '_', $str), '_');
+ }
+
+ /**
+ * Makes an underscored or dashed phrase human-readable.
+ *
+ * @param string phrase to make human-readable
+ * @return string
+ */
+ public static function humanize($str)
+ {
+ return trim(preg_replace('/[_-\s]+/', ' ', $str));
+ }
+
+} // End inflector \ No newline at end of file
diff --git a/system/helpers/num.php b/system/helpers/num.php
new file mode 100644
index 0000000..c9e9843
--- /dev/null
+++ b/system/helpers/num.php
@@ -0,0 +1,24 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Number helper class.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class num_Core {
+
+ /**
+ * Round a number to the nearest nth
+ *
+ * @param integer number to round
+ * @param integer number to round to
+ * @return integer
+ */
+ public static function round($number, $nearest = 5)
+ {
+ return round($number / $nearest) * $nearest;
+ }
+
+} // End num \ No newline at end of file
diff --git a/system/helpers/remote.php b/system/helpers/remote.php
new file mode 100644
index 0000000..e6edb9c
--- /dev/null
+++ b/system/helpers/remote.php
@@ -0,0 +1,64 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Remote url/file helper.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class remote_Core {
+
+ public static function status($url)
+ {
+ if ( ! valid::url($url))
+ return FALSE;
+
+ // Get the hostname and path
+ $url = parse_url($url);
+
+ if (empty($url['path']))
+ {
+ // Request the root document
+ $url['path'] = '/';
+ }
+
+ // Open a remote connection
+ $remote = fsockopen($url['host'], 80, $errno, $errstr, 5);
+
+ if ( ! is_resource($remote))
+ return FALSE;
+
+ // Set CRLF
+ $CRLF = "\r\n";
+
+ // Send request
+ fwrite($remote, 'HEAD '.$url['path'].(isset($url['query']) ? '?'.$url['query'] : '').' HTTP/1.0'.$CRLF);
+ fwrite($remote, 'Host: '.$url['host'].$CRLF);
+ fwrite($remote, 'Connection: close'.$CRLF);
+ fwrite($remote, 'User-Agent: Kohana Framework (+http://kohanaphp.com/)'.$CRLF);
+
+ // Send one more CRLF to terminate the headers
+ fwrite($remote, $CRLF);
+
+ while ( ! feof($remote))
+ {
+ // Get the line
+ $line = trim(fgets($remote, 512));
+
+ if ($line !== '' AND preg_match('#^HTTP/1\.[01] (\d{3})#', $line, $matches))
+ {
+ // Response code found
+ $response = (int) $matches[1];
+
+ break;
+ }
+ }
+
+ // Close the connection
+ fclose($remote);
+
+ return isset($response) ? $response : FALSE;
+ }
+
+} // End remote
diff --git a/system/helpers/request.php b/system/helpers/request.php
new file mode 100644
index 0000000..c04a2fa
--- /dev/null
+++ b/system/helpers/request.php
@@ -0,0 +1,618 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Request helper class.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class request_Core {
+
+ // Possible HTTP methods
+ protected static $http_methods = array('get', 'head', 'options', 'post', 'put', 'delete');
+
+ // Character sets from client's HTTP Accept-Charset request header
+ protected static $accept_charsets;
+
+ // Content codings from client's HTTP Accept-Encoding request header
+ protected static $accept_encodings;
+
+ // Language tags from client's HTTP Accept-Language request header
+ protected static $accept_languages;
+
+ // Content types from client's HTTP Accept request header
+ protected static $accept_types;
+
+ // The current user agent and its parsed attributes
+ protected static $user_agent;
+
+ /**
+ * Returns the HTTP referrer, or the default if the referrer is not set.
+ *
+ * @param mixed default to return
+ * @param bool Remove base URL
+ * @return string
+ */
+ public static function referrer($default = FALSE, $remove_base = FALSE)
+ {
+ if ( ! empty($_SERVER['HTTP_REFERER']))
+ {
+ // Set referrer
+ $ref = $_SERVER['HTTP_REFERER'];
+
+ if ($remove_base === TRUE AND (strpos($ref, url::base(FALSE)) === 0))
+ {
+ // Remove the base URL from the referrer
+ $ref = substr($ref, strlen(url::base(FALSE)));
+ }
+ }
+
+ return isset($ref) ? $ref : $default;
+ }
+
+ /**
+ * Returns the current request protocol, based on $_SERVER['https']. In CLI
+ * mode, NULL will be returned.
+ *
+ * @return string
+ */
+ public static function protocol()
+ {
+ if (Kohana::$server_api === 'cli')
+ {
+ return NULL;
+ }
+ elseif ( ! empty($_SERVER['HTTPS']) AND $_SERVER['HTTPS'] === 'on')
+ {
+ return 'https';
+ }
+ else
+ {
+ return 'http';
+ }
+ }
+
+ /**
+ * Tests if the current request is an AJAX request by checking the X-Requested-With HTTP
+ * request header that most popular JS frameworks now set for AJAX calls.
+ *
+ * @return boolean
+ */
+ public static function is_ajax()
+ {
+ return (isset($_SERVER['HTTP_X_REQUESTED_WITH']) AND strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest');
+ }
+
+ /**
+ * Returns current request method.
+ *
+ * @throws Kohana_Exception in case of an unknown request method
+ * @return string
+ */
+ public static function method()
+ {
+ $method = strtolower($_SERVER['REQUEST_METHOD']);
+
+ if ( ! in_array($method, request::$http_methods))
+ throw new Kohana_Exception('Invalid request method :method:', array(':method:' => $method));
+
+ return $method;
+ }
+
+ /**
+ * Retrieves current user agent information
+ * keys: browser, version, platform, mobile, robot
+ *
+ * @param string key
+ * @return mixed NULL or the parsed value
+ */
+ public static function user_agent($key = 'agent')
+ {
+ // Retrieve raw user agent without parsing
+ if ($key === 'agent')
+ {
+ if (request::$user_agent === NULL)
+ return request::$user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? trim($_SERVER['HTTP_USER_AGENT']) : '';
+
+ if (is_array(request::$user_agent))
+ return request::$user_agent['agent'];
+
+ return request::$user_agent;
+ }
+
+ if ( ! is_array(request::$user_agent))
+ {
+ request::$user_agent = array();
+ request::$user_agent['agent'] = isset($_SERVER['HTTP_USER_AGENT']) ? trim($_SERVER['HTTP_USER_AGENT']) : '';
+
+ // Parse the user agent and extract basic information
+ foreach (Kohana::config('user_agents') as $type => $data)
+ {
+ foreach ($data as $fragment => $name)
+ {
+ if (stripos(request::$user_agent['agent'], $fragment) !== FALSE)
+ {
+ if ($type === 'browser' AND preg_match('|'.preg_quote($fragment).'[^0-9.]*+([0-9.][0-9.a-z]*)|i', request::$user_agent['agent'], $match))
+ {
+ // Set the browser version
+ request::$user_agent['version'] = $match[1];
+ }
+
+ // Set the agent name
+ request::$user_agent[$type] = $name;
+ break;
+ }
+ }
+ }
+ }
+
+ return isset(request::$user_agent[$key]) ? request::$user_agent[$key] : NULL;
+ }
+
+ /**
+ * Returns boolean of whether client accepts content type.
+ *
+ * @param string content type
+ * @param boolean set to TRUE to disable wildcard checking
+ * @return boolean
+ */
+ public static function accepts($type = NULL, $explicit_check = FALSE)
+ {
+ request::parse_accept_content_header();
+
+ if ($type === NULL)
+ return request::$accept_types;
+
+ return (request::accepts_at_quality($type, $explicit_check) > 0);
+ }
+
+ /**
+ * Returns boolean indicating if the client accepts a charset
+ *
+ * @param string
+ * @return boolean
+ */
+ public static function accepts_charset($charset = NULL)
+ {
+ request::parse_accept_charset_header();
+
+ if ($charset === NULL)
+ return request::$accept_charsets;
+
+ return (request::accepts_charset_at_quality($charset) > 0);
+ }
+
+ /**
+ * Returns boolean indicating if the client accepts an encoding
+ *
+ * @param string
+ * @param boolean set to TRUE to disable wildcard checking
+ * @return boolean
+ */
+ public static function accepts_encoding($encoding = NULL, $explicit_check = FALSE)
+ {
+ request::parse_accept_encoding_header();
+
+ if ($encoding === NULL)
+ return request::$accept_encodings;
+
+ return (request::accepts_encoding_at_quality($encoding, $explicit_check) > 0);
+ }
+
+ /**
+ * Returns boolean indicating if the client accepts a language tag
+ *
+ * @param string language tag
+ * @param boolean set to TRUE to disable prefix and wildcard checking
+ * @return boolean
+ */
+ public static function accepts_language($tag = NULL, $explicit_check = FALSE)
+ {
+ request::parse_accept_language_header();
+
+ if ($tag === NULL)
+ return request::$accept_languages;
+
+ return (request::accepts_language_at_quality($tag, $explicit_check) > 0);
+ }
+
+ /**
+ * Compare the q values for given array of content types and return the one with the highest value.
+ * If items are found to have the same q value, the first one encountered in the given array wins.
+ * If all items in the given array have a q value of 0, FALSE is returned.
+ *
+ * @param array content types
+ * @param boolean set to TRUE to disable wildcard checking
+ * @return mixed string mime type with highest q value, FALSE if none of the given types are accepted
+ */
+ public static function preferred_accept($types, $explicit_check = FALSE)
+ {
+ $max_q = 0;
+ $preferred = FALSE;
+
+ foreach ($types as $type)
+ {
+ $q = request::accepts_at_quality($type, $explicit_check);
+
+ if ($q > $max_q)
+ {
+ $max_q = $q;
+ $preferred = $type;
+ }
+ }
+
+ return $preferred;
+ }
+
+ /**
+ * Compare the q values for a given array of character sets and return the
+ * one with the highest value. If items are found to have the same q value,
+ * the first one encountered takes precedence. If all items in the given
+ * array have a q value of 0, FALSE is returned.
+ *
+ * @param array character sets
+ * @return mixed
+ */
+ public static function preferred_charset($charsets)
+ {
+ $max_q = 0;
+ $preferred = FALSE;
+
+ foreach ($charsets as $charset)
+ {
+ $q = request::accepts_charset_at_quality($charset);
+
+ if ($q > $max_q)
+ {
+ $max_q = $q;
+ $preferred = $charset;
+ }
+ }
+
+ return $preferred;
+ }
+
+ /**
+ * Compare the q values for a given array of encodings and return the one with
+ * the highest value. If items are found to have the same q value, the first
+ * one encountered takes precedence. If all items in the given array have
+ * a q value of 0, FALSE is returned.
+ *
+ * @param array encodings
+ * @param boolean set to TRUE to disable wildcard checking
+ * @return mixed
+ */
+ public static function preferred_encoding($encodings, $explicit_check = FALSE)
+ {
+ $max_q = 0;
+ $preferred = FALSE;
+
+ foreach ($encodings as $encoding)
+ {
+ $q = request::accepts_encoding_at_quality($encoding, $explicit_check);
+
+ if ($q > $max_q)
+ {
+ $max_q = $q;
+ $preferred = $encoding;
+ }
+ }
+
+ return $preferred;
+ }
+
+ /**
+ * Compare the q values for a given array of language tags and return the
+ * one with the highest value. If items are found to have the same q value,
+ * the first one encountered takes precedence. If all items in the given
+ * array have a q value of 0, FALSE is returned.
+ *
+ * @param array language tags
+ * @param boolean set to TRUE to disable prefix and wildcard checking
+ * @return mixed
+ */
+ public static function preferred_language($tags, $explicit_check = FALSE)
+ {
+ $max_q = 0;
+ $preferred = FALSE;
+
+ foreach ($tags as $tag)
+ {
+ $q = request::accepts_language_at_quality($tag, $explicit_check);
+
+ if ($q > $max_q)
+ {
+ $max_q = $q;
+ $preferred = $tag;
+ }
+ }
+
+ return $preferred;
+ }
+
+ /**
+ * Returns quality factor at which the client accepts content type
+ *
+ * @param string content type (e.g. "image/jpg", "jpg")
+ * @param boolean set to TRUE to disable wildcard checking
+ * @return integer|float
+ */
+ public static function accepts_at_quality($type, $explicit_check = FALSE)
+ {
+ request::parse_accept_content_header();
+
+ // Normalize type
+ $type = strtolower($type);
+
+ // General content type (e.g. "jpg")
+ if (strpos($type, '/') === FALSE)
+ {
+ // Don't accept anything by default
+ $q = 0;
+
+ // Look up relevant mime types
+ foreach ((array) Kohana::config('mimes.'.$type) as $type)
+ {
+ $q2 = request::accepts_at_quality($type, $explicit_check);
+ $q = ($q2 > $q) ? $q2 : $q;
+ }
+
+ return $q;
+ }
+
+ // Content type with subtype given (e.g. "image/jpg")
+ $type = explode('/', $type, 2);
+
+ // Exact match
+ if (isset(request::$accept_types[$type[0]][$type[1]]))
+ return request::$accept_types[$type[0]][$type[1]];
+
+ if ($explicit_check === FALSE)
+ {
+ // Wildcard match
+ if (isset(request::$accept_types[$type[0]]['*']))
+ return request::$accept_types[$type[0]]['*'];
+
+ // Catch-all wildcard match
+ if (isset(request::$accept_types['*']['*']))
+ return request::$accept_types['*']['*'];
+ }
+
+ // Content type not accepted
+ return 0;
+ }
+
+ /**
+ * Returns quality factor at which the client accepts a charset
+ *
+ * @param string charset (e.g., "ISO-8859-1", "utf-8")
+ * @return integer|float
+ */
+ public static function accepts_charset_at_quality($charset)
+ {
+ request::parse_accept_charset_header();
+
+ // Normalize charset
+ $charset = strtolower($charset);
+
+ // Exact match
+ if (isset(request::$accept_charsets[$charset]))
+ return request::$accept_charsets[$charset];
+
+ if (isset(request::$accept_charsets['*']))
+ return request::$accept_charsets['*'];
+
+ if ($charset === 'iso-8859-1')
+ return 1;
+
+ return 0;
+ }
+
+ /**
+ * Returns quality factor at which the client accepts an encoding
+ *
+ * @param string encoding (e.g., "gzip", "deflate")
+ * @param boolean set to TRUE to disable wildcard checking
+ * @return integer|float
+ */
+ public static function accepts_encoding_at_quality($encoding, $explicit_check = FALSE)
+ {
+ request::parse_accept_encoding_header();
+
+ // Normalize encoding
+ $encoding = strtolower($encoding);
+
+ // Exact match
+ if (isset(request::$accept_encodings[$encoding]))
+ return request::$accept_encodings[$encoding];
+
+ if ($explicit_check === FALSE)
+ {
+ if (isset(request::$accept_encodings['*']))
+ return request::$accept_encodings['*'];
+
+ if ($encoding === 'identity')
+ return 1;
+ }
+
+ return 0;
+ }
+
+ /**
+ * Returns quality factor at which the client accepts a language
+ *
+ * @param string tag (e.g., "en", "en-us", "fr-ca")
+ * @param boolean set to TRUE to disable prefix and wildcard checking
+ * @return integer|float
+ */
+ public static function accepts_language_at_quality($tag, $explicit_check = FALSE)
+ {
+ request::parse_accept_language_header();
+
+ $tag = explode('-', strtolower($tag), 2);
+
+ if (isset(request::$accept_languages[$tag[0]]))
+ {
+ if (isset($tag[1]))
+ {
+ // Exact match
+ if (isset(request::$accept_languages[$tag[0]][$tag[1]]))
+ return request::$accept_languages[$tag[0]][$tag[1]];
+
+ // A prefix matches
+ if ($explicit_check === FALSE AND isset(request::$accept_languages[$tag[0]]['*']))
+ return request::$accept_languages[$tag[0]]['*'];
+ }
+ else
+ {
+ // No subtags
+ if (isset(request::$accept_languages[$tag[0]]['*']))
+ return request::$accept_languages[$tag[0]]['*'];
+ }
+ }
+
+ if ($explicit_check === FALSE AND isset(request::$accept_languages['*']))
+ return request::$accept_languages['*'];
+
+ return 0;
+ }
+
+ /**
+ * Parses a HTTP Accept or Accept-* header for q values
+ *
+ * @param string header data
+ * @return array
+ */
+ protected static function parse_accept_header($header)
+ {
+ $result = array();
+
+ // Remove linebreaks and parse the HTTP Accept header
+ foreach (explode(',', str_replace(array("\r", "\n"), '', strtolower($header))) as $entry)
+ {
+ // Explode each entry in content type and possible quality factor
+ $entry = explode(';', trim($entry), 2);
+
+ $q = (isset($entry[1]) AND preg_match('~\bq\s*+=\s*+([.0-9]+)~', $entry[1], $match)) ? (float) $match[1] : 1;
+
+ // Overwrite entries with a smaller q value
+ if ( ! isset($result[$entry[0]]) OR $q > $result[$entry[0]])
+ {
+ $result[$entry[0]] = $q;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Parses a client's HTTP Accept-Charset header
+ */
+ protected static function parse_accept_charset_header()
+ {
+ // Run this function just once
+ if (request::$accept_charsets !== NULL)
+ return;
+
+ // No HTTP Accept-Charset header found
+ if (empty($_SERVER['HTTP_ACCEPT_CHARSET']))
+ {
+ // Accept everything
+ request::$accept_charsets['*'] = 1;
+ }
+ else
+ {
+ request::$accept_charsets = request::parse_accept_header($_SERVER['HTTP_ACCEPT_CHARSET']);
+ }
+ }
+
+ /**
+ * Parses a client's HTTP Accept header
+ */
+ protected static function parse_accept_content_header()
+ {
+ // Run this function just once
+ if (request::$accept_types !== NULL)
+ return;
+
+ // No HTTP Accept header found
+ if (empty($_SERVER['HTTP_ACCEPT']))
+ {
+ // Accept everything
+ request::$accept_types['*']['*'] = 1;
+ }
+ else
+ {
+ request::$accept_types = array();
+
+ foreach (request::parse_accept_header($_SERVER['HTTP_ACCEPT']) as $type => $q)
+ {
+ // Explode each content type (e.g. "text/html")
+ $type = explode('/', $type, 2);
+
+ // Skip invalid content types
+ if ( ! isset($type[1]))
+ continue;
+
+ request::$accept_types[$type[0]][$type[1]] = $q;
+ }
+ }
+ }
+
+ /**
+ * Parses a client's HTTP Accept-Encoding header
+ */
+ protected static function parse_accept_encoding_header()
+ {
+ // Run this function just once
+ if (request::$accept_encodings !== NULL)
+ return;
+
+ // No HTTP Accept-Encoding header found
+ if ( ! isset($_SERVER['HTTP_ACCEPT_ENCODING']))
+ {
+ // Accept everything
+ request::$accept_encodings['*'] = 1;
+ }
+ elseif ($_SERVER['HTTP_ACCEPT_ENCODING'] === '')
+ {
+ // Accept only identity
+ request::$accept_encodings['identity'] = 1;
+ }
+ else
+ {
+ request::$accept_encodings = request::parse_accept_header($_SERVER['HTTP_ACCEPT_ENCODING']);
+ }
+ }
+
+ /**
+ * Parses a client's HTTP Accept-Language header
+ */
+ protected static function parse_accept_language_header()
+ {
+ // Run this function just once
+ if (request::$accept_languages !== NULL)
+ return;
+
+ // No HTTP Accept-Language header found
+ if (empty($_SERVER['HTTP_ACCEPT_LANGUAGE']))
+ {
+ // Accept everything
+ request::$accept_languages['*'] = 1;
+ }
+ else
+ {
+ request::$accept_languages = array();
+
+ foreach (request::parse_accept_header($_SERVER['HTTP_ACCEPT_LANGUAGE']) as $tag => $q)
+ {
+ // Explode each language (e.g. "en-us")
+ $tag = explode('-', $tag, 2);
+
+ request::$accept_languages[$tag[0]][isset($tag[1]) ? $tag[1] : '*'] = $q;
+ }
+ }
+ }
+
+} // End request
diff --git a/system/helpers/security.php b/system/helpers/security.php
new file mode 100644
index 0000000..7103bd1
--- /dev/null
+++ b/system/helpers/security.php
@@ -0,0 +1,35 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Security helper class.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class security_Core {
+
+ /**
+ * Sanitize a string with the xss_clean method.
+ *
+ * @param string string to sanitize
+ * @param string xss_clean method to use ('htmlpurifier' or defaults to built-in method)
+ * @return string
+ */
+ public static function xss_clean($str, $tool = NULL)
+ {
+ return Input::instance()->xss_clean($str, $tool);
+ }
+
+ /**
+ * Remove image tags from a string.
+ *
+ * @param string string to sanitize
+ * @return string
+ */
+ public static function strip_image_tags($str)
+ {
+ return preg_replace('#<img\s.*?(?:src\s*=\s*["\']?([^"\'<>\s]*)["\']?[^>]*)?>#is', '$1', $str);
+ }
+
+} // End security \ No newline at end of file
diff --git a/system/helpers/text.php b/system/helpers/text.php
new file mode 100644
index 0000000..f7f040c
--- /dev/null
+++ b/system/helpers/text.php
@@ -0,0 +1,596 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Text helper class.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class text_Core {
+
+ /**
+ * Limits a phrase to a given number of words.
+ *
+ * @param string phrase to limit words of
+ * @param integer number of words to limit to
+ * @param string 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.
+ *
+ * @param string phrase to limit characters of
+ * @param integer number of characters to limit to
+ * @param string end character or entity
+ * @param boolean enable or disable the preservation of words while limiting
+ * @return string
+ */
+ 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 mb_strlen($str) <= $limit)
+ return $str;
+
+ if ($limit <= 0)
+ return $end_char;
+
+ if ($preserve_words == FALSE)
+ {
+ return rtrim(mb_substr($str, 0, $limit)).$end_char;
+ }
+
+ preg_match('/^.{'.($limit - 1).'}\S*/us', $str, $matches);
+
+ return rtrim($matches[0]).(strlen($matches[0]) == strlen($str) ? '' : $end_char);
+ }
+
+ /**
+ * Alternates between two or more strings.
+ *
+ * @param string 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.
+ *
+ * @param string a type of pool, or a string of characters to use as the pool
+ * @param integer length of string to return
+ * @return string
+ *
+ * @tutorial alnum alpha-numeric characters
+ * @tutorial alpha alphabetical characters
+ * @tutorial hexdec hexadecimal characters, 0-9 plus a-f
+ * @tutorial numeric digit characters, 0-9
+ * @tutorial nozero digit characters, 1-9
+ * @tutorial distinct clearly distinct alpha-numeric characters
+ */
+ public static function random($type = 'alnum', $length = 8)
+ {
+ $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 = ! text::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;
+ }
+
+ /**
+ * Reduces multiple slashes in a string to single slashes.
+ *
+ * @param string string to reduce slashes of
+ * @return string
+ */
+ public static function reduce_slashes($str)
+ {
+ return preg_replace('#(?<!:)//+#', '/', $str);
+ }
+
+ /**
+ * Replaces the given words with a string.
+ *
+ * @param string phrase to replace words in
+ * @param array words to replace
+ * @param string replacement string
+ * @param boolean replace words across word boundries (space, period, etc)
+ * @return string
+ */
+ 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 (mb_strlen($replacement) == 1)
+ {
+ $regex .= 'e';
+ return preg_replace($regex, 'str_repeat($replacement, mb_strlen(\'$1\'))', $str);
+ }
+
+ return preg_replace($regex, $replacement, $str);
+ }
+
+ /**
+ * Finds the text that is similar between a set of words.
+ *
+ * @param array 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);
+ }
+
+ /**
+ * An alternative to the php levenshtein() function that work out the
+ * distance between 2 words using the Damerau–Levenshtein algorithm.
+ * Credit: http://forums.devnetwork.net/viewtopic.php?f=50&t=89094
+ *
+ * @see http://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance
+ * @param string first word
+ * @param string second word
+ * @return int distance between words
+ */
+ public static function distance($string1, $string2)
+ {
+ $string1_length = strlen($string1);
+ $string2_length = strlen($string2);
+
+ // Here we start building the table of values
+ $matrix = array();
+
+ // String1 length + 1 = rows.
+ for ($i = 0; $i <= $string1_length; ++$i)
+ {
+ $matrix[$i][0] = $i;
+ }
+
+ // String2 length + 1 columns.
+ for ($j = 0; $j <= $string2_length; ++$j)
+ {
+ $matrix[0][$j] = $j;
+ }
+
+ for ($i = 1; $i <= $string1_length; ++$i)
+ {
+ for ($j = 1; $j <= $string2_length; ++$j)
+ {
+ $cost = substr($string1, $i - 1, 1) == substr($string2, $j - 1, 1) ? 0 : 1;
+
+ $matrix[$i][$j] = min(
+ $matrix[$i - 1][$j] + 1, // deletion
+ $matrix[$i][$j - 1] + 1, // insertion
+ $matrix[$i - 1][$j - 1] + $cost // substitution
+ );
+
+ if ($i > 1 && $j > 1 && (substr($string1, $i - 1, 1) == substr($string2, $j - 2, 1))
+ && (substr($string1, $i - 2, 1) == substr($string2, $j - 1, 1)))
+ {
+ $matrix[$i][$j] = min(
+ $matrix[$i][$j],
+ $matrix[$i - 2][$j - 2] + $cost // transposition
+ );
+ }
+ }
+ }
+
+ return $matrix[$string1_length][$string2_length];
+ }
+
+ /**
+ * Converts text anchors into links.
+ *
+ * @param string text to auto link
+ * @return string
+ */
+ public static function auto_link_urls($text)
+ {
+
+ $regex = '~\\b'
+ .'((?:ht|f)tps?://)?' // protocol
+ .'(?:[-a-zA-Z0-9]{1,63}\.)+' // host name
+ .'(?:[0-9]{1,3}|aero|asia|biz|cat|com|coop|edu|gov|info|int|jobs|mil|mobi|museum|name|net|org|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw)' // tlds
+ .'(?:/[!$-/0-9:;=@_\':;!a-zA-Z\x7f-\xff]*?)?' // path
+ .'(?:\?[!$-/0-9:;=@_\':;!a-zA-Z\x7f-\xff]+?)?' // query
+ .'(?:#[!$-/0-9:;=@_\':;!a-zA-Z\x7f-\xff]+?)?' // fragment
+ .'(?=[?.!,;:"]?(?:\s|$))~'; // punctuation and url end
+
+ $result = "";
+ $position = 0;
+
+ while (preg_match($regex, $text, $match, PREG_OFFSET_CAPTURE, $position))
+ {
+ list($url, $url_pos) = $match[0];
+
+ // Add the text before the url
+ $result .= substr($text, $position, $url_pos - $position);
+
+ // Default to http://
+ $full_url = empty($match[1][0]) ? 'http://'.$url : $url;
+
+ // Add the hyperlink.
+ $result .= html::anchor($full_url, $url);
+
+ // New position to start parsing
+ $position = $url_pos + strlen($url);
+ }
+
+ return $result.substr($text, $position);
+ }
+
+ /**
+ * Converts text email addresses into links.
+ *
+ * @param string text to auto link
+ * @return string
+ */
+ public static function auto_link_emails($text)
+ {
+ // Finds 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.
+ if (preg_match_all('~\b(?<!href="mailto:|">|58;)(?!\.)[-+_a-z0-9.]++(?<!\.)@(?![-.])[-a-z0-9.]+(?<!\.)\.[a-z]{2,6}\b~i', $text, $matches))
+ {
+ foreach ($matches[0] as $match)
+ {
+ // Replace each email with an encoded mailto
+ $text = str_replace($match, html::mailto($match), $text);
+ }
+ }
+
+ return $text;
+ }
+
+ /**
+ * Automatically applies <p> and <br /> markup to text. Basically nl2br() on steroids.
+ *
+ * @param string subject
+ * @param boolean 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.
+ * @see Based on original functions written by:
+ * @see Aidan Lister: http://aidanlister.com/repos/v/function.size_readable.php
+ * @see Quentin Zervaas: http://www.phpriot.com/d/code/strings/filesize-format/
+ *
+ * @param integer size in bytes
+ * @param string a definitive unit
+ * @param string the return string format
+ * @param boolean 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]);
+ }
+
+ /**
+ * Prevents widow words by inserting a non-breaking space between the last two words.
+ * @see http://www.shauninman.com/archive/2006/08/22/widont_wordpress_plugin
+ *
+ * @param string string 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;
+ }
+
+ /**
+ * Tests whether a string contains only 7bit ASCII bytes. This is used to
+ * determine when to use native functions or UTF-8 functions.
+ *
+ * @see http://sourceforge.net/projects/phputf8/
+ * @copyright (c) 2007-2009 Kohana Team
+ * @copyright (c) 2005 Harry Fuecks
+ * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ *
+ * @param string string to check
+ * @return bool
+ */
+ public static function is_ascii($str)
+ {
+ return is_string($str) AND ! preg_match('/[^\x00-\x7F]/S', $str);
+ }
+
+ /**
+ * Strips out device control codes in the ASCII range.
+ *
+ * @see http://sourceforge.net/projects/phputf8/
+ * @copyright (c) 2007-2009 Kohana Team
+ * @copyright (c) 2005 Harry Fuecks
+ * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ *
+ * @param string 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.
+ *
+ * @see http://sourceforge.net/projects/phputf8/
+ * @copyright (c) 2007-2009 Kohana Team
+ * @copyright (c) 2005 Harry Fuecks
+ * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ *
+ * @param string 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'.
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @see http://sourceforge.net/projects/phputf8/
+ * @copyright (c) 2007-2009 Kohana Team
+ * @copyright (c) 2005 Harry Fuecks
+ * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ *
+ * @param string string to transliterate
+ * @param integer -1 lowercase only, +1 uppercase only, 0 both cases
+ * @return string
+ */
+ public static function transliterate_to_ascii($str, $case = 0)
+ {
+ static $UTF8_LOWER_ACCENTS = NULL;
+ static $UTF8_UPPER_ACCENTS = NULL;
+
+ if ($case <= 0)
+ {
+ if ($UTF8_LOWER_ACCENTS === NULL)
+ {
+ $UTF8_LOWER_ACCENTS = array(
+ 'à' => 'a', 'ô' => 'o', 'ď' => 'd', 'ḟ' => 'f', 'ë' => 'e', 'š' => 's', 'ơ' => 'o',
+ 'ß' => 'ss', 'ă' => 'a', 'ř' => 'r', 'ț' => 't', 'ň' => 'n', 'ā' => 'a', 'ķ' => 'k',
+ 'ŝ' => 's', 'ỳ' => 'y', 'ņ' => 'n', 'ĺ' => 'l', 'ħ' => 'h', 'ṗ' => 'p', 'ó' => 'o',
+ 'ú' => 'u', 'ě' => 'e', 'é' => 'e', 'ç' => 'c', 'ẁ' => 'w', 'ċ' => 'c', 'õ' => 'o',
+ 'ṡ' => 's', 'ø' => 'o', 'ģ' => 'g', 'ŧ' => 't', 'ș' => 's', 'ė' => 'e', 'ĉ' => 'c',
+ 'ś' => 's', 'î' => 'i', 'ű' => 'u', 'ć' => 'c', 'ę' => 'e', 'ŵ' => 'w', 'ṫ' => 't',
+ 'ū' => 'u', 'č' => 'c', 'ö' => 'o', 'è' => 'e', 'ŷ' => 'y', 'ą' => 'a', 'ł' => 'l',
+ 'ų' => 'u', 'ů' => 'u', 'ş' => 's', 'ğ' => 'g', 'ļ' => 'l', 'ƒ' => 'f', 'ž' => 'z',
+ 'ẃ' => 'w', 'ḃ' => 'b', 'å' => 'a', 'ì' => 'i', 'ï' => 'i', 'ḋ' => 'd', 'ť' => 't',
+ 'ŗ' => 'r', 'ä' => 'a', 'í' => 'i', 'ŕ' => 'r', 'ê' => 'e', 'ü' => 'u', 'ò' => 'o',
+ 'ē' => 'e', 'ñ' => 'n', 'ń' => 'n', 'ĥ' => 'h', 'ĝ' => 'g', 'đ' => 'd', 'ĵ' => 'j',
+ 'ÿ' => 'y', 'ũ' => 'u', 'ŭ' => 'u', 'ư' => 'u', 'ţ' => 't', 'ý' => 'y', 'ő' => 'o',
+ 'â' => 'a', 'ľ' => 'l', 'ẅ' => 'w', 'ż' => 'z', 'ī' => 'i', 'ã' => 'a', 'ġ' => 'g',
+ 'ṁ' => 'm', 'ō' => 'o', 'ĩ' => 'i', 'ù' => 'u', 'į' => 'i', 'ź' => 'z', 'á' => 'a',
+ 'û' => 'u', 'þ' => 'th', 'ð' => 'dh', 'æ' => 'ae', 'µ' => 'u', 'ĕ' => 'e', 'ı' => 'i',
+ );
+ }
+
+ $str = str_replace(
+ array_keys($UTF8_LOWER_ACCENTS),
+ array_values($UTF8_LOWER_ACCENTS),
+ $str
+ );
+ }
+
+ if ($case >= 0)
+ {
+ if ($UTF8_UPPER_ACCENTS === NULL)
+ {
+ $UTF8_UPPER_ACCENTS = array(
+ 'À' => 'A', 'Ô' => 'O', 'Ď' => 'D', 'Ḟ' => 'F', 'Ë' => 'E', 'Š' => 'S', 'Ơ' => 'O',
+ 'Ă' => 'A', 'Ř' => 'R', 'Ț' => 'T', 'Ň' => 'N', 'Ā' => 'A', 'Ķ' => 'K', 'Ĕ' => 'E',
+ 'Ŝ' => 'S', 'Ỳ' => 'Y', 'Ņ' => 'N', 'Ĺ' => 'L', 'Ħ' => 'H', 'Ṗ' => 'P', 'Ó' => 'O',
+ 'Ú' => 'U', 'Ě' => 'E', 'É' => 'E', 'Ç' => 'C', 'Ẁ' => 'W', 'Ċ' => 'C', 'Õ' => 'O',
+ 'Ṡ' => 'S', 'Ø' => 'O', 'Ģ' => 'G', 'Ŧ' => 'T', 'Ș' => 'S', 'Ė' => 'E', 'Ĉ' => 'C',
+ 'Ś' => 'S', 'Î' => 'I', 'Ű' => 'U', 'Ć' => 'C', 'Ę' => 'E', 'Ŵ' => 'W', 'Ṫ' => 'T',
+ 'Ū' => 'U', 'Č' => 'C', 'Ö' => 'O', 'È' => 'E', 'Ŷ' => 'Y', 'Ą' => 'A', 'Ł' => 'L',
+ 'Ų' => 'U', 'Ů' => 'U', 'Ş' => 'S', 'Ğ' => 'G', 'Ļ' => 'L', 'Ƒ' => 'F', 'Ž' => 'Z',
+ 'Ẃ' => 'W', 'Ḃ' => 'B', 'Å' => 'A', 'Ì' => 'I', 'Ï' => 'I', 'Ḋ' => 'D', 'Ť' => 'T',
+ 'Ŗ' => 'R', 'Ä' => 'A', 'Í' => 'I', 'Ŕ' => 'R', 'Ê' => 'E', 'Ü' => 'U', 'Ò' => 'O',
+ 'Ē' => 'E', 'Ñ' => 'N', 'Ń' => 'N', 'Ĥ' => 'H', 'Ĝ' => 'G', 'Đ' => 'D', 'Ĵ' => 'J',
+ 'Ÿ' => 'Y', 'Ũ' => 'U', 'Ŭ' => 'U', 'Ư' => 'U', 'Ţ' => 'T', 'Ý' => 'Y', 'Ő' => 'O',
+ 'Â' => 'A', 'Ľ' => 'L', 'Ẅ' => 'W', 'Ż' => 'Z', 'Ī' => 'I', 'Ã' => 'A', 'Ġ' => 'G',
+ 'Ṁ' => 'M', 'Ō' => 'O', 'Ĩ' => 'I', 'Ù' => 'U', 'Į' => 'I', 'Ź' => 'Z', 'Á' => 'A',
+ 'Û' => 'U', 'Þ' => 'Th', 'Ð' => 'Dh', 'Æ' => 'Ae', 'İ' => 'I',
+ );
+ }
+
+ $str = str_replace(
+ array_keys($UTF8_UPPER_ACCENTS),
+ array_values($UTF8_UPPER_ACCENTS),
+ $str
+ );
+ }
+
+ return $str;
+ }
+
+} // End text \ No newline at end of file
diff --git a/system/helpers/upload.php b/system/helpers/upload.php
new file mode 100644
index 0000000..62de674
--- /dev/null
+++ b/system/helpers/upload.php
@@ -0,0 +1,157 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Upload helper class for working with the global $_FILES
+ * array and Validation library.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class upload_Core {
+
+ /**
+ * Save an uploaded file to a new location.
+ *
+ * @param mixed name of $_FILE input or array of upload data
+ * @param string new filename
+ * @param string new directory
+ * @param integer chmod mask
+ * @return string full path to new file
+ */
+ public static function save($file, $filename = NULL, $directory = NULL, $chmod = 0644)
+ {
+ // Load file data from FILES if not passed as array
+ $file = is_array($file) ? $file : $_FILES[$file];
+
+ if ($filename === NULL)
+ {
+ // Use the default filename, with a timestamp pre-pended
+ $filename = time().$file['name'];
+ }
+
+ if (Kohana::config('upload.remove_spaces') === TRUE)
+ {
+ // Remove spaces from the filename
+ $filename = preg_replace('/\s+/', '_', $filename);
+ }
+
+ if ($directory === NULL)
+ {
+ // Use the pre-configured upload directory
+ $directory = Kohana::config('upload.directory', TRUE);
+ }
+
+ // Make sure the directory ends with a slash
+ $directory = rtrim($directory, '/').'/';
+
+ if ( ! is_dir($directory) AND Kohana::config('upload.create_directories') === TRUE)
+ {
+ // Create the upload directory
+ mkdir($directory, 0777, TRUE);
+ }
+
+ if ( ! is_writable($directory))
+ throw new Kohana_Exception('The upload destination folder, :dir:, does not appear to be writable.', array(':dir:' => $directory));
+
+ if (is_uploaded_file($file['tmp_name']) AND move_uploaded_file($file['tmp_name'], $filename = $directory.$filename))
+ {
+ if ($chmod !== FALSE)
+ {
+ // Set permissions on filename
+ chmod($filename, $chmod);
+ }
+
+ // Return new file path
+ return $filename;
+ }
+
+ return FALSE;
+ }
+
+ /* Validation Rules */
+
+ /**
+ * Tests if input data is valid file type, even if no upload is present.
+ *
+ * @param array $_FILES item
+ * @return bool
+ */
+ public static function valid($file)
+ {
+ return (is_array($file)
+ AND isset($file['error'])
+ AND isset($file['name'])
+ AND isset($file['type'])
+ AND isset($file['tmp_name'])
+ AND isset($file['size']));
+ }
+
+ /**
+ * Tests if input data has valid upload data.
+ *
+ * @param array $_FILES item
+ * @return bool
+ */
+ public static function required(array $file)
+ {
+ return (isset($file['tmp_name'])
+ AND isset($file['error'])
+ AND is_uploaded_file($file['tmp_name'])
+ AND (int) $file['error'] === UPLOAD_ERR_OK);
+ }
+
+ /**
+ * Validation rule to test if an uploaded file is allowed by extension.
+ *
+ * @param array $_FILES item
+ * @param array allowed file extensions
+ * @return bool
+ */
+ public static function type(array $file, array $allowed_types)
+ {
+ if ((int) $file['error'] !== UPLOAD_ERR_OK)
+ return TRUE;
+
+ // Get the default extension of the file
+ $extension = strtolower(substr(strrchr($file['name'], '.'), 1));
+
+ // Make sure there is an extension and that the extension is allowed
+ return ( ! empty($extension) AND in_array($extension, $allowed_types));
+ }
+
+ /**
+ * 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, 15, 300, etc) and
+ * B is the byte modifier: (B)ytes, (K)ilobytes, (M)egabytes, (G)igabytes.
+ * Eg: to limit the size to 1MB or less, you would use "1M".
+ *
+ * @param array $_FILES item
+ * @param array maximum file size
+ * @return bool
+ */
+ public static function size(array $file, array $size)
+ {
+ if ((int) $file['error'] !== UPLOAD_ERR_OK)
+ return TRUE;
+
+ // Only one size is allowed
+ $size = strtoupper($size[0]);
+
+ if ( ! preg_match('/[0-9]++[BKMG]/', $size))
+ return FALSE;
+
+ // Make the size into a power of 1024
+ switch (substr($size, -1))
+ {
+ case 'G': $size = intval($size) * pow(1024, 3); break;
+ case 'M': $size = intval($size) * pow(1024, 2); break;
+ case 'K': $size = intval($size) * pow(1024, 1); break;
+ default: $size = intval($size); break;
+ }
+
+ // Test that the file is under or equal to the max size
+ return ($file['size'] <= $size);
+ }
+
+} // End upload \ No newline at end of file
diff --git a/system/helpers/url.php b/system/helpers/url.php
new file mode 100644
index 0000000..02956fc
--- /dev/null
+++ b/system/helpers/url.php
@@ -0,0 +1,264 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * URL helper class.
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class url_Core {
+
+ /**
+ * Fetches the current URI.
+ *
+ * @param boolean include the query string
+ * @param boolean include the suffix
+ * @return string
+ */
+ public static function current($qs = FALSE, $suffix = FALSE)
+ {
+ $uri = ($qs === TRUE) ? Router::$complete_uri : Router::$current_uri;
+
+ return ($suffix === TRUE) ? $uri.Kohana::config('core.url_suffix') : $uri;
+ }
+
+ /**
+ * Base URL, with or without the index page.
+ *
+ * If protocol (and core.site_protocol) and core.site_domain are both empty,
+ * then
+ *
+ * @param boolean include the index page
+ * @param boolean non-default protocol
+ * @return string
+ */
+ public static function base($index = FALSE, $protocol = FALSE)
+ {
+ if ($protocol == FALSE)
+ {
+ // Use the default configured protocol
+ $protocol = Kohana::config('core.site_protocol');
+ }
+
+ // Load the site domain
+ $site_domain = (string) Kohana::config('core.site_domain', TRUE);
+
+ if ($protocol == FALSE)
+ {
+ if ($site_domain === '' OR $site_domain[0] === '/')
+ {
+ // Use the configured site domain
+ $base_url = $site_domain;
+ }
+ else
+ {
+ // Guess the protocol to provide full http://domain/path URL
+ $base_url = ((empty($_SERVER['HTTPS']) OR $_SERVER['HTTPS'] === 'off') ? 'http' : 'https').'://'.$site_domain;
+ }
+ }
+ else
+ {
+ if ($site_domain === '' OR $site_domain[0] === '/')
+ {
+ // Guess the server name if the domain starts with slash
+ $port = $_SERVER['SERVER_PORT'];
+ $port = ((($port == 80) && ($protocol == 'http')) || (($port == 443) && ($protocol == 'https')) || !$port) ? '' : ":$port";
+ $base_url = $protocol.'://'.($_SERVER['SERVER_NAME']?($_SERVER['SERVER_NAME'].$port):$_SERVER['HTTP_HOST']).$site_domain;
+ }
+ else
+ {
+ // Use the configured site domain
+ $base_url = $protocol.'://'.$site_domain;
+ }
+ }
+
+ if ($index === TRUE AND $index = Kohana::config('core.index_page'))
+ {
+ // Append the index page
+ $base_url = $base_url.$index;
+ }
+
+ // Force a slash on the end of the URL
+ return rtrim($base_url, '/').'/';
+ }
+
+ /**
+ * Fetches an absolute site URL based on a URI segment.
+ *
+ * @param string site URI to convert
+ * @param string non-default protocol
+ * @return string
+ */
+ public static function site($uri = '', $protocol = FALSE)
+ {
+ if ($path = trim(parse_url($uri, PHP_URL_PATH), '/'))
+ {
+ // Add path suffix
+ $path .= Kohana::config('core.url_suffix');
+ }
+
+ if ($query = parse_url($uri, PHP_URL_QUERY))
+ {
+ // ?query=string
+ $query = '?'.$query;
+ }
+
+ if ($fragment = parse_url($uri, PHP_URL_FRAGMENT))
+ {
+ // #fragment
+ $fragment = '#'.$fragment;
+ }
+
+ // Concat the URL
+ return url::base(TRUE, $protocol).$path.$query.$fragment;
+ }
+
+ /**
+ * Return the URL to a file. Absolute filenames and relative filenames
+ * are allowed.
+ *
+ * @param string filename
+ * @param boolean include the index page
+ * @return string
+ */
+ public static function file($file, $index = FALSE)
+ {
+ if (strpos($file, '://') === FALSE)
+ {
+ // Add the base URL to the filename
+ $file = url::base($index).$file;
+ }
+
+ return $file;
+ }
+
+ /**
+ * Merges an array of arguments with the current URI and query string to
+ * overload, instead of replace, the current query string.
+ *
+ * @param array associative array of arguments
+ * @return string
+ */
+ public static function merge(array $arguments)
+ {
+ if ($_GET === $arguments)
+ {
+ $query = Router::$query_string;
+ }
+ elseif ($query = http_build_query(array_merge($_GET, $arguments)))
+ {
+ $query = '?'.$query;
+ }
+
+ // Return the current URI with the arguments merged into the query string
+ return Router::$current_uri.$query;
+ }
+
+ /**
+ * Convert a phrase to a URL-safe title.
+ *
+ * @param string phrase to convert
+ * @param string word separator (- or _)
+ * @param boolean transliterate to ASCII
+ * @return string
+ */
+ public static function title($title, $separator = '-', $ascii_only = FALSE)
+ {
+ $separator = ($separator === '-') ? '-' : '_';
+
+ if ($ascii_only === TRUE)
+ {
+ // Replace accented characters by their unaccented equivalents
+ $title = text::transliterate_to_ascii($title);
+
+ // Remove all characters that are not the separator, a-z, 0-9, or whitespace
+ $title = preg_replace('/[^'.$separator.'a-z0-9\s]+/', '', strtolower($title));
+ }
+ else
+ {
+ // Remove all characters that are not the separator, letters, numbers, or whitespace
+ $title = preg_replace('/[^'.$separator.'\pL\pN\s]+/u', '', mb_strtolower($title));
+ }
+
+ // Replace all separator characters and whitespace by a single separator
+ $title = preg_replace('/['.$separator.'\s]+/', $separator, $title);
+
+ // Trim separators from the beginning and end
+ return trim($title, $separator);
+ }
+
+ /**
+ * Sends a page redirect header and runs the system.redirect Event.
+ *
+ * @param mixed string site URI or URL to redirect to, or array of strings if method is 300
+ * @param string HTTP method of redirect
+ * @return void
+ */
+ public static function redirect($uri = '', $method = '302')
+ {
+ if (Event::has_run('system.send_headers'))
+ {
+ return FALSE;
+ }
+
+ $codes = array
+ (
+ 'refresh' => 'Refresh',
+ '300' => 'Multiple Choices',
+ '301' => 'Moved Permanently',
+ '302' => 'Found',
+ '303' => 'See Other',
+ '304' => 'Not Modified',
+ '305' => 'Use Proxy',
+ '307' => 'Temporary Redirect'
+ );
+
+ // Validate the method and default to 302
+ $method = isset($codes[$method]) ? (string) $method : '302';
+
+ if ($method === '300')
+ {
+ $uri = (array) $uri;
+
+ $output = '<ul>';
+ foreach ($uri as $link)
+ {
+ $output .= '<li>'.html::anchor($link).'</li>';
+ }
+ $output .= '</ul>';
+
+ // The first URI will be used for the Location header
+ $uri = $uri[0];
+ }
+ else
+ {
+ $output = '<p>'.html::anchor($uri).'</p>';
+ }
+
+ // Run the redirect event
+ Event::run('system.redirect', $uri);
+
+ if (strpos($uri, '://') === FALSE)
+ {
+ // HTTP headers expect absolute URLs
+ $uri = url::site($uri, request::protocol());
+ }
+
+ if ($method === 'refresh')
+ {
+ header('Refresh: 0; url='.$uri);
+ }
+ else
+ {
+ header('HTTP/1.1 '.$method.' '.$codes[$method]);
+ header('Location: '.$uri);
+ }
+
+ // We are about to exit, so run the send_headers event
+ Event::run('system.send_headers');
+
+ exit('<h1>'.$method.' - '.$codes[$method].'</h1>'.$output);
+ }
+
+} // End url \ No newline at end of file
diff --git a/system/helpers/utf8.php b/system/helpers/utf8.php
new file mode 100644
index 0000000..0123cfb
--- /dev/null
+++ b/system/helpers/utf8.php
@@ -0,0 +1,744 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * A port of phputf8 to a unified file/class.
+ *
+ * This file is licensed differently from the rest of Kohana. As a port of
+ * phputf8, which is LGPL software, this file is released under the LGPL.
+ *
+ * PCRE needs to be compiled with UTF-8 support (--enable-utf8).
+ * Support for Unicode properties is highly recommended (--enable-unicode-properties).
+ * @see http://php.net/manual/reference.pcre.pattern.modifiers.php
+ *
+ * string functions.
+ * @see http://php.net/mbstring
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @copyright (c) 2005 Harry Fuecks
+ * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+
+class utf8_Core {
+
+ /**
+ * Replaces text within a portion of a UTF-8 string.
+ * @see http://php.net/substr_replace
+ *
+ * @author Harry Fuecks <hfuecks@gmail.com>
+ *
+ * @param string input string
+ * @param string replacement string
+ * @param integer offset
+ * @return string
+ */
+ public static function substr_replace($str, $replacement, $offset, $length = NULL)
+ {
+ if (text::is_ascii($str))
+ return ($length === NULL) ? substr_replace($str, $replacement, $offset) : substr_replace($str, $replacement, $offset, $length);
+
+ $length = ($length === NULL) ? mb_strlen($str) : (int) $length;
+ preg_match_all('/./us', $str, $str_array);
+ preg_match_all('/./us', $replacement, $replacement_array);
+
+ array_splice($str_array[0], $offset, $length, $replacement_array[0]);
+ return implode('', $str_array[0]);
+ }
+
+ /**
+ * Makes a UTF-8 string's first character uppercase.
+ * @see http://php.net/ucfirst
+ *
+ * @author Harry Fuecks <hfuecks@gmail.com>
+ *
+ * @param string mixed case string
+ * @return string
+ */
+ public static function ucfirst($str)
+ {
+ if (text::is_ascii($str))
+ return ucfirst($str);
+
+ preg_match('/^(.?)(.*)$/us', $str, $matches);
+ return mb_strtoupper($matches[1]).$matches[2];
+ }
+
+ /**
+ * Case-insensitive UTF-8 string comparison.
+ * @see http://php.net/strcasecmp
+ *
+ * @author Harry Fuecks <hfuecks@gmail.com>
+ *
+ * @param string string to compare
+ * @param string 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 (text::is_ascii($str1) AND text::is_ascii($str2))
+ return strcasecmp($str1, $str2);
+
+ $str1 = mb_strtolower($str1);
+ $str2 = mb_strtolower($str2);
+ return strcmp($str1, $str2);
+ }
+
+ /**
+ * Returns a string or an array with all occurrences of search in subject (ignoring case).
+ * replaced with the given replace value.
+ * @see http://php.net/str_ireplace
+ *
+ * @note It's not fast and gets slower if $search and/or $replace are arrays.
+ * @author Harry Fuecks <hfuecks@gmail.com
+ *
+ * @param string|array text to replace
+ * @param string|array replacement text
+ * @param string|array subject text
+ * @param integer 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 (text::is_ascii($search) AND text::is_ascii($replace) AND text::is_ascii($str))
+ return str_ireplace($search, $replace, $str, $count);
+
+ if (is_array($str))
+ {
+ foreach ($str as $key => $val)
+ {
+ $str[$key] = utf8::str_ireplace($search, $replace, $val, $count);
+ }
+ return $str;
+ }
+
+ if (is_array($search))
+ {
+ $keys = array_keys($search);
+
+ foreach ($keys as $k)
+ {
+ if (is_array($replace))
+ {
+ if (array_key_exists($k, $replace))
+ {
+ $str = utf8::str_ireplace($search[$k], $replace[$k], $str, $count);
+ }
+ else
+ {
+ $str = utf8::str_ireplace($search[$k], '', $str, $count);
+ }
+ }
+ else
+ {
+ $str = utf8::str_ireplace($search[$k], $replace, $str, $count);
+ }
+ }
+ return $str;
+ }
+
+ $search = mb_strtolower($search);
+ $str_lower = mb_strtolower($str);
+
+ $total_matched_strlen = 0;
+ $i = 0;
+
+ while (preg_match('/(.*?)'.preg_quote($search, '/').'/s', $str_lower, $matches))
+ {
+ $matched_strlen = strlen($matches[0]);
+ $str_lower = substr($str_lower, $matched_strlen);
+
+ $offset = $total_matched_strlen + strlen($matches[1]) + ($i * (strlen($replace) - 1));
+ $str = substr_replace($str, $replace, $offset, strlen($search));
+
+ $total_matched_strlen += $matched_strlen;
+ $i++;
+ }
+
+ $count += $i;
+ return $str;
+ }
+
+ /**
+ * Case-insenstive UTF-8 version of strstr. Returns all of input string
+ * from the first occurrence of needle to the end.
+ * @see http://php.net/stristr
+ *
+ * @author Harry Fuecks <hfuecks@gmail.com>
+ *
+ * @param string input string
+ * @param string needle
+ * @return string matched substring if found
+ * @return boolean FALSE if the substring was not found
+ */
+ public static function stristr($str, $search)
+ {
+ if (text::is_ascii($str) AND text::is_ascii($search))
+ return stristr($str, $search);
+
+ if ($search == '')
+ return $str;
+
+ $str_lower = mb_strtolower($str);
+ $search_lower = mb_strtolower($search);
+
+ preg_match('/^(.*?)'.preg_quote($search, '/').'/s', $str_lower, $matches);
+
+ if (isset($matches[1]))
+ return substr($str, strlen($matches[1]));
+
+ return FALSE;
+ }
+
+ /**
+ * Finds the length of the initial segment matching mask.
+ * @see http://php.net/strspn
+ *
+ * @author Harry Fuecks <hfuecks@gmail.com>
+ *
+ * @param string input string
+ * @param string mask for search
+ * @param integer start position of the string to examine
+ * @param integer 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 ($str == '' OR $mask == '')
+ return 0;
+
+ if (text::is_ascii($str) AND text::is_ascii($mask))
+ return ($offset === NULL) ? strspn($str, $mask) : (($length === NULL) ? strspn($str, $mask, $offset) : strspn($str, $mask, $offset, $length));
+
+ if ($offset !== NULL OR $length !== NULL)
+ {
+ $str = mb_substr($str, $offset, $length);
+ }
+
+ // Escape these characters: - [ ] . : \ ^ /
+ // The . and : are escaped to prevent possible warnings about POSIX regex elements
+ $mask = preg_replace('#[-[\].:\\\\^/]#', '\\\\$0', $mask);
+ preg_match('/^[^'.$mask.']+/u', $str, $matches);
+
+ return isset($matches[0]) ? mb_strlen($matches[0]) : 0;
+ }
+
+ /**
+ * Finds the length of the initial segment not matching mask.
+ * @see http://php.net/strcspn
+ *
+ * @author Harry Fuecks <hfuecks@gmail.com>
+ *
+ * @param string input string
+ * @param string mask for search
+ * @param integer start position of the string to examine
+ * @param integer 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 ($str == '' OR $mask == '')
+ return 0;
+
+ if (text::is_ascii($str) AND text::is_ascii($mask))
+ return ($offset === NULL) ? strcspn($str, $mask) : (($length === NULL) ? strcspn($str, $mask, $offset) : strcspn($str, $mask, $offset, $length));
+
+ if ($str !== NULL OR $length !== NULL)
+ {
+ $str = mb_substr($str, $offset, $length);
+ }
+
+ // Escape these characters: - [ ] . : \ ^ /
+ // The . and : are escaped to prevent possible warnings about POSIX regex elements
+ $mask = preg_replace('#[-[\].:\\\\^/]#', '\\\\$0', $mask);
+ preg_match('/^[^'.$mask.']+/u', $str, $matches);
+
+ return isset($matches[0]) ? mb_strlen($matches[0]) : 0;
+ }
+
+ /**
+ * Pads a UTF-8 string to a certain length with another string.
+ * @see http://php.net/str_pad
+ *
+ * @author Harry Fuecks <hfuecks@gmail.com>
+ *
+ * @param string input string
+ * @param integer desired string length after padding
+ * @param string string to use as padding
+ * @param string 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 (text::is_ascii($str) AND text::is_ascii($pad_str))
+ {
+ return str_pad($str, $final_str_length, $pad_str, $pad_type);
+ }
+
+ $str_length = mb_strlen($str);
+
+ if ($final_str_length <= 0 OR $final_str_length <= $str_length)
+ {
+ return $str;
+ }
+
+ $pad_str_length = mb_strlen($pad_str);
+ $pad_length = $final_str_length - $str_length;
+
+ if ($pad_type == STR_PAD_RIGHT)
+ {
+ $repeat = ceil($pad_length / $pad_str_length);
+ return mb_substr($str.str_repeat($pad_str, $repeat), 0, $final_str_length);
+ }
+
+ if ($pad_type == STR_PAD_LEFT)
+ {
+ $repeat = ceil($pad_length / $pad_str_length);
+ return mb_substr(str_repeat($pad_str, $repeat), 0, floor($pad_length)).$str;
+ }
+
+ if ($pad_type == STR_PAD_BOTH)
+ {
+ $pad_length /= 2;
+ $pad_length_left = floor($pad_length);
+ $pad_length_right = ceil($pad_length);
+ $repeat_left = ceil($pad_length_left / $pad_str_length);
+ $repeat_right = ceil($pad_length_right / $pad_str_length);
+
+ $pad_left = mb_substr(str_repeat($pad_str, $repeat_left), 0, $pad_length_left);
+ $pad_right = mb_substr(str_repeat($pad_str, $repeat_right), 0, $pad_length_left);
+ return $pad_left.$str.$pad_right;
+ }
+
+ trigger_error('utf8::str_pad: Unknown padding type (' . $pad_type . ')', E_USER_ERROR);
+ }
+
+ /**
+ * Converts a UTF-8 string to an array.
+ * @see http://php.net/str_split
+ *
+ * @author Harry Fuecks <hfuecks@gmail.com>
+ *
+ * @param string input string
+ * @param integer maximum length of each chunk
+ * @return array
+ */
+ public static function str_split($str, $split_length = 1)
+ {
+ $split_length = (int) $split_length;
+
+ if (text::is_ascii($str))
+ {
+ return str_split($str, $split_length);
+ }
+
+ if ($split_length < 1)
+ {
+ return FALSE;
+ }
+
+ if (mb_strlen($str) <= $split_length)
+ {
+ return array($str);
+ }
+
+ preg_match_all('/.{'.$split_length.'}|[^\x00]{1,'.$split_length.'}$/us', $str, $matches);
+
+ return $matches[0];
+ }
+
+ /**
+ * Reverses a UTF-8 string.
+ * @see http://php.net/strrev
+ *
+ * @author Harry Fuecks <hfuecks@gmail.com>
+ *
+ * @param string string to be reversed
+ * @return string
+ */
+ public static function strrev($str)
+ {
+ if (text::is_ascii($str))
+ return strrev($str);
+
+ preg_match_all('/./us', $str, $matches);
+ return implode('', array_reverse($matches[0]));
+ }
+
+ /**
+ * Strips whitespace (or other UTF-8 characters) from the beginning and
+ * end of a string.
+ * @see http://php.net/trim
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ *
+ * @param string input string
+ * @param string string of characters to remove
+ * @return string
+ */
+ public static function trim($str, $charlist = NULL)
+ {
+ if ($charlist === NULL)
+ return trim($str);
+
+ return utf8::ltrim(utf8::rtrim($str, $charlist), $charlist);
+ }
+
+ /**
+ * Strips whitespace (or other UTF-8 characters) from the beginning of a string.
+ * @see http://php.net/ltrim
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ *
+ * @param string input string
+ * @param string string of characters to remove
+ * @return string
+ */
+ public static function ltrim($str, $charlist = NULL)
+ {
+ if ($charlist === NULL)
+ return ltrim($str);
+
+ if (text::is_ascii($charlist))
+ return ltrim($str, $charlist);
+
+ $charlist = preg_replace('#[-\[\]:\\\\^/]#', '\\\\$0', $charlist);
+
+ return preg_replace('/^['.$charlist.']+/u', '', $str);
+ }
+
+ /**
+ * Strips whitespace (or other UTF-8 characters) from the end of a string.
+ * @see http://php.net/rtrim
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ *
+ * @param string input string
+ * @param string string of characters to remove
+ * @return string
+ */
+ public static function rtrim($str, $charlist = NULL)
+ {
+ if ($charlist === NULL)
+ return rtrim($str);
+
+ if (text::is_ascii($charlist))
+ return rtrim($str, $charlist);
+
+ $charlist = preg_replace('#[-\[\]:\\\\^/]#', '\\\\$0', $charlist);
+
+ return preg_replace('/['.$charlist.']++$/uD', '', $str);
+ }
+
+ /**
+ * Returns the unicode ordinal for a character.
+ * @see http://php.net/ord
+ *
+ * @author Harry Fuecks <hfuecks@gmail.com>
+ *
+ * @param string UTF-8 encoded character
+ * @return integer
+ */
+ public static function ord($chr)
+ {
+ $ord0 = ord($chr);
+
+ if ($ord0 >= 0 AND $ord0 <= 127)
+ {
+ return $ord0;
+ }
+
+ if ( ! isset($chr[1]))
+ {
+ trigger_error('Short sequence - at least 2 bytes expected, only 1 seen', E_USER_WARNING);
+ return FALSE;
+ }
+
+ $ord1 = ord($chr[1]);
+
+ if ($ord0 >= 192 AND $ord0 <= 223)
+ {
+ return ($ord0 - 192) * 64 + ($ord1 - 128);
+ }
+
+ if ( ! isset($chr[2]))
+ {
+ trigger_error('Short sequence - at least 3 bytes expected, only 2 seen', E_USER_WARNING);
+ return FALSE;
+ }
+
+ $ord2 = ord($chr[2]);
+
+ if ($ord0 >= 224 AND $ord0 <= 239)
+ {
+ return ($ord0 - 224) * 4096 + ($ord1 - 128) * 64 + ($ord2 - 128);
+ }
+
+ if ( ! isset($chr[3]))
+ {
+ trigger_error('Short sequence - at least 4 bytes expected, only 3 seen', E_USER_WARNING);
+ return FALSE;
+ }
+
+ $ord3 = ord($chr[3]);
+
+ if ($ord0 >= 240 AND $ord0 <= 247)
+ {
+ return ($ord0 - 240) * 262144 + ($ord1 - 128) * 4096 + ($ord2-128) * 64 + ($ord3 - 128);
+ }
+
+ if ( ! isset($chr[4]))
+ {
+ trigger_error('Short sequence - at least 5 bytes expected, only 4 seen', E_USER_WARNING);
+ return FALSE;
+ }
+
+ $ord4 = ord($chr[4]);
+
+ if ($ord0 >= 248 AND $ord0 <= 251)
+ {
+ return ($ord0 - 248) * 16777216 + ($ord1-128) * 262144 + ($ord2 - 128) * 4096 + ($ord3 - 128) * 64 + ($ord4 - 128);
+ }
+
+ if ( ! isset($chr[5]))
+ {
+ trigger_error('Short sequence - at least 6 bytes expected, only 5 seen', E_USER_WARNING);
+ return FALSE;
+ }
+
+ if ($ord0 >= 252 AND $ord0 <= 253)
+ {
+ return ($ord0 - 252) * 1073741824 + ($ord1 - 128) * 16777216 + ($ord2 - 128) * 262144 + ($ord3 - 128) * 4096 + ($ord4 - 128) * 64 + (ord($chr[5]) - 128);
+ }
+
+ if ($ord0 >= 254 AND $ord0 <= 255)
+ {
+ trigger_error('Invalid UTF-8 with surrogate ordinal '.$ord0, E_USER_WARNING);
+ return FALSE;
+ }
+ }
+
+ /**
+ * 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.
+ * Occurrances of the BOM are ignored. Surrogates are not allowed.
+ *
+ * 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 UTF-8 encoded string
+ * @return array unicode code points
+ * @return boolean FALSE if the string is invalid
+ */
+ public static function to_unicode($str)
+ {
+ $mState = 0; // cached expected number of octets after the current octet until the beginning of the next UTF8 character sequence
+ $mUcs4 = 0; // cached Unicode character
+ $mBytes = 1; // cached expected number of octets in the current sequence
+
+ $out = array();
+
+ $len = strlen($str);
+
+ for ($i = 0; $i < $len; $i++)
+ {
+ $in = ord($str[$i]);
+
+ if ($mState == 0)
+ {
+ // When mState is zero we expect either a US-ASCII character or a
+ // multi-octet sequence.
+ if (0 == (0x80 & $in))
+ {
+ // US-ASCII, pass straight through.
+ $out[] = $in;
+ $mBytes = 1;
+ }
+ elseif (0xC0 == (0xE0 & $in))
+ {
+ // First octet of 2 octet sequence
+ $mUcs4 = $in;
+ $mUcs4 = ($mUcs4 & 0x1F) << 6;
+ $mState = 1;
+ $mBytes = 2;
+ }
+ elseif (0xE0 == (0xF0 & $in))
+ {
+ // First octet of 3 octet sequence
+ $mUcs4 = $in;
+ $mUcs4 = ($mUcs4 & 0x0F) << 12;
+ $mState = 2;
+ $mBytes = 3;
+ }
+ elseif (0xF0 == (0xF8 & $in))
+ {
+ // First octet of 4 octet sequence
+ $mUcs4 = $in;
+ $mUcs4 = ($mUcs4 & 0x07) << 18;
+ $mState = 3;
+ $mBytes = 4;
+ }
+ elseif (0xF8 == (0xFC & $in))
+ {
+ // First octet of 5 octet sequence.
+ //
+ // This is illegal because the encoded codepoint must be either
+ // (a) not the shortest form or
+ // (b) outside the Unicode range of 0-0x10FFFF.
+ // Rather than trying to resynchronize, we will carry on until the end
+ // of the sequence and let the later error handling code catch it.
+ $mUcs4 = $in;
+ $mUcs4 = ($mUcs4 & 0x03) << 24;
+ $mState = 4;
+ $mBytes = 5;
+ }
+ elseif (0xFC == (0xFE & $in))
+ {
+ // First octet of 6 octet sequence, see comments for 5 octet sequence.
+ $mUcs4 = $in;
+ $mUcs4 = ($mUcs4 & 1) << 30;
+ $mState = 5;
+ $mBytes = 6;
+ }
+ else
+ {
+ // Current octet is neither in the US-ASCII range nor a legal first octet of a multi-octet sequence.
+ trigger_error('utf8::to_unicode: Illegal sequence identifier in UTF-8 at byte '.$i, E_USER_WARNING);
+ return FALSE;
+ }
+ }
+ else
+ {
+ // When mState is non-zero, we expect a continuation of the multi-octet sequence
+ if (0x80 == (0xC0 & $in))
+ {
+ // Legal continuation
+ $shift = ($mState - 1) * 6;
+ $tmp = $in;
+ $tmp = ($tmp & 0x0000003F) << $shift;
+ $mUcs4 |= $tmp;
+
+ // End of the multi-octet sequence. mUcs4 now contains the final Unicode codepoint to be output
+ if (0 == --$mState)
+ {
+ // Check for illegal sequences and codepoints
+
+ // From Unicode 3.1, non-shortest form is illegal
+ if (((2 == $mBytes) AND ($mUcs4 < 0x0080)) OR
+ ((3 == $mBytes) AND ($mUcs4 < 0x0800)) OR
+ ((4 == $mBytes) AND ($mUcs4 < 0x10000)) OR
+ (4 < $mBytes) OR
+ // From Unicode 3.2, surrogate characters are illegal
+ (($mUcs4 & 0xFFFFF800) == 0xD800) OR
+ // Codepoints outside the Unicode range are illegal
+ ($mUcs4 > 0x10FFFF))
+ {
+ trigger_error('utf8::to_unicode: Illegal sequence or codepoint in UTF-8 at byte '.$i, E_USER_WARNING);
+ return FALSE;
+ }
+
+ if (0xFEFF != $mUcs4)
+ {
+ // BOM is legal but we don't want to output it
+ $out[] = $mUcs4;
+ }
+
+ // Initialize UTF-8 cache
+ $mState = 0;
+ $mUcs4 = 0;
+ $mBytes = 1;
+ }
+ }
+ else
+ {
+ // ((0xC0 & (*in) != 0x80) AND (mState != 0))
+ // Incomplete multi-octet sequence
+ trigger_error('utf8::to_unicode: Incomplete multi-octet sequence in UTF-8 at byte '.$i, E_USER_WARNING);
+ return FALSE;
+ }
+ }
+ }
+
+ return $out;
+ }
+
+ /**
+ * 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.
+ *
+ * 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 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)
+ {
+ ob_start();
+
+ $keys = array_keys($arr);
+
+ foreach ($keys as $k)
+ {
+ // ASCII range (including control chars)
+ if (($arr[$k] >= 0) AND ($arr[$k] <= 0x007f))
+ {
+ echo chr($arr[$k]);
+ }
+ // 2 byte sequence
+ elseif ($arr[$k] <= 0x07ff)
+ {
+ echo chr(0xc0 | ($arr[$k] >> 6));
+ echo chr(0x80 | ($arr[$k] & 0x003f));
+ }
+ // Byte order mark (skip)
+ elseif ($arr[$k] == 0xFEFF)
+ {
+ // nop -- zap the BOM
+ }
+ // Test for illegal surrogates
+ elseif ($arr[$k] >= 0xD800 AND $arr[$k] <= 0xDFFF)
+ {
+ // Found a surrogate
+ trigger_error('utf8::from_unicode: Illegal surrogate at index: '.$k.', value: '.$arr[$k], E_USER_WARNING);
+ return FALSE;
+ }
+ // 3 byte sequence
+ elseif ($arr[$k] <= 0xffff)
+ {
+ echo chr(0xe0 | ($arr[$k] >> 12));
+ echo chr(0x80 | (($arr[$k] >> 6) & 0x003f));
+ echo chr(0x80 | ($arr[$k] & 0x003f));
+ }
+ // 4 byte sequence
+ elseif ($arr[$k] <= 0x10ffff)
+ {
+ echo chr(0xf0 | ($arr[$k] >> 18));
+ echo chr(0x80 | (($arr[$k] >> 12) & 0x3f));
+ echo chr(0x80 | (($arr[$k] >> 6) & 0x3f));
+ echo chr(0x80 | ($arr[$k] & 0x3f));
+ }
+ // Out of range
+ else
+ {
+ trigger_error('utf8::from_unicode: Codepoint out of Unicode range at index: '.$k.', value: '.$arr[$k], E_USER_WARNING);
+ return FALSE;
+ }
+ }
+
+ $result = ob_get_contents();
+ ob_end_clean();
+ return $result;
+ }
+
+} // End utf8 \ No newline at end of file
diff --git a/system/helpers/valid.php b/system/helpers/valid.php
new file mode 100644
index 0000000..1f7be22
--- /dev/null
+++ b/system/helpers/valid.php
@@ -0,0 +1,453 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * The Valid Helper provides functions to help validate data. They can be used as standalone static functions or
+ * as rules for the Validation Library.
+ *
+ * ###### Validation Library Example:
+ * $data = new Validation($_POST);
+ * $data->add_rules('phone', 'required', 'valid::phone[7, 10, 11, 14]')
+ *
+ * if ($data->validate())
+ * {
+ * echo 'The phone number is valid';
+ * }
+ * else
+ * {
+ * echo Kohana::debug($data->errors());
+ * }
+ *
+ * [!!] The *valid::* part of the rule is optional, but is recommended to avoid conflicts with php functions.
+ *
+ * For more informaiton see the [Validation] Library.
+ *
+ * ###### Standalone Example:
+ * if (valid::phone($_POST['phone'], array(7, 10, 11, 14))
+ * {
+ * echo 'The phone number is valid';
+ * }
+ * else
+ * {
+ * echo 'Not valid';
+ * }
+ *
+ * @package Kohana
+ * @author Kohana Team
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
+ */
+class valid_Core {
+
+ /**
+ * Validate an email address. This method is more strict than valid::email_rfc();
+ *
+ * ###### Example:
+ * $email = 'bill@gates.com';
+ * if (valid::email($email))
+ * {
+ * echo "Valid email";
+ * }
+ * else
+ * {
+ * echo "Invalid email";
+ * }
+ *
+ * @param string A email address
+ * @return boolean
+ */
+ public static function email($email)
+ {
+ return (bool) preg_match('/^[-_a-z0-9\'+*$^&%=~!?{}]++(?:\.[-_a-z0-9\'+*$^&%=~!?{}]+)*+@(?:(?![-.])[-a-z0-9.]+(?<![-.])\.[a-z]{2,6}|\d{1,3}(?:\.\d{1,3}){3})(?::\d++)?$/iD', (string) $email);
+ }
+
+ /**
+ * Validate the domain of an email address by checking if the domain has a
+ * valid MX record.
+ *
+ * [!!] This function will always return `TRUE` if the checkdnsrr() function isn't avaliable (All Windows platforms before php 5.3)
+ *
+ * @param string email address
+ * @return boolean
+ */
+ public static function email_domain($email)
+ {
+ // If we can't prove the domain is invalid, consider it valid
+ // Note: checkdnsrr() is not implemented on Windows platforms
+ if ( ! function_exists('checkdnsrr'))
+ return TRUE;
+
+ // Check if the email domain has a valid MX record
+ return (bool) checkdnsrr(preg_replace('/^[^@]+@/', '', $email), 'MX');
+ }
+
+ /**
+ * RFC compliant email validation. This function is __LESS__ strict than [valid::email]. Choose carefully.
+ *
+ * @see Originally by Cal Henderson, modified to fit Kohana syntax standards:
+ * @see http://www.iamcal.com/publish/articles/php/parsing_email/
+ * @see http://www.w3.org/Protocols/rfc822/
+ *
+ * @param string email address
+ * @return boolean
+ */
+ public static function email_rfc($email)
+ {
+ $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)*";
+ $addr_spec = "$local_part\\x40$domain";
+
+ return (bool) preg_match('/^'.$addr_spec.'$/D', (string) $email);
+ }
+
+ /**
+ * Basic URL validation.
+ *
+ * ###### Example:
+ * $url = 'http://www.kohanaphp.com';
+ * if (valid::url($url))
+ * {
+ * echo "Valid url";
+ * }
+ * else
+ * {
+ * echo "Invalid url";
+ * }
+ *
+ * @param string URL
+ * @return boolean
+ */
+ public static function url($url)
+ {
+ return (bool) filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_HOST_REQUIRED);
+ }
+
+ /**
+ * Validates an IP Address. This only tests to see if the ip address is valid,
+ * it doesn't check to see if the ip address is actually in use. Has optional support for
+ * IPv6, and private ip address ranges.
+ *
+ * @param string IP address
+ * @param boolean allow IPv6 addresses
+ * @param boolean allow private IP networks
+ * @return boolean
+ */
+ public static function ip($ip, $ipv6 = FALSE, $allow_private = TRUE)
+ {
+ // By default do not allow private and reserved range IPs
+ $flags = FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE;
+ if ($allow_private === TRUE)
+ $flags = FILTER_FLAG_NO_RES_RANGE;
+
+ if ($ipv6 === TRUE)
+ return (bool) filter_var($ip, FILTER_VALIDATE_IP, $flags);
+
+ return (bool) filter_var($ip, FILTER_VALIDATE_IP, $flags | FILTER_FLAG_IPV4);
+ }
+
+ /**
+ * Validates a credit card number using the [Luhn (mod10)](http://en.wikipedia.org/wiki/Luhn_algorithm)
+ * formula.
+ *
+ * ###### Example:
+ * $cc_number = '4111111111111111';
+ * if (valid::credit_card($cc_number, array('visa', 'mastercard')))
+ * {
+ * echo "Valid number";
+ * }
+ * else
+ * {
+ * echo "Invalid number";
+ * }
+ *
+ * @param integer credit card number
+ * @param string|array card type, or an array of card types
+ * @return boolean
+ */
+ 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('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;
+
+ // 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. This function will strip all non-digit
+ * characters from the phone number for testing.
+ *
+ * ###### Example:
+ * $phone_number = '(201) 664-0274';
+ * if (valid::phone($phone_number))
+ * {
+ * echo "Valid phone number";
+ * }
+ * else
+ * {
+ * echo "Invalid phone number";
+ * }
+ *
+ * @param string phone number to check
+ * @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 using the php
+ * [strtotime()](http://php.net/strtotime) function
+ *
+ * @param string 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 input string
+ * @param boolean trigger UTF-8 compatibility
+ * @return boolean
+ */
+ public static function alpha($str, $utf8 = FALSE)
+ {
+ return ($utf8 === TRUE)
+ ? (bool) preg_match('/^\pL++$/uD', (string) $str)
+ : ctype_alpha((string) $str);
+ }
+
+ /**
+ * Checks whether a string consists of alphabetical characters and numbers only.
+ *
+ * @param string input string
+ * @param boolean trigger UTF-8 compatibility
+ * @return boolean
+ */
+ public static function alpha_numeric($str, $utf8 = FALSE)
+ {
+ return ($utf8 === TRUE)
+ ? (bool) preg_match('/^[\pL\pN]++$/uD', (string) $str)
+ : ctype_alnum((string) $str);
+ }
+
+ /**
+ * Checks whether a string consists of alphabetical characters, numbers, underscores and dashes only.
+ *
+ * @param string input string
+ * @param boolean trigger UTF-8 compatibility
+ * @return boolean
+ */
+ public static function alpha_dash($str, $utf8 = FALSE)
+ {
+ return ($utf8 === TRUE)
+ ? (bool) preg_match('/^[-\pL\pN_]++$/uD', (string) $str)
+ : (bool) preg_match('/^[-a-z0-9_]++$/iD', (string) $str);
+ }
+
+ /**
+ * Checks whether a string consists of digits only (no dots or dashes).
+ *
+ * @param string input string
+ * @param boolean trigger UTF-8 compatibility
+ * @return boolean
+ */
+ public static function digit($str, $utf8 = FALSE)
+ {
+ return ($utf8 === TRUE)
+ ? (bool) preg_match('/^\pN++$/uD', (string) $str)
+ : ctype_digit((string) $str);
+ }
+
+ /**
+ * Checks whether a string is a valid number (negative and decimal numbers allowed).
+ * This function uses [localeconv()](http://www.php.net/manual/en/function.localeconv.php)
+ * to support international number formats.
+ *
+ * @param string input string
+ * @return boolean
+ */
+ public static function numeric($str)
+ {
+ // Use localeconv to set the decimal_point value: Usually a comma or period.
+ $locale = localeconv();
+ return (bool) preg_match('/^-?[0-9'.$locale['decimal_point'].']++$/D', (string) $str);
+ }
+
+ /**
+ * Tests if an integer is within a range.
+ *
+ * @param integer number to check
+ * @param array valid range of input
+ * @return boolean
+ */
+ public static function range($number, array $range)
+ {
+ // Invalid by default
+ $status = FALSE;
+
+ if (is_int($number) OR ctype_digit($number))
+ {
+ if (count($range) > 1)
+ {
+ if ($number >= $range[0] AND $number <= $range[1])
+ {
+ // Number is within the required range
+ $status = TRUE;
+ }
+ }
+ elseif ($number >= $range[0])
+ {
+ // Number is greater than the minimum
+ $status = TRUE;
+ }
+ }
+
+ return $status;
+ }
+
+ /**
+ * Checks if a string is a proper decimal format. The format array can be
+ * used to specify a decimal length, or a number and decimal length, eg:
+ * array(2) would force the number to have 2 decimal places, array(4,2)
+ * would force the number to have 4 digits and 2 decimal places.
+ *
+ * ###### Example:
+ * $decimal = '4.5';
+ * if (valid::decimal($decimal, array(2,1)))
+ * {
+ * echo "Valid decimal";
+ * }
+ * else
+ * {
+ * echo "Invalid decimal";
+ * }
+ *
+ * Output: Invalid decimal
+ *
+ * @param string input string
+ * @param array decimal format: y or x,y
+ * @return boolean
+ */
+ public static function decimal($str, $format = NULL)
+ {
+ // Create the pattern
+ $pattern = '/^[0-9]%s\.[0-9]%s$/';
+
+ if ( ! empty($format))
+ {
+ if (count($format) > 1)
+ {
+ // Use the format for number and decimal length
+ $pattern = sprintf($pattern, '{'.$format[0].'}', '{'.$format[1].'}');
+ }
+ elseif (count($format) > 0)
+ {
+ // Use the format as decimal length
+ $pattern = sprintf($pattern, '+', '{'.$format[0].'}');
+ }
+ }
+ else
+ {
+ // No format
+ $pattern = sprintf($pattern, '+', '+');
+ }
+
+ return (bool) preg_match($pattern, (string) $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.
+ * You may want to normalize these values with format::color().
+ *
+ * @param string input string
+ * @return boolean
+ */
+ public static function color($str)
+ {
+ return (bool) preg_match('/^#?+[0-9a-f]{3}(?:[0-9a-f]{3})?$/iD', $str);
+ }
+
+} // End valid \ No newline at end of file