From 12fe58d997d2066dc362fd393a18b4e5da190513 Mon Sep 17 00:00:00 2001
From: Bharat Mediratta
Date: Wed, 27 May 2009 15:11:53 -0700
Subject: Rename 'kohana' to 'system' to conform to the Kohana filesystem
layout. I'm comfortable with us not clearly drawing the distinction about
the fact that it's Kohana.
---
system/helpers/arr.php | 312 +++++++++++++++++++++++++
system/helpers/cookie.php | 84 +++++++
system/helpers/date.php | 405 ++++++++++++++++++++++++++++++++
system/helpers/download.php | 105 +++++++++
system/helpers/email.php | 181 +++++++++++++++
system/helpers/expires.php | 111 +++++++++
system/helpers/feed.php | 122 ++++++++++
system/helpers/file.php | 186 +++++++++++++++
system/helpers/form.php | 542 +++++++++++++++++++++++++++++++++++++++++++
system/helpers/format.php | 66 ++++++
system/helpers/html.php | 440 +++++++++++++++++++++++++++++++++++
system/helpers/inflector.php | 193 +++++++++++++++
system/helpers/num.php | 26 +++
system/helpers/remote.php | 66 ++++++
system/helpers/request.php | 239 +++++++++++++++++++
system/helpers/security.php | 47 ++++
system/helpers/text.php | 410 ++++++++++++++++++++++++++++++++
system/helpers/upload.php | 162 +++++++++++++
system/helpers/url.php | 252 ++++++++++++++++++++
system/helpers/valid.php | 338 +++++++++++++++++++++++++++
20 files changed, 4287 insertions(+)
create mode 100644 system/helpers/arr.php
create mode 100644 system/helpers/cookie.php
create mode 100644 system/helpers/date.php
create mode 100644 system/helpers/download.php
create mode 100644 system/helpers/email.php
create mode 100644 system/helpers/expires.php
create mode 100644 system/helpers/feed.php
create mode 100644 system/helpers/file.php
create mode 100644 system/helpers/form.php
create mode 100644 system/helpers/format.php
create mode 100644 system/helpers/html.php
create mode 100644 system/helpers/inflector.php
create mode 100644 system/helpers/num.php
create mode 100644 system/helpers/remote.php
create mode 100644 system/helpers/request.php
create mode 100644 system/helpers/security.php
create mode 100644 system/helpers/text.php
create mode 100644 system/helpers/upload.php
create mode 100644 system/helpers/url.php
create mode 100644 system/helpers/valid.php
(limited to 'system/helpers')
diff --git a/system/helpers/arr.php b/system/helpers/arr.php
new file mode 100644
index 00000000..9570c4b5
--- /dev/null
+++ b/system/helpers/arr.php
@@ -0,0 +1,312 @@
+ $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)
+ {
+ if (isset($search[$key]))
+ {
+ $found[$key] = $search[$key];
+ }
+ else
+ {
+ $found[$key] = NULL;
+ }
+ }
+
+ return $found;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * @param mixed $needle the value to search for
+ * @param array $haystack an array of values to search in
+ * @param boolean $sort sort the array now
+ * @return integer|FALSE the index of the match or FALSE when not found
+ */
+ public static function binary_search($needle, $haystack, $sort = FALSE)
+ {
+ if ($sort)
+ {
+ sort($haystack);
+ }
+
+ $high = count($haystack) - 1;
+ $low = 0;
+
+ while ($low <= $high)
+ {
+ $mid = ($low + $high) >> 1;
+
+ if ($haystack[$mid] < $needle)
+ {
+ $low = $mid + 1;
+ }
+ elseif ($haystack[$mid] > $needle)
+ {
+ $high = $mid - 1;
+ }
+ else
+ {
+ return $mid;
+ }
+ }
+
+ return FALSE;
+ }
+
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Fill an array with a range of numbers.
+ *
+ * @param integer stepping
+ * @param integer ending number
+ * @return array
+ */
+ public static function range($step = 10, $max = 100)
+ {
+ if ($step < 1)
+ return array();
+
+ $array = array();
+ for ($i = $step; $i <= $max; $i += $step)
+ {
+ $array[$i] = $i;
+ }
+
+ return $array;
+ }
+
+ /**
+ * 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;
+ }
+
+} // End arr
diff --git a/system/helpers/cookie.php b/system/helpers/cookie.php
new file mode 100644
index 00000000..901b6d86
--- /dev/null
+++ b/system/helpers/cookie.php
@@ -0,0 +1,84 @@
+cookie($name, $default, $xss_clean);
+ }
+
+ /**
+ * 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)
+ {
+ if ( ! isset($_COOKIE[$name]))
+ return FALSE;
+
+ // 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);
+ }
+
+} // 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 00000000..7d5a9ab6
--- /dev/null
+++ b/system/helpers/date.php
@@ -0,0 +1,405 @@
+> 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 that to find the offset of
+ * @param string|boolean timezone used as the baseline
+ * @return integer
+ */
+ public static function offset($remote, $local = TRUE)
+ {
+ static $offsets;
+
+ // Default values
+ $remote = (string) $remote;
+ $local = ($local === TRUE) ? date_default_timezone_get() : (string) $local;
+
+ // Cache key name
+ $cache = $remote.$local;
+
+ if (empty($offsets[$cache]))
+ {
+ // Create timezone objects
+ $remote = new DateTimeZone($remote);
+ $local = new DateTimeZone($local);
+
+ // Create date objects from timezones
+ $time_there = new DateTime('now', $remote);
+ $time_here = new DateTime('now', $local);
+
+ // Find the offset
+ $offsets[$cache] = $remote->getOffset($time_there) - $local->getOffset($time_here);
+ }
+
+ return $offsets[$cache];
+ }
+
+ /**
+ * 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/download.php b/system/helpers/download.php
new file mode 100644
index 00000000..49fed42c
--- /dev/null
+++ b/system/helpers/download.php
@@ -0,0 +1,105 @@
+setUsername($config['options']['username']);
+ empty($config['options']['password']) or $connection->setPassword($config['options']['password']);
+
+ if ( ! empty($config['options']['auth']))
+ {
+ // Get the class name and params
+ list ($class, $params) = arr::callback_string($config['options']['auth']);
+
+ if ($class === 'PopB4Smtp')
+ {
+ // Load the PopB4Smtp class manually, due to its odd filename
+ require Kohana::find_file('vendor', 'swift/Swift/Authenticator/$PopB4Smtp$');
+ }
+
+ // Prepare the class name for auto-loading
+ $class = 'Swift_Authenticator_'.$class;
+
+ // Attach the authenticator
+ $connection->attachAuthenticator(($params === NULL) ? new $class : new $class($params[0]));
+ }
+
+ // Set the timeout to 5 seconds
+ $connection->setTimeout(empty($config['options']['timeout']) ? 5 : (int) $config['options']['timeout']);
+ break;
+ case 'sendmail':
+ // Create a sendmail connection
+ $connection = new Swift_Connection_Sendmail
+ (
+ empty($config['options']) ? Swift_Connection_Sendmail::AUTO_DETECT : $config['options']
+ );
+
+ // Set the timeout to 5 seconds
+ $connection->setTimeout(5);
+ break;
+ default:
+ // Use the native connection
+ $connection = new Swift_Connection_NativeMail($config['options']);
+ break;
+ }
+
+ // Create the SwiftMailer instance
+ return email::$mail = new Swift($connection);
+ }
+
+ /**
+ * Send an email message.
+ *
+ * @param string|array recipient email (and name), or an array of To, Cc, Bcc names
+ * @param string|array sender email (and name)
+ * @param string message subject
+ * @param string message body
+ * @param boolean send email as HTML
+ * @return integer number of emails sent
+ */
+ public static function send($to, $from, $subject, $message, $html = FALSE)
+ {
+ // Connect to SwiftMailer
+ (email::$mail === NULL) and email::connect();
+
+ // Determine the message type
+ $html = ($html === TRUE) ? 'text/html' : 'text/plain';
+
+ // Create the message
+ $message = new Swift_Message($subject, $message, $html, '8bit', 'utf-8');
+
+ if (is_string($to))
+ {
+ // Single recipient
+ $recipients = new Swift_Address($to);
+ }
+ elseif (is_array($to))
+ {
+ if (isset($to[0]) AND isset($to[1]))
+ {
+ // Create To: address set
+ $to = array('to' => $to);
+ }
+
+ // Create a list of recipients
+ $recipients = new Swift_RecipientList;
+
+ foreach ($to as $method => $set)
+ {
+ if ( ! in_array($method, array('to', 'cc', 'bcc')))
+ {
+ // Use To: by default
+ $method = 'to';
+ }
+
+ // Create method name
+ $method = 'add'.ucfirst($method);
+
+ if (is_array($set))
+ {
+ // Add a recipient with name
+ $recipients->$method($set[0], $set[1]);
+ }
+ else
+ {
+ // Add a recipient without name
+ $recipients->$method($set);
+ }
+ }
+ }
+
+ if (is_string($from))
+ {
+ // From without a name
+ $from = new Swift_Address($from);
+ }
+ elseif (is_array($from))
+ {
+ // From with a name
+ $from = new Swift_Address($from[0], $from[1]);
+ }
+
+ return email::$mail->send($message, $recipients, $from);
+ }
+
+} // End email
\ No newline at end of file
diff --git a/system/helpers/expires.php b/system/helpers/expires.php
new file mode 100644
index 00000000..c43cc0cc
--- /dev/null
+++ b/system/helpers/expires.php
@@ -0,0 +1,111 @@
+ 0)
+ {
+ // Re-send headers
+ header('Last-Modified: '.gmdate('D, d M Y H:i:s T', $mod_time));
+ header('Expires: '.gmdate('D, d M Y H:i:s T', time() + $mod_time_diff));
+ header('Cache-Control: max-age='.$mod_time_diff);
+ header('Status: 304 Not Modified', TRUE, 304);
+
+ // Prevent any output
+ Event::add('system.display', array('expires', 'prevent_output'));
+
+ // Exit to prevent other output
+ exit;
+ }
+ }
+
+ return FALSE;
+ }
+
+ /**
+ * Check headers already created to not step on download or Img_lib's feet
+ *
+ * @return boolean
+ */
+ public static function check_headers()
+ {
+ foreach (headers_list() as $header)
+ {
+ if ((session_cache_limiter() == '' AND stripos($header, 'Last-Modified:') === 0)
+ OR stripos($header, 'Expires:') === 0)
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+ }
+
+ /**
+ * Prevent any output from being displayed. Executed during system.display.
+ *
+ * @return void
+ */
+ public static function prevent_output()
+ {
+ Kohana::$output = '';
+ }
+
+} // End expires
\ No newline at end of file
diff --git a/system/helpers/feed.php b/system/helpers/feed.php
new file mode 100644
index 00000000..74bb2f6b
--- /dev/null
+++ b/system/helpers/feed.php
@@ -0,0 +1,122 @@
+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 = '';
+ $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 00000000..b1b71740
--- /dev/null
+++ b/system/helpers/file.php
@@ -0,0 +1,186 @@
+'."\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);
+ }
+
+ /**
+ * Generates a fieldset opening tag.
+ *
+ * @param array html attributes
+ * @param string a string to be attached to the end of the attributes
+ * @return string
+ */
+ public static function open_fieldset($data = NULL, $extra = '')
+ {
+ return ''."\n";
+ }
+
+ /**
+ * Generates a legend tag for use with a fieldset.
+ *
+ * @param string legend text
+ * @param array HTML attributes
+ * @param string a string to be attached to the end of the attributes
+ * @return string
+ */
+ public static function legend($text = '', $data = NULL, $extra = '')
+ {
+ return ''."\n";
+ }
+
+ /**
+ * Generates hidden form fields.
+ * You can pass a simple key/value string or an associative array with multiple values.
+ *
+ * @param string|array input name (string) or key/value pairs (array)
+ * @param string input value, if using an input name
+ * @return string
+ */
+ public static function hidden($data, $value = '')
+ {
+ if ( ! is_array($data))
+ {
+ $data = array
+ (
+ $data => $value
+ );
+ }
+
+ $input = '';
+ foreach ($data as $name => $value)
+ {
+ $attr = array
+ (
+ 'type' => 'hidden',
+ 'name' => $name,
+ 'value' => $value
+ );
+
+ $input .= form::input($attr)."\n";
+ }
+
+ return $input;
+ }
+
+ /**
+ * 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 '';
+ }
+
+ /**
+ * 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);
+ }
+
+ // 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 '';
+ }
+
+ /**
+ * 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 = '';
+
+ 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 '';
+ }
+
+ /**
+ * Closes an open form tag.
+ *
+ * @param string string to be attached after the closing tag
+ * @return string
+ */
+ public static function close($extra = '')
+ {
+ return ''."\n".$extra;
+ }
+
+ /**
+ * 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 '';
+ }
+
+ /**
+ * 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 '';
+
+ if (isset($attr['name']) AND empty($attr['id']) AND strpos($attr['name'], '[') === FALSE)
+ {
+ if ($type === NULL AND ! empty($attr['type']))
+ {
+ // Set the type by the attributes
+ $type = $attr['type'];
+ }
+
+ switch ($type)
+ {
+ case 'text':
+ case 'textarea':
+ case 'password':
+ case 'select':
+ case 'checkbox':
+ case 'file':
+ case 'image':
+ case 'button':
+ case 'submit':
+ // Only specific types of inputs use name to id matching
+ $attr['id'] = $attr['name'];
+ break;
+ }
+ }
+
+ $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
\ No newline at end of file
diff --git a/system/helpers/format.php b/system/helpers/format.php
new file mode 100644
index 00000000..fb8a0294
--- /dev/null
+++ b/system/helpers/format.php
@@ -0,0 +1,66 @@
+='))
+ {
+ $str = htmlspecialchars($str, ENT_QUOTES, 'UTF-8', FALSE);
+ }
+ else
+ {
+ $str = preg_replace('/&(?!(?:#\d++|[a-z]++);)/ui', '&', $str);
+ $str = str_replace(array('<', '>', '\'', '"'), array('<', '>', ''', '"'), $str);
+ }
+ }
+
+ return $str;
+ }
+
+ /**
+ * Perform a html::specialchars() with additional URL specific encoding.
+ *
+ * @param string string to convert
+ * @param boolean encode existing entities
+ * @return string
+ */
+ public static function specialurlencode($str, $double_encode = TRUE)
+ {
+ return str_replace(' ', '%20', html::specialchars($str, $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
+ * @return string
+ */
+ public static function anchor($uri, $title = NULL, $attributes = NULL, $protocol = NULL)
+ {
+ if ($uri === '')
+ {
+ $site_url = url::base(FALSE);
+ }
+ elseif (strpos($uri, '://') === FALSE AND strpos($uri, '#') !== 0)
+ {
+ $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
+ ''
+ // Title empty? Use the parsed URL
+ .(($title === NULL) ? $site_url : $title).'';
+ }
+
+ /**
+ * 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
+ ''
+ // Title empty? Use the filename part of the URI
+ .(($title === NULL) ? end(explode('/', $file)) : $title) .'';
+ }
+
+ /**
+ * Similar to anchor, but with the protocol parameter first.
+ *
+ * @param string link protocol
+ * @param string URI or URL to link to
+ * @param string link text
+ * @param array HTML anchor attributes
+ * @return string
+ */
+ public static function panchor($protocol, $uri, $title = NULL, $attributes = FALSE)
+ {
+ return html::anchor($uri, $title, $attributes, $protocol);
+ }
+
+ /**
+ * Create an array of anchors from an array of link/title pairs.
+ *
+ * @param array link/title pairs
+ * @return array
+ */
+ public static function anchor_array(array $array)
+ {
+ $anchors = array();
+ foreach ($array as $link => $title)
+ {
+ // Create list of anchors
+ $anchors[] = html::anchor($link, $title);
+ }
+ return $anchors;
+ }
+
+ /**
+ * 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 .= ''.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 ''.$title.'';
+ }
+
+ /**
+ * 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 '';
+ }
+
+ /**
+ * 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', '.css', $media, $index);
+ }
+
+ /**
+ * Creates a link tag.
+ *
+ * @param string|array filename
+ * @param string|array relationship
+ * @param string|array mimetype
+ * @param string specifies suffix of the file
+ * @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, $suffix = FALSE, $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, $suffix, $_media, $index);
+ }
+ }
+ else
+ {
+ if (strpos($href, '://') === FALSE)
+ {
+ // Make the URL absolute
+ $href = url::base($index).$href;
+ }
+
+ $length = strlen($suffix);
+
+ if ( $length > 0 AND substr_compare($href, $suffix, -$length, $length, FALSE) !== 0)
+ {
+ // Add the defined suffix
+ $href .= $suffix;
+ }
+
+ $attr = array
+ (
+ 'rel' => $rel,
+ 'type' => $type,
+ 'href' => $href,
+ );
+
+ if ( ! empty($media))
+ {
+ // Add the media type to the attributes
+ $attr['media'] = $media;
+ }
+
+ $compiled = '';
+ }
+
+ 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;
+ }
+
+ if (substr_compare($script, '.js', -3, 3, FALSE) !== 0)
+ {
+ // Add the javascript suffix
+ $script .= '.js';
+ }
+
+ $compiled = '';
+ }
+
+ 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 '';
+ }
+
+ /**
+ * 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.'="'.html::specialchars($val).'"';
+ }
+
+ return $compiled;
+ }
+
+} // End html
diff --git a/system/helpers/inflector.php b/system/helpers/inflector.php
new file mode 100644
index 00000000..1e4fee23
--- /dev/null
+++ b/system/helpers/inflector.php
@@ -0,0 +1,193 @@
+ 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)
+ {
+ // 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 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 preg_replace('/\s+/', '_', trim($str));
+ }
+
+ /**
+ * Makes an underscored or dashed phrase human-reable.
+ *
+ * @param string phrase to make human-reable
+ * @return string
+ */
+ public static function humanize($str)
+ {
+ return preg_replace('/[_-]+/', ' ', trim($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 00000000..3eb5d5ac
--- /dev/null
+++ b/system/helpers/num.php
@@ -0,0 +1,26 @@
+ 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)
+ {
+ // Initialize
+ $mime_types = array();
+ $max_q = 0;
+ $preferred = FALSE;
+
+ // Load q values for all given content types
+ foreach (array_unique($types) as $type)
+ {
+ $mime_types[$type] = request::accepts_at_quality($type, $explicit_check);
+ }
+
+ // Look for the highest q value
+ foreach ($mime_types as $type => $q)
+ {
+ if ($q > $max_q)
+ {
+ $max_q = $q;
+ $preferred = $type;
+ }
+ }
+
+ 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 = NULL, $explicit_check = FALSE)
+ {
+ request::parse_accept_header();
+
+ // Normalize type
+ $type = strtolower((string) $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]];
+
+ // Wildcard match (if not checking explicitly)
+ if ($explicit_check === FALSE AND isset(request::$accept_types[$type[0]]['*']))
+ return request::$accept_types[$type[0]]['*'];
+
+ // Catch-all wildcard match (if not checking explicitly)
+ if ($explicit_check === FALSE AND isset(request::$accept_types['*']['*']))
+ return request::$accept_types['*']['*'];
+
+ // Content type not accepted
+ return 0;
+ }
+
+ /**
+ * Parses client's HTTP Accept request header, and builds array structure representing it.
+ *
+ * @return void
+ */
+ protected static function parse_accept_header()
+ {
+ // Run this function just once
+ if (request::$accept_types !== NULL)
+ return;
+
+ // Initialize accept_types array
+ request::$accept_types = array();
+
+ // No HTTP Accept header found
+ if (empty($_SERVER['HTTP_ACCEPT']))
+ {
+ // Accept everything
+ request::$accept_types['*']['*'] = 1;
+ return;
+ }
+
+ // Remove linebreaks and parse the HTTP Accept header
+ foreach (explode(',', str_replace(array("\r", "\n"), '', $_SERVER['HTTP_ACCEPT'])) as $accept_entry)
+ {
+ // Explode each entry in content type and possible quality factor
+ $accept_entry = explode(';', trim($accept_entry), 2);
+
+ // Explode each content type (e.g. "text/html")
+ $type = explode('/', $accept_entry[0], 2);
+
+ // Skip invalid content types
+ if ( ! isset($type[1]))
+ continue;
+
+ // Assume a default quality factor of 1 if no custom q value found
+ $q = (isset($accept_entry[1]) AND preg_match('~\bq\s*+=\s*+([.0-9]+)~', $accept_entry[1], $match)) ? (float) $match[1] : 1;
+
+ // Populate accept_types array
+ if ( ! isset(request::$accept_types[$type[0]][$type[1]]) OR $q > request::$accept_types[$type[0]][$type[1]])
+ {
+ request::$accept_types[$type[0]][$type[1]] = $q;
+ }
+ }
+ }
+
+} // End request
\ No newline at end of file
diff --git a/system/helpers/security.php b/system/helpers/security.php
new file mode 100644
index 00000000..cd48d2e0
--- /dev/null
+++ b/system/helpers/security.php
@@ -0,0 +1,47 @@
+xss_clean($str);
+ }
+
+ /**
+ * Remove image tags from a string.
+ *
+ * @param string string to sanitize
+ * @return string
+ */
+ public static function strip_image_tags($str)
+ {
+ return preg_replace('#\s]*)["\']?[^>]*)?>#is', '$1', $str);
+ }
+
+ /**
+ * Remove PHP tags from a string.
+ *
+ * @param string string to sanitize
+ * @return string
+ */
+ public static function encode_php_tags($str)
+ {
+ return str_replace(array('', '?>'), array('<?', '?>'), $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 00000000..d0e573ec
--- /dev/null
+++ b/system/helpers/text.php
@@ -0,0 +1,410 @@
+ 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('#(? $badword)
+ {
+ $badwords[$key] = str_replace('\*', '\S*?', preg_quote((string) $badword));
+ }
+
+ $regex = '('.implode('|', $badwords).')';
+
+ if ($replace_partial_words == TRUE)
+ {
+ // Just using \b isn't sufficient when we need to replace a badword that already contains word boundaries itself
+ $regex = '(?<=\b|\s|^)'.$regex.'(?=\b|\s|$)';
+ }
+
+ $regex = '!'.$regex.'!ui';
+
+ if (utf8::strlen($replacement) == 1)
+ {
+ $regex .= 'e';
+ return preg_replace($regex, 'str_repeat($replacement, utf8::strlen(\'$1\'))', $str);
+ }
+
+ return preg_replace($regex, $replacement, $str);
+ }
+
+ /**
+ * Finds the text that is similar between a set of words.
+ *
+ * @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);
+ }
+
+ /**
+ * Converts text email addresses and anchors into links.
+ *
+ * @param string text to auto link
+ * @return string
+ */
+ public static function auto_link($text)
+ {
+ // Auto link emails first to prevent problems with "www.domain.com@example.com"
+ return text::auto_link_urls(text::auto_link_emails($text));
+ }
+
+ /**
+ * Converts text anchors into links.
+ *
+ * @param string text to auto link
+ * @return string
+ */
+ public static function auto_link_urls($text)
+ {
+ // Finds all http/https/ftp/ftps links that are not part of an existing html anchor
+ if (preg_match_all('~\b(?)(?:ht|f)tps?://\S+(?:/|\b)~i', $text, $matches))
+ {
+ foreach ($matches[0] as $match)
+ {
+ // Replace each link with an anchor
+ $text = str_replace($match, html::anchor($match), $text);
+ }
+ }
+
+ // Find all naked www.links.com (without http://)
+ if (preg_match_all('~\b(?|58;)(?!\.)[-+_a-z0-9.]++(? and markup to text. Basically nl2br() on steroids.
+ *
+ * @param string subject
+ * @return string
+ */
+ public static function auto_p($str)
+ {
+ // 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
magic!
+ $str = '
'.trim($str).'
';
+ $str = preg_replace('~\n{2,}~', "
\n\n
", $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('~