diff options
Diffstat (limited to 'kohana/helpers')
-rw-r--r-- | kohana/helpers/arr.php | 291 | ||||
-rw-r--r-- | kohana/helpers/cookie.php | 84 | ||||
-rw-r--r-- | kohana/helpers/date.php | 399 | ||||
-rw-r--r-- | kohana/helpers/download.php | 105 | ||||
-rw-r--r-- | kohana/helpers/email.php | 181 | ||||
-rw-r--r-- | kohana/helpers/expires.php | 110 | ||||
-rw-r--r-- | kohana/helpers/feed.php | 116 | ||||
-rw-r--r-- | kohana/helpers/file.php | 177 | ||||
-rw-r--r-- | kohana/helpers/form.php | 526 | ||||
-rw-r--r-- | kohana/helpers/format.php | 66 | ||||
-rw-r--r-- | kohana/helpers/html.php | 400 | ||||
-rw-r--r-- | kohana/helpers/inflector.php | 193 | ||||
-rw-r--r-- | kohana/helpers/num.php | 26 | ||||
-rw-r--r-- | kohana/helpers/remote.php | 66 | ||||
-rw-r--r-- | kohana/helpers/request.php | 217 | ||||
-rw-r--r-- | kohana/helpers/security.php | 47 | ||||
-rw-r--r-- | kohana/helpers/text.php | 389 | ||||
-rw-r--r-- | kohana/helpers/upload.php | 162 | ||||
-rw-r--r-- | kohana/helpers/url.php | 247 | ||||
-rw-r--r-- | kohana/helpers/valid.php | 313 |
20 files changed, 4115 insertions, 0 deletions
diff --git a/kohana/helpers/arr.php b/kohana/helpers/arr.php new file mode 100644 index 00000000..b0592347 --- /dev/null +++ b/kohana/helpers/arr.php @@ -0,0 +1,291 @@ +<?php defined('SYSPATH') or die('No direct script access.'); +/** + * Array helper class. + * + * $Id$ + * + * @package Core + * @author Kohana Team + * @copyright (c) 2007-2008 Kohana Team + * @license http://kohanaphp.com/license.html + */ +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) + { + 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; + } + + /** + * Binary search algorithm. + * + * @param mixed the value to search for + * @param array an array of values to search in + * @param boolean return false, or the nearest value + * @param mixed sort the array before searching it + * @return integer + */ + public static function binary_search($needle, $haystack, $nearest = FALSE, $sort = FALSE) + { + if ($sort === TRUE) + { + sort($haystack); + } + + $high = count($haystack); + $low = 0; + + while ($high - $low > 1) + { + $probe = ($high + $low) / 2; + if ($haystack[$probe] < $needle) + { + $low = $probe; + } + else + { + $high = $probe; + } + } + + if ($high == count($haystack) OR $haystack[$high] != $needle) + { + if ($nearest === FALSE) + return FALSE; + + // return the nearest value + $high_distance = $haystack[ceil($low)] - $needle; + $low_distance = $needle - $haystack[floor($low)]; + + return ($high_distance >= $low_distance) ? $haystack[ceil($low)] : $haystack[floor($low)]; + } + + return $high; + } + + /** + * 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) + { + foreach (array_slice(func_get_args(), 1) as $array2) + { + foreach ($array2 as $key => $value) + { + if (array_key_exists($key, $array1)) + { + $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; + } + +} // End arr
\ No newline at end of file diff --git a/kohana/helpers/cookie.php b/kohana/helpers/cookie.php new file mode 100644 index 00000000..d10c6966 --- /dev/null +++ b/kohana/helpers/cookie.php @@ -0,0 +1,84 @@ +<?php defined('SYSPATH') or die('No direct script access.'); +/** + * Cookie helper class. + * + * $Id$ + * + * @package Core + * @author Kohana Team + * @copyright (c) 2007-2008 Kohana Team + * @license http://kohanaphp.com/license.html + */ +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]; + } + } + + // Expiration timestamp + $expire = ($expire == 0) ? 0 : time() + (int) $expire; + + 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, $default = NULL, $xss_clean = FALSE) + { + return Input::instance()->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/kohana/helpers/date.php b/kohana/helpers/date.php new file mode 100644 index 00000000..c6607009 --- /dev/null +++ b/kohana/helpers/date.php @@ -0,0 +1,399 @@ +<?php defined('SYSPATH') or die('No direct script access.'); +/** + * Date helper class. + * + * $Id$ + * + * @package Core + * @author Kohana Team + * @copyright (c) 2007-2008 Kohana Team + * @license http://kohanaphp.com/license.html + */ +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 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 ($name !== $last AND $amount === 0) + { + // Skip empty amounts + continue; + } + + // Add the amount to the span + $span[] = ($name === $last ? ' and ' : ', ').$amount.' '.($amount === 1 ? inflector::singular($name) : $name); + } + + // 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/kohana/helpers/download.php b/kohana/helpers/download.php new file mode 100644 index 00000000..9151208f --- /dev/null +++ b/kohana/helpers/download.php @@ -0,0 +1,105 @@ +<?php defined('SYSPATH') or die('No direct script access.'); +/** + * Download helper class. + * + * $Id$ + * + * @package Core + * @author Kohana Team + * @copyright (c) 2007-2008 Kohana Team + * @license http://kohanaphp.com/license.html + */ +class download_Core { + + /** + * Force a download of a file to the user's browser. This function is + * binary-safe and will work with any MIME type that Kohana is aware of. + * + * @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) + { + if (empty($filename)) + return FALSE; + + if (is_file($filename)) + { + // Get the real path + $filepath = str_replace('\\', '/', realpath($filename)); + + // Set filesize + $filesize = filesize($filepath); + + // Get filename + $filename = substr(strrchr('/'.$filepath, '/'), 1); + + // Get extension + $extension = strtolower(substr(strrchr($filepath, '.'), 1)); + } + else + { + // Get filesize + $filesize = strlen($data); + + // Make sure the filename does not have directory info + $filename = substr(strrchr('/'.$filename, '/'), 1); + + // Get extension + $extension = strtolower(substr(strrchr($filename, '.'), 1)); + } + + // Get the mime type of the file + $mime = Kohana::config('mimes.'.$extension); + + if (empty($mime)) + { + // Set a default mime if none was found + $mime = array('application/octet-stream'); + } + + // Generate the server headers + header('Content-Type: '.$mime[0]); + header('Content-Disposition: attachment; filename="'.(empty($nicename) ? $filename : $nicename).'"'); + header('Content-Transfer-Encoding: binary'); + header('Content-Length: '.sprintf('%d', $filesize)); + + // More caching prevention + header('Expires: 0'); + + if (Kohana::user_agent('browser') === 'Internet Explorer') + { + // Send IE headers + header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); + header('Pragma: public'); + } + else + { + // Send normal headers + header('Pragma: no-cache'); + } + + // Clear the output buffer + Kohana::close_buffers(FALSE); + + if (isset($filepath)) + { + // Open the file + $handle = fopen($filepath, 'rb'); + + // Send the file data + fpassthru($handle); + + // Close the file + fclose($handle); + } + else + { + // Send the file data + echo $data; + } + } + +} // End download
\ No newline at end of file diff --git a/kohana/helpers/email.php b/kohana/helpers/email.php new file mode 100644 index 00000000..89f29096 --- /dev/null +++ b/kohana/helpers/email.php @@ -0,0 +1,181 @@ +<?php defined('SYSPATH') or die('No direct script access.'); +/** + * Email helper class. + * + * $Id$ + * + * @package Core + * @author Kohana Team + * @copyright (c) 2007-2008 Kohana Team + * @license http://kohanaphp.com/license.html + */ +class email_Core { + + // SwiftMailer instance + protected static $mail; + + /** + * Creates a SwiftMailer instance. + * + * @param string DSN connection string + * @return object Swift object + */ + public static function connect($config = NULL) + { + if ( ! class_exists('Swift', FALSE)) + { + // Load SwiftMailer + require Kohana::find_file('vendor', 'swift/Swift'); + + // Register the Swift ClassLoader as an autoload + spl_autoload_register(array('Swift_ClassLoader', 'load')); + } + + // Load default configuration + ($config === NULL) and $config = Kohana::config('email'); + + switch ($config['driver']) + { + case 'smtp': + // Set port + $port = empty($config['options']['port']) ? NULL : (int) $config['options']['port']; + + if (empty($config['options']['encryption'])) + { + // No encryption + $encryption = Swift_Connection_SMTP::ENC_OFF; + } + else + { + // Set encryption + switch (strtolower($config['options']['encryption'])) + { + case 'tls': $encryption = Swift_Connection_SMTP::ENC_TLS; break; + case 'ssl': $encryption = Swift_Connection_SMTP::ENC_SSL; break; + } + } + + // Create a SMTP connection + $connection = new Swift_Connection_SMTP($config['options']['hostname'], $port, $encryption); + + // Do authentication, if part of the DSN + empty($config['options']['username']) or $connection->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; + 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/kohana/helpers/expires.php b/kohana/helpers/expires.php new file mode 100644 index 00000000..72bfd79b --- /dev/null +++ b/kohana/helpers/expires.php @@ -0,0 +1,110 @@ +<?php defined('SYSPATH') or die('No direct script access.'); +/** + * Controls headers that effect client caching of pages + * + * $Id$ + * + * @package Core + * @author Kohana Team + * @copyright (c) 2007-2008 Kohana Team + * @license http://kohanaphp.com/license.html + */ +class expires_Core { + + /** + * Sets the amount of time before a page expires + * + * @param integer Seconds before the page expires + * @return boolean + */ + public static function set($seconds = 60) + { + if (expires::check_headers()) + { + $now = $expires = time(); + + // Set the expiration timestamp + $expires += $seconds; + + // Send headers + header('Last-Modified: '.gmdate('D, d M Y H:i:s T', $now)); + header('Expires: '.gmdate('D, d M Y H:i:s T', $expires)); + header('Cache-Control: max-age='.$seconds); + + return $expires; + } + + return FALSE; + } + + /** + * Checks to see if a page should be updated or send Not Modified status + * + * @param integer Seconds added to the modified time received to calculate what should be sent + * @return bool FALSE when the request needs to be updated + */ + public static function check($seconds = 60) + { + if ( ! empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) AND expires::check_headers()) + { + if (($strpos = strpos($_SERVER['HTTP_IF_MODIFIED_SINCE'], ';')) !== FALSE) + { + // IE6 and perhaps other IE versions send length too, compensate here + $mod_time = substr($_SERVER['HTTP_IF_MODIFIED_SINCE'], 0, $strpos); + } + else + { + $mod_time = $_SERVER['HTTP_IF_MODIFIED_SINCE']; + } + + $mod_time = strtotime($mod_time); + $mod_time_diff = $mod_time + $seconds - time(); + + if ($mod_time_diff > 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 (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/kohana/helpers/feed.php b/kohana/helpers/feed.php new file mode 100644 index 00000000..2049c7bd --- /dev/null +++ b/kohana/helpers/feed.php @@ -0,0 +1,116 @@ +<?php defined('SYSPATH') or die('No direct script access.'); +/** + * Feed helper class. + * + * $Id$ + * + * @package Core + * @author Kohana Team + * @copyright (c) 2007-2008 Kohana Team + * @license http://kohanaphp.com/license.html + */ +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) + { + // 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 + * @return string + */ + public static function create($info, $items, $format = 'rss2') + { + $info += array('title' => 'Generated Feed', 'link' => '', 'generator' => 'KohanaPHP'); + + $feed = '<?xml version="1.0"?><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/kohana/helpers/file.php b/kohana/helpers/file.php new file mode 100644 index 00000000..65268e08 --- /dev/null +++ b/kohana/helpers/file.php @@ -0,0 +1,177 @@ +<?php defined('SYSPATH') or die('No direct script access.'); +/** + * File helper class. + * + * $Id$ + * + * @package Core + * @author Kohana Team + * @copyright (c) 2007-2008 Kohana Team + * @license http://kohanaphp.com/license.html + */ +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 ( ! 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/kohana/helpers/form.php b/kohana/helpers/form.php new file mode 100644 index 00000000..b432fc78 --- /dev/null +++ b/kohana/helpers/form.php @@ -0,0 +1,526 @@ +<?php defined('SYSPATH') or die('No direct script access.'); +/** + * Form helper class. + * + * $Id$ + * + * @package Core + * @author Kohana Team + * @copyright (c) 2007-2008 Kohana Team + * @license http://kohanaphp.com/license.html + */ +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 + * @return string + */ + public static function open($action = NULL, $attr = array(), $hidden = 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); + } + elseif (strpos($action, '://') === FALSE) + { + // Make the action URI into a URL + $action = url::site($action); + } + + // 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); + } + + /** + * 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 '<fieldset'.html::attributes((array) $data).' '.$extra.'>'."\n"; + } + + /** + * Generates a fieldset closing tag. + * + * @return string + */ + public static function close_fieldset() + { + return '</fieldset>'."\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 '<legend'.form::attributes((array) $data).' '.$extra.'>'.$text.'</legend>'."\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 + ); + + // For safe form data + $data['value'] = html::specialchars($data['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 + * @return string + */ + public static function textarea($data, $value = '', $extra = '') + { + 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 '<textarea'.form::attributes($data, 'textarea').' '.$extra.'>'.html::specialchars($value).'</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 option key 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']; + } + } + + // Selected value should always be a string + $selected = (string) $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 = ($selected === $inner_key) ? ' selected="selected"' : ''; + $input .= '<option value="'.$inner_key.'"'.$sel.'>'.$inner_val.'</option>'."\n"; + } + $input .= '</optgroup>'."\n"; + } + else + { + $sel = ($selected === $key) ? ' selected="selected"' : ''; + $input .= '<option value="'.$key.'"'.$sel.'>'.$val.'</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>'; + } + + /** + * Closes an open form tag. + * + * @param string string to be attached after the closing tag + * @return string + */ + public static function close($extra = '') + { + return '</form>'."\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 = '', $extra = '') + { + if ( ! is_array($data)) + { + if (strpos($data, '[') !== FALSE) + { + $data = preg_replace('/\[.*\]/', '', $data); + } + + $data = empty($data) ? array() : array('for' => $data); + } + + 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 ''; + + 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/kohana/helpers/format.php b/kohana/helpers/format.php new file mode 100644 index 00000000..dd37be11 --- /dev/null +++ b/kohana/helpers/format.php @@ -0,0 +1,66 @@ +<?php defined('SYSPATH') or die('No direct script access.'); +/** + * Format helper class. + * + * $Id$ + * + * @package Core + * @author Kohana Team + * @copyright (c) 2007-2008 Kohana Team + * @license http://kohanaphp.com/license.html + */ +class format_Core { + + /** + * 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; + } + +} // End format
\ No newline at end of file diff --git a/kohana/helpers/html.php b/kohana/helpers/html.php new file mode 100644 index 00000000..3cb8d5a4 --- /dev/null +++ b/kohana/helpers/html.php @@ -0,0 +1,400 @@ +<?php defined('SYSPATH') or die('No direct script access.'); +/** + * HTML helper class. + * + * $Id$ + * + * @package Core + * @author Kohana Team + * @copyright (c) 2007-2008 Kohana Team + * @license http://kohanaphp.com/license.html + */ +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 specialchars($str, $double_encode = TRUE) + { + // Force the string to be a string + $str = (string) $str; + + // Do encode existing HTML entities (default) + if ($double_encode === TRUE) + { + $str = htmlspecialchars($str, ENT_QUOTES, 'UTF-8'); + } + else + { + // Do not encode existing HTML entities + // From PHP 5.2.3 this functionality is built-in, otherwise use a regex + if (version_compare(PHP_VERSION, '5.2.3', '>=')) + { + $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; + } + + /** + * 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 + '<a href="'.html::specialchars($site_url, FALSE).'"' + // Attributes empty? Use an empty string + .(is_array($attributes) ? html::attributes($attributes) : '').'>' + // Title empty? Use the parsed URL + .(($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="'.html::specialchars(url::base(FALSE, $protocol).$file, 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>'; + } + + /** + * 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 = FALSE, $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 .= '&#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="mailto:'.$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', '.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 + { + // Add the suffix only when it's not already present + $suffix = ( ! empty($suffix) AND strpos($href, $suffix) === FALSE) ? $suffix : ''; + $media = empty($media) ? '' : ' media="'.$media.'"'; + $compiled = '<link rel="'.$rel.'" type="'.$type.'" href="'.url::base((bool) $index).$href.$suffix.'"'.$media.' />'; + } + + 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 + { + // Do not touch full URLs + if (strpos($script, '://') === FALSE) + { + // Add the suffix only when it's not already present + $suffix = (substr($script, -3) !== '.js') ? '.js' : ''; + $script = url::base((bool) $index).$script.$suffix; + } + + $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.'="'.$val.'"'; + } + + return $compiled; + } + +} // End html diff --git a/kohana/helpers/inflector.php b/kohana/helpers/inflector.php new file mode 100644 index 00000000..0e980390 --- /dev/null +++ b/kohana/helpers/inflector.php @@ -0,0 +1,193 @@ +<?php defined('SYSPATH') or die('No direct script access.'); +/** + * Inflector helper class. + * + * $Id$ + * + * @package Core + * @author Kohana Team + * @copyright (c) 2007-2008 Kohana Team + * @license http://kohanaphp.com/license.html + */ +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 (self::$uncountable === NULL) + { + // Cache uncountables + self::$uncountable = Kohana::config('inflector.uncountable'); + + // Make uncountables mirroed + self::$uncountable = array_combine(self::$uncountable, self::$uncountable); + } + + return isset(self::$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) + { + // 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(self::$cache[$key])) + return self::$cache[$key]; + + if (inflector::uncountable($str)) + return self::$cache[$key] = $str; + + if (empty(self::$irregular)) + { + // Cache irregular words + self::$irregular = Kohana::config('inflector.irregular'); + } + + if ($irregular = array_search($str, self::$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 self::$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(self::$cache[$key])) + return self::$cache[$key]; + + if (inflector::uncountable($str)) + return self::$cache[$key] = $str; + + if (empty(self::$irregular)) + { + // Cache irregular words + self::$irregular = Kohana::config('inflector.irregular'); + } + + if (isset(self::$irregular[$str])) + { + $str = self::$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 self::$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/kohana/helpers/num.php b/kohana/helpers/num.php new file mode 100644 index 00000000..62516e81 --- /dev/null +++ b/kohana/helpers/num.php @@ -0,0 +1,26 @@ +<?php defined('SYSPATH') or die('No direct script access.'); +/** + * Number helper class. + * + * $Id$ + * + * @package Core + * @author Kohana Team + * @copyright (c) 2007-2008 Kohana Team + * @license http://kohanaphp.com/license.html + */ +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/kohana/helpers/remote.php b/kohana/helpers/remote.php new file mode 100644 index 00000000..f37d156e --- /dev/null +++ b/kohana/helpers/remote.php @@ -0,0 +1,66 @@ +<?php defined('SYSPATH') or die('No direct script access.'); +/** + * Remote url/file helper. + * + * $Id$ + * + * @package Core + * @author Kohana Team + * @copyright (c) 2007-2008 Kohana Team + * @license http://kohanaphp.com/license.html + */ +class remote_Core { + + public static function status($url) + { + if ( ! valid::url($url, 'http')) + 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'].' 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
\ No newline at end of file diff --git a/kohana/helpers/request.php b/kohana/helpers/request.php new file mode 100644 index 00000000..17408643 --- /dev/null +++ b/kohana/helpers/request.php @@ -0,0 +1,217 @@ +<?php defined('SYSPATH') or die('No direct script access.'); +/** + * Request helper class. + * + * $Id$ + * + * @package Core + * @author Kohana Team + * @copyright (c) 2007-2008 Kohana Team + * @license http://kohanaphp.com/license.html + */ +class request_Core { + + // Possible HTTP methods + protected static $http_methods = array('get', 'head', 'options', 'post', 'put', 'delete'); + + // Content types from client's HTTP Accept request header (array) + protected static $accept_types; + + /** + * Returns the HTTP referrer, or the default if the referrer is not set. + * + * @param mixed default to return + * @return string + */ + public static function referrer($default = FALSE) + { + if ( ! empty($_SERVER['HTTP_REFERER'])) + { + // Set referrer + $ref = $_SERVER['HTTP_REFERER']; + + if (strpos($ref, url::base(FALSE)) === 0) + { + // Remove the base URL from the referrer + $ref = substr($ref, strlen(url::base(TRUE))); + } + } + + return isset($ref) ? $ref : $default; + } + + /** + * 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, self::$http_methods)) + throw new Kohana_Exception('request.unknown_method', $method); + + return $method; + } + + /** + * 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_header(); + + if ($type === NULL) + return self::$accept_types; + + return (request::accepts_at_quality($type, $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) + { + // 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(self::$accept_types[$type[0]][$type[1]])) + return self::$accept_types[$type[0]][$type[1]]; + + // Wildcard match (if not checking explicitly) + if ($explicit_check === FALSE AND isset(self::$accept_types[$type[0]]['*'])) + return self::$accept_types[$type[0]]['*']; + + // Catch-all wildcard match (if not checking explicitly) + if ($explicit_check === FALSE AND isset(self::$accept_types['*']['*'])) + return self::$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 (self::$accept_types !== NULL) + return; + + // Initialize accept_types array + self::$accept_types = array(); + + // No HTTP Accept header found + if (empty($_SERVER['HTTP_ACCEPT'])) + { + // Accept everything + self::$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(self::$accept_types[$type[0]][$type[1]]) OR $q > self::$accept_types[$type[0]][$type[1]]) + { + self::$accept_types[$type[0]][$type[1]] = $q; + } + } + } + +} // End request
\ No newline at end of file diff --git a/kohana/helpers/security.php b/kohana/helpers/security.php new file mode 100644 index 00000000..de723d76 --- /dev/null +++ b/kohana/helpers/security.php @@ -0,0 +1,47 @@ +<?php defined('SYSPATH') or die('No direct script access.'); +/** + * Security helper class. + * + * $Id$ + * + * @package Core + * @author Kohana Team + * @copyright (c) 2007-2008 Kohana Team + * @license http://kohanaphp.com/license.html + */ +class security_Core { + + /** + * Sanitize a string with the xss_clean method. + * + * @param string string to sanitize + * @return string + */ + public static function xss_clean($str) + { + return Input::instance()->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('#<img\s.*?(?:src\s*=\s*["\']?([^"\'<>\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/kohana/helpers/text.php b/kohana/helpers/text.php new file mode 100644 index 00000000..ea648a19 --- /dev/null +++ b/kohana/helpers/text.php @@ -0,0 +1,389 @@ +<?php defined('SYSPATH') or die('No direct script access.'); +/** + * Text helper class. + * + * $Id$ + * + * @package Core + * @author Kohana Team + * @copyright (c) 2007-2008 Kohana Team + * @license http://kohanaphp.com/license.html + */ +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 utf8::strlen($str) <= $limit) + return $str; + + if ($limit <= 0) + return $end_char; + + if ($preserve_words == FALSE) + { + return rtrim(utf8::substr($str, 0, $limit)).$end_char; + } + + 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 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 'numeric': + $pool = '0123456789'; + break; + case 'nozero': + $pool = '123456789'; + break; + case 'distinct': + $pool = '2345679ACDEFHJKLMNPRSTUVWXYZ'; + break; + default: + $pool = (string) $type; + $utf8 = ! utf8::is_ascii($pool); + break; + } + + $str = ''; + + $pool_size = ($utf8 === TRUE) ? utf8::strlen($pool) : strlen($pool); + + for ($i = 0; $i < $length; $i++) + { + $str .= ($utf8 === TRUE) + ? utf8::substr($pool, mt_rand(0, $pool_size - 1), 1) + : substr($pool, mt_rand(0, $pool_size - 1), 1); + } + + 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 = FALSE) + { + foreach ((array) $badwords as $key => $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(?<!href="|">)(?: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(?<!://)www(?:\.[a-z0-9][-a-z0-9]*+)+\.[a-z]{2,6}\b~i', $text, $matches)) + { + foreach ($matches[0] as $match) + { + // Replace each link with an anchor + $text = str_replace($match, html::anchor('http://'.$match, $match), $text); + } + } + + return $text; + } + + /** + * 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 : or : or : 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 + * @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 <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 /> + $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).' '.substr($str, $space + 1); + } + + return $str; + } + +} // End text
\ No newline at end of file diff --git a/kohana/helpers/upload.php b/kohana/helpers/upload.php new file mode 100644 index 00000000..15839640 --- /dev/null +++ b/kohana/helpers/upload.php @@ -0,0 +1,162 @@ +<?php defined('SYSPATH') or die('No direct script access.'); +/** + * Upload helper class for working with the global $_FILES + * array and Validation library. + * + * $Id$ + * + * @package Core + * @author Kohana Team + * @copyright (c) 2007-2008 Kohana Team + * @license http://kohanaphp.com/license.html + */ +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('upload.not_writable', $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)); + + // Get the mime types for the extension + $mime_types = Kohana::config('mimes.'.$extension); + + // Make sure there is an extension, that the extension is allowed, and that mime types exist + return ( ! empty($extension) AND in_array($extension, $allowed_types) AND is_array($mime_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/kohana/helpers/url.php b/kohana/helpers/url.php new file mode 100644 index 00000000..12453936 --- /dev/null +++ b/kohana/helpers/url.php @@ -0,0 +1,247 @@ +<?php defined('SYSPATH') or die('No direct script access.'); +/** + * URL helper class. + * + * $Id$ + * + * @package Core + * @author Kohana Team + * @copyright (c) 2007-2008 Kohana Team + * @license http://kohanaphp.com/license.html + */ +class url_Core { + + /** + * Fetches the current URI. + * + * @param boolean include the query string + * @return string + */ + public static function current($qs = FALSE) + { + return ($qs === TRUE) ? Router::$complete_uri : Router::$current_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 + $base_url = $protocol.'://'.$_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 _) + * @return string + */ + public static function title($title, $separator = '-') + { + $separator = ($separator === '-') ? '-' : '_'; + + // Replace accented characters by their unaccented equivalents + $title = utf8::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)); + + // 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. + * + * @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; + + $uri = (array) $uri; + + for ($i = 0, $count_uri = count($uri); $i < $count_uri; $i++) + { + if (strpos($uri[$i], '://') === FALSE) + { + $uri[$i] = url::site($uri[$i]); + } + } + + if ($method == '300') + { + if ($count_uri > 0) + { + header('HTTP/1.1 300 Multiple Choices'); + header('Location: '.$uri[0]); + + $choices = ''; + foreach ($uri as $href) + { + $choices .= '<li><a href="'.$href.'">'.$href.'</a></li>'; + } + + exit('<h1>301 - Multiple Choices:</h1><ul>'.$choices.'</ul>'); + } + } + else + { + $uri = $uri[0]; + + if ($method == 'refresh') + { + header('Refresh: 0; url='.$uri); + } + else + { + $codes = array + ( + '301' => 'Moved Permanently', + '302' => 'Found', + '303' => 'See Other', + '304' => 'Not Modified', + '305' => 'Use Proxy', + '307' => 'Temporary Redirect' + ); + + $method = isset($codes[$method]) ? $method : '302'; + + header('HTTP/1.1 '.$method.' '.$codes[$method]); + header('Location: '.$uri); + } + + exit('<h1>'.$method.' - '.$codes[$method].'</h1><p><a href="'.$uri.'">'.$uri.'</a></p>'); + } + } + +} // End url
\ No newline at end of file diff --git a/kohana/helpers/valid.php b/kohana/helpers/valid.php new file mode 100644 index 00000000..cee303fd --- /dev/null +++ b/kohana/helpers/valid.php @@ -0,0 +1,313 @@ +<?php defined('SYSPATH') or die('No direct script access.'); +/** + * Validation helper class. + * + * $Id$ + * + * @package Core + * @author Kohana Team + * @copyright (c) 2007-2008 Kohana Team + * @license http://kohanaphp.com/license.html + */ +class valid_Core { + + /** + * Validate email, commonly used characters only + * + * @param string 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. + * + * @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'); + } + + /** + * Validate email, RFC compliant version + * Note: 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); + } + + /** + * Validate URL + * + * @param string URL + * @return boolean + */ + public static function url($url) + { + return (bool) filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_HOST_REQUIRED); + } + + /** + * Validate IP + * + * @param string IP address + * @param boolean allow IPv6 addresses + * @return boolean + */ + public static function ip($ip, $ipv6 = FALSE) + { + // Do not allow private and reserved range IPs + $flags = FILTER_FLAG_NO_PRIV_RANGE | 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) formula. + * @see http://en.wikipedia.org/wiki/Luhn_algorithm + * + * @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. + * + * @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); + } + + /** + * 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). + * + * @param string input string + * @return boolean + */ + public static function numeric($str) + { + return (is_numeric($str) AND preg_match('/^[-0-9.]++$/D', (string) $str)); + } + + /** + * Checks whether a string is a valid text. Letters, numbers, whitespace, + * dashes, periods, and underscores are allowed. + * + * @param string $str + * @return boolean + */ + public static function standard_text($str) + { + return (bool) preg_match('/^[-\pL\pN\pZ_.]++$/uD', (string) $str); + } + + /** + * 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. + * + * @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); + } + +} // End valid
\ No newline at end of file |