|
- <?php
- /**
- * File: Helpers
- * Various functions used throughout Chyrp's code.
- */
-
- # Integer: $time_start
- # Times Chyrp.
- $time_start = 0;
-
- # Array: $l10n
- # Stores loaded gettext domains.
- $l10n = array();
-
- /**
- * Function: session
- * Begins Chyrp's custom session storage whatnots.
- */
- function session() {
- session_set_save_handler(array("Session", "open"),
- array("Session", "close"),
- array("Session", "read"),
- array("Session", "write"),
- array("Session", "destroy"),
- array("Session", "gc"));
- $domain = (substr_count($_SERVER['HTTP_HOST'], ".")) ? preg_replace("/^www\./", ".", $_SERVER['HTTP_HOST']) : "" ;
- session_set_cookie_params(60 * 60 * 24 * 30, "/", $domain);
- session_name("ChyrpSession");
- register_shutdown_function("session_write_close");
- session_start();
- }
-
- /**
- * Function: error
- * Shows an error message.
- *
- * Parameters:
- * $title - The title for the error dialog.
- * $body - The message for the error dialog.
- * $backtrace - The trace of the error.
- */
- function error($title, $body, $backtrace = array()) {
- if (defined('MAIN_DIR') and !empty($backtrace))
- foreach ($backtrace as $index => &$trace)
- if (!isset($trace["file"]) or !isset($trace["line"]))
- unset($backtrace[$index]);
- else
- $trace["file"] = str_replace(MAIN_DIR."/", "", $trace["file"]);
- # $trace["file"] = isset($trace["file"]) ?
- # :
- # (isset($trace["function"]) ?
- # (isset($trace["class"]) ?
- # $trace["class"].$trace["type"] :
- # "").$trace["function"] :
- # "[internal]");
-
- # Clear all output sent before this error.
- if (($buffer = ob_get_contents()) !== false) {
- ob_end_clean();
-
- # Since the header might already be set to gzip, start output buffering again.
- if (extension_loaded("zlib") and !ini_get("zlib.output_compression") and
- isset($_SERVER['HTTP_ACCEPT_ENCODING']) and
- substr_count($_SERVER['HTTP_ACCEPT_ENCODING'], "gzip") and
- USE_ZLIB) {
- ob_start("ob_gzhandler");
- header("Content-Encoding: gzip");
- } else
- ob_start();
- } elseif (!UPGRADING) {
- # If output buffering is not started, assume this
- # is sent from the Session class or somewhere deep.
- error_log($title.": ".$body);
-
- foreach ($backtrace as $index => $trace)
- error_log(" ".($index + 1).": "._f("%s on line %d", array($trace["file"], $trace["line"])));
-
- exit;
- }
-
- if (TESTER)
- exit("ERROR: ".$body);
-
- if ($title == __("Access Denied"))
- $_SESSION['redirect_to'] = self_url();
-
- # Display the error.
- if (defined('THEME_DIR') and class_exists("Theme") and Theme::current()->file_exists("pages/error"))
- MainController::current()->display("pages/error",
- array("title" => $title,
- "body" => $body,
- "backtrace" => $backtrace));
- else
- require INCLUDES_DIR."/error.php";
-
- if ($buffer !== false)
- ob_end_flush();
-
- exit;
- }
-
- /**
- * Function: show_403
- * Shows an error message with a 403 HTTP header.
- *
- * Parameters:
- * $title - The title for the error dialog.
- * $body - The message for the error dialog.
- */
- function show_403($title, $body) {
- header("Status: 403");
- error($title, $body);
- }
-
- /**
- * Function: logged_in
- * Returns whether or not they are logged in by returning the <Visitor.$id> (which defaults to 0).
- */
- function logged_in() {
- return (class_exists("Visitor") and isset(Visitor::current()->id) and Visitor::current()->id != 0);
- }
-
- /**
- * Function: load_translator
- * Loads a .mo file for gettext translation.
- *
- * Parameters:
- * $domain - The name for this translation domain.
- * $mofile - The .mo file to read from.
- */
- function load_translator($domain, $mofile) {
- global $l10n;
-
- if (isset($l10n[$domain]))
- return;
-
- if (is_readable($mofile))
- $input = new CachedFileReader($mofile);
- else
- return;
-
- $l10n[$domain] = new gettext_reader($input);
- }
-
- /**
- * Function: __
- * Returns a translated string.
- *
- * Parameters:
- * $text - The string to translate.
- * $domain - The translation domain to read from.
- */
- function __($text, $domain = "chyrp") {
- global $l10n;
- return (isset($l10n[$domain])) ? $l10n[$domain]->translate($text) : $text ;
- }
-
- /**
- * Function: _p
- * Returns a plural (or not) form of a translated string.
- *
- * Parameters:
- * $single - Singular string.
- * $plural - Pluralized string.
- * $number - The number to judge by.
- * $domain - The translation domain to read from.
- */
- function _p($single, $plural, $number, $domain = "chyrp") {
- global $l10n;
- return isset($l10n[$domain]) ?
- $l10n[$domain]->ngettext($single, $plural, $number) :
- (($number != 1) ? $plural : $single) ;
- }
-
- /**
- * Function: _f
- * Returns a formatted translated string.
- *
- * Parameters:
- * $string - String to translate and format.
- * $args - One arg or an array of arguments to format with.
- * $domain - The translation domain to read from.
- */
- function _f($string, $args = array(), $domain = "chyrp") {
- $args = (array) $args;
- array_unshift($args, __($string, $domain));
- return call_user_func_array("sprintf", $args);
- }
-
- /**
- * Function: redirect
- * Redirects to the given URL and exits immediately.
- *
- * Parameters:
- * $url - The URL to redirect to. If it begins with @/@ it will be relative to the @Config.chyrp_url@.
- * $use_chyrp_url - Use the @Config.chyrp_url@ instead of @Config.url@ for $urls beginning with @/@?
- */
- function redirect($url, $use_chyrp_url = false) {
- # Handle URIs without domain
- if ($url[0] == "/")
- $url = (ADMIN or $use_chyrp_url) ?
- Config::current()->chyrp_url.$url :
- Config::current()->url.$url ;
- elseif (file_exists(INCLUDES_DIR."/config.yaml.php") and class_exists("Route") and !substr_count($url, "://"))
- $url = url($url);
-
- header("Location: ".html_entity_decode($url));
- exit;
- }
-
- /**
- * Function: url
- * Mask for Route->url().
- */
- function url($url, $controller = null) {
- return Route::current()->url($url, $controller);
- }
-
- /**
- * Function: pluralize
- * Returns a pluralized string. This is a port of Rails's pluralizer.
- *
- * Parameters:
- * $string - The string to pluralize.
- * $number - If passed, and this number is 1, it will not pluralize.
- */
- function pluralize($string, $number = null) {
- $uncountable = array("moose", "sheep", "fish", "series", "species",
- "rice", "money", "information", "equipment", "piss");
-
- if (in_array($string, $uncountable) or $number == 1)
- return $string;
-
- $replacements = array("/person/i" => "people",
- "/man/i" => "men",
- "/child/i" => "children",
- "/cow/i" => "kine",
- "/goose/i" => "geese",
- "/(penis)$/i" => "\\1es", # Take that, Rails!
- "/(ax|test)is$/i" => "\\1es",
- "/(octop|vir)us$/i" => "\\1ii",
- "/(cact)us$/i" => "\\1i",
- "/(alias|status)$/i" => "\\1es",
- "/(bu)s$/i" => "\\1ses",
- "/(buffal|tomat)o$/i" => "\\1oes",
- "/([ti])um$/i" => "\\1a",
- "/sis$/i" => "ses",
- "/(hive)$/i" => "\\1s",
- "/([^aeiouy]|qu)y$/i" => "\\1ies",
- "/^(ox)$/i" => "\\1en",
- "/(matr|vert|ind)(?:ix|ex)$/i" => "\\1ices",
- "/(x|ch|ss|sh)$/i" => "\\1es",
- "/([m|l])ouse$/i" => "\\1ice",
- "/(quiz)$/i" => "\\1zes");
-
- $replaced = preg_replace(array_keys($replacements), array_values($replacements), $string, 1);
-
- if ($replaced == $string)
- return $string."s";
- else
- return $replaced;
- }
-
- /**
- * Function: depluralize
- * Returns a depluralized string. This is the inverse of <pluralize>.
- *
- * Parameters:
- * $string - The string to depluralize.
- * $number - If passed, and this number is not 1, it will not depluralize.
- */
- function depluralize($string, $number = null) {
- if (isset($number) and $number != 1)
- return $string;
-
- $replacements = array("/people/i" => "person",
- "/^men/i" => "man",
- "/children/i" => "child",
- "/kine/i" => "cow",
- "/geese/i" => "goose",
- "/(penis)es$/i" => "\\1",
- "/(ax|test)es$/i" => "\\1is",
- "/(octopi|viri|cact)i$/i" => "\\1us",
- "/(alias|status)es$/i" => "\\1",
- "/(bu)ses$/i" => "\\1s",
- "/(buffal|tomat)oes$/i" => "\\1o",
- "/([ti])a$/i" => "\\1um",
- "/ses$/i" => "sis",
- "/(hive)s$/i" => "\\1",
- "/([^aeiouy]|qu)ies$/i" => "\\1y",
- "/^(ox)en$/i" => "\\1",
- "/(vert|ind)ices$/i" => "\\1ex",
- "/(matr)ices$/i" => "\\1ix",
- "/(x|ch|ss|sh)es$/i" => "\\1",
- "/([ml])ice$/i" => "\\1ouse",
- "/(quiz)zes$/i" => "\\1");
-
- $replaced = preg_replace(array_keys($replacements), array_values($replacements), $string, 1);
-
- if ($replaced == $string and substr($string, -1) == "s")
- return substr($string, 0, -1);
- else
- return $replaced;
- }
-
- /**
- * Function: truncate
- * Truncates a string to the given length, optionally taking into account HTML tags, and/or keeping words in tact.
- *
- * Parameters:
- * $text - String to shorten.
- * $length - Length to truncate to.
- * $ending - What to place at the end, e.g. "...".
- * $exact - Break words?
- * $html - Auto-close cut-off HTML tags?
- *
- * Author:
- * CakePHP team, code style modified.
- */
- function truncate($text, $length = 100, $ending = "...", $exact = false, $html = false) {
- if (is_array($ending))
- extract($ending);
-
- if ($html) {
- if (strlen(preg_replace("/<[^>]+>/", "", $text)) <= $length)
- return $text;
-
- $totalLength = strlen($ending);
- $openTags = array();
- $truncate = "";
- preg_match_all("/(<\/?([\w+]+)[^>]*>)?([^<>]*)/", $text, $tags, PREG_SET_ORDER);
- foreach ($tags as $tag) {
- if (!preg_match('/img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param/s', $tag[2])
- and preg_match('/<[\w]+[^>]*>/s', $tag[0]))
- array_unshift($openTags, $tag[2]);
- elseif (preg_match('/<\/([\w]+)[^>]*>/s', $tag[0], $closeTag)) {
- $pos = array_search($closeTag[1], $openTags);
- if ($pos !== false)
- array_splice($openTags, $pos, 1);
- }
-
- $truncate .= $tag[1];
-
- $contentLength = strlen(preg_replace("/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i", " ", $tag[3]));
- if ($contentLength + $totalLength > $length) {
- $left = $length - $totalLength;
- $entitiesLength = 0;
- if (preg_match_all("/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i", $tag[3], $entities, PREG_OFFSET_CAPTURE))
- foreach ($entities[0] as $entity)
- if ($entity[1] + 1 - $entitiesLength <= $left) {
- $left--;
- $entitiesLength += strlen($entity[0]);
- } else
- break;
-
- $truncate .= substr($tag[3], 0 , $left + $entitiesLength);
-
- break;
- } else {
- $truncate .= $tag[3];
- $totalLength += $contentLength;
- }
-
- if ($totalLength >= $length)
- break;
- }
- } else {
- if (strlen($text) <= $length)
- return $text;
- else
- $truncate = substr($text, 0, $length - strlen($ending));
- }
-
- if (!$exact) {
- $spacepos = strrpos($truncate, " ");
-
- if (isset($spacepos)) {
- if ($html) {
- $bits = substr($truncate, $spacepos);
- preg_match_all('/<\/([a-z]+)>/', $bits, $droppedTags, PREG_SET_ORDER);
- if (!empty($droppedTags))
- foreach ($droppedTags as $closingTag)
- if (!in_array($closingTag[1], $openTags))
- array_unshift($openTags, $closingTag[1]);
- }
-
- $truncate = substr($truncate, 0, $spacepos);
- }
- }
-
- $truncate .= $ending;
-
- if ($html)
- foreach ($openTags as $tag)
- $truncate .= '</'.$tag.'>';
-
- return $truncate;
- }
-
- /**
- * Function: when
- * Returns date formatting for a string that isn't a regular time() value
- *
- * Parameters:
- * $formatting - The formatting for date().
- * $when - Time to base on. If it is not numeric it will be run through strtotime.
- * $strftime - Use @strftime@ instead of @date@?
- */
- function when($formatting, $when, $strftime = false) {
- $time = (is_numeric($when)) ? $when : strtotime($when) ;
-
- if ($strftime)
- return strftime($formatting, $time);
- else
- return date($formatting, $time);
- }
-
- /**
- * Function: datetime
- * Returns a standard datetime string based on either the passed timestamp or their time offset, usually for MySQL inserts.
- *
- * Parameters:
- * $when - An optional timestamp.
- */
- function datetime($when = null) {
- fallback($when, time());
-
- $time = (is_numeric($when)) ? $when : strtotime($when) ;
-
- return date("Y-m-d H:i:s", $time);
- }
-
- /**
- * Function: fix
- * Returns a HTML-sanitized version of a string.
- *
- * Parameters:
- * $string - String to fix.
- * $quotes - Encode quotes?
- */
- function fix($string, $quotes = false) {
- $quotes = ($quotes) ? ENT_QUOTES : ENT_NOQUOTES ;
- return htmlspecialchars($string, $quotes, "utf-8");
- }
-
- /**
- * Function: unfix
- * Returns the reverse of fix().
- *
- * Parameters:
- * $string - String to unfix.
- */
- function unfix($string) {
- return htmlspecialchars_decode($string, ENT_QUOTES, "utf-8");
- }
-
- /**
- * Function: lang_code
- * Returns the passed language code (e.g. en_US) to the human-readable text (e.g. English (US))
- *
- * Parameters:
- * $code - The language code to convert
- *
- * Author:
- * TextPattern devs, modified to fit with Chyrp.
- */
- function lang_code($code) {
- $langs = array("ar_DZ" => "جزائري عربي",
- "ca_ES" => "Català",
- "cs_CZ" => "Čeština",
- "da_DK" => "Dansk",
- "de_DE" => "Deutsch",
- "el_GR" => "Ελληνικά",
- "en_GB" => "English (GB)",
- "en_US" => "English (US)",
- "es_ES" => "Español",
- "et_EE" => "Eesti",
- "fi_FI" => "Suomi",
- "fr_FR" => "Français",
- "gl_GZ" => "Galego (Galiza)",
- "he_IL" => "עברית",
- "hu_HU" => "Magyar",
- "id_ID" => "Bahasa Indonesia",
- "is_IS" => "Íslenska",
- "it_IT" => "Italiano",
- "ja_JP" => "日本語",
- "lv_LV" => "Latviešu",
- "nl_NL" => "Nederlands",
- "no_NO" => "Norsk",
- "pl_PL" => "Polski",
- "pt_PT" => "Português",
- "ro_RO" => "Română",
- "ru_RU" => "Русский",
- "sk_SK" => "Slovenčina",
- "sv_SE" => "Svenska",
- "th_TH" => "ไทย",
- "uk_UA" => "Українська",
- "vi_VN" => "Tiếng Việt",
- "zh_CN" => "中文(简体)",
- "zh_TW" => "中文(繁體)",
- "bg_BG" => "Български");
- return (isset($langs[$code])) ? str_replace(array_keys($langs), array_values($langs), $code) : $code ;
- }
-
- /**
- * Function: sanitize
- * Returns a sanitized string, typically for URLs.
- *
- * Parameters:
- * $string - The string to sanitize.
- * $force_lowercase - Force the string to lowercase?
- * $anal - If set to *true*, will remove all non-alphanumeric characters.
- * $trunc - Number of characters to truncate to (default 100, 0 to disable).
- */
- function sanitize($string, $force_lowercase = true, $anal = false, $trunc = 100) {
- $strip = array("~", "`", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "=", "+", "[", "{", "]",
- "}", "\\", "|", ";", ":", "\"", "'", "‘", "’", "“", "”", "–", "—",
- "—", "–", ",", "<", ".", ">", "/", "?");
- $clean = trim(str_replace($strip, "", strip_tags($string)));
- $clean = preg_replace('/\s+/', "-", $clean);
- $clean = ($anal ? preg_replace("/[^a-zA-Z0-9]/", "", $clean) : $clean);
- $clean = ($trunc ? substr($clean, 0, $trunc) : $clean);
- return ($force_lowercase) ?
- (function_exists('mb_strtolower')) ?
- mb_strtolower($clean, 'UTF-8') :
- strtolower($clean) :
- $clean;
- }
-
- /**
- * Function: trackback_respond
- * Responds to a trackback request.
- *
- * Parameters:
- * $error - Is this an error?
- * $message - Message to return.
- */
- function trackback_respond($error = false, $message = "") {
- header("Content-Type: text/xml; charset=utf-8");
-
- if ($error) {
- echo '<?xml version="1.0" encoding="utf-8"?'.">\n";
- echo "<response>\n";
- echo "<error>1</error>\n";
- echo "<message>".$message."</message>\n";
- echo "</response>";
- exit;
- } else {
- echo '<?xml version="1.0" encoding="utf-8"?'.">\n";
- echo "<response>\n";
- echo "<error>0</error>\n";
- echo "</response>";
- }
-
- exit;
- }
-
- /**
- * Function: trackback_send
- * Sends a trackback request.
- *
- * Parameters:
- * $post - The post we're sending from.
- * $target - The URL we're sending to.
- */
- function trackback_send($post, $target) {
- if (empty($target)) return false;
-
- $target = parse_url($target);
- $title = $post->title();
- fallback($title, ucfirst($post->feather)." Post #".$post->id);
- $excerpt = strip_tags(truncate($post->excerpt(), 255));
-
- if (!empty($target["query"])) $target["query"] = "?".$target["query"];
- if (empty($target["port"])) $target["port"] = 80;
-
- $connect = fsockopen($target["host"], $target["port"]);
- if (!$connect) return false;
-
- $config = Config::current();
- $query = "url=".rawurlencode($post->url())."&".
- "title=".rawurlencode($title)."&".
- "blog_name=".rawurlencode($config->name)."&".
- "excerpt=".rawurlencode($excerpt);
-
- fwrite($connect, "POST ".$target["path"].$target["query"]." HTTP/1.1\n");
- fwrite($connect, "Host: ".$target["host"]."\n");
- fwrite($connect, "Content-type: application/x-www-form-urlencoded\n");
- fwrite($connect, "Content-length: ". strlen($query)."\n");
- fwrite($connect, "Connection: close\n\n");
- fwrite($connect, $query);
-
- fclose($connect);
-
- return true;
- }
-
- /**
- * Function: send_pingbacks
- * Sends pingback requests to the URLs in a string.
- *
- * Parameters:
- * $string - The string to crawl for pingback URLs.
- * $post - The post we're sending from.
- */
- function send_pingbacks($string, $post) {
- foreach (grab_urls($string) as $url)
- if ($ping_url = pingback_url($url)) {
- require_once INCLUDES_DIR."/lib/ixr.php";
-
- $client = new IXR_Client($ping_url);
- $client->timeout = 3;
- $client->useragent.= " -- Chyrp/".CHYRP_VERSION;
- $client->query("pingback.ping", $post->url(), $url);
- }
- }
-
- /**
- * Function: grab_urls
- * Crawls a string for links.
- *
- * Parameters:
- * $string - The string to crawl.
- *
- * Returns:
- * An array of all URLs found in the string.
- */
- function grab_urls($string) {
- $regexp = "/<a[^>]+href=[\"|']([^\"]+)[\"|']>[^<]+<\/a>/";
- preg_match_all(Trigger::current()->filter($regexp, "link_regexp"), stripslashes($string), $matches);
- $matches = $matches[1];
- return $matches;
- }
-
- /**
- * Function: pingback_url
- * Checks if a URL is pingback-capable.
- *
- * Parameters:
- * $url - The URL to check.
- *
- * Returns:
- * The pingback target, if the URL is pingback-capable.
- */
- function pingback_url($url) {
- extract(parse_url($url), EXTR_SKIP);
- if (!isset($host)) return false;
-
- $path = (!isset($path)) ? '/' : $path ;
- if (isset($query)) $path.= '?'.$query;
- $port = (isset($port)) ? $port : 80 ;
-
- # Connect
- $connect = @fsockopen($host, $port, $errno, $errstr, 2);
- if (!$connect) return false;
-
- # Send the GET headers
- fwrite($connect, "GET $path HTTP/1.1\r\n");
- fwrite($connect, "Host: $host\r\n");
- fwrite($connect, "User-Agent: Chyrp/".CHYRP_VERSION."\r\n\r\n");
-
- # Check for X-Pingback header
- $headers = "";
- while (!feof($connect)) {
- $line = fgets($connect, 512);
- if (trim($line) == "") break;
- $headers.= trim($line)."\n";
-
- if (preg_match("/X-Pingback: (.+)/i", $line, $matches))
- return trim($matches[1]);
-
- # Nothing's found so far, so grab the content-type
- # for the <link> search afterwards
- if (preg_match("/Content-Type: (.+)/i", $headers, $matches))
- $content_type = trim($matches[1]);
- }
-
- # No header found, check for <link>
- if (preg_match('/(image|audio|video|model)/i', $content_type)) return false;
- $size = 0;
- while (!feof($connect)) {
- $line = fgets($connect, 1024);
- if (preg_match("/<link rel=[\"|']pingback[\"|'] href=[\"|']([^\"]+)[\"|'] ?\/?>/i", $line, $link))
- return $link[1];
- $size += strlen($line);
- if ($size > 2048) return false;
- }
-
- fclose($connect);
-
- return false;
- }
-
- /**
- * Function: camelize
- * Converts a given string to camel-case.
- *
- * Parameters:
- * $string - The string to camelize.
- * $keep_spaces - Whether or not to convert underscores to spaces or remove them.
- *
- * Returns:
- * A CamelCased string.
- *
- * See Also:
- * <decamelize>
- */
- function camelize($string, $keep_spaces = false) {
- $lower = strtolower($string);
- $deunderscore = str_replace("_", " ", $lower);
- $dehyphen = str_replace("-", " ", $deunderscore);
- $final = ucwords($dehyphen);
-
- if (!$keep_spaces)
- $final = str_replace(" ", "", $final);
-
- return $final;
- }
-
- /**
- * Function: decamelize
- * Decamelizes a string.
- *
- * Parameters:
- * $string - The string to decamelize.
- *
- * Returns:
- * A de_camel_cased string.
- *
- * See Also:
- * <camelize>
- */
- function decamelize($string) {
- return strtolower(preg_replace("/([a-z])([A-Z])/", "\\1_\\2", $string));
- }
-
- /**
- * Function: selected
- * If $val1 == $val2, outputs or returns @ selected="selected"@
- *
- * Parameters:
- * $val1 - First value.
- * $val2 - Second value.
- * $return - Return @ selected="selected"@ instead of outputting it
- */
- function selected($val1, $val2, $return = false) {
- if ($val1 == $val2)
- if ($return)
- return ' selected="selected"';
- else
- echo ' selected="selected"';
- }
-
- /**
- * Function: checked
- * If $val == 1 (true), outputs ' checked="checked"'
- *
- * Parameters:
- * $val - Value to check.
- */
- function checked($val) {
- if ($val == 1) echo ' checked="checked"';
- }
-
- /**
- * Function: module_enabled
- * Returns whether the given module is enabled or not.
- *
- * Parameters:
- * $name - The folder name of the module.
- *
- * Returns:
- * Whether or not the requested module is enabled.
- */
- function module_enabled($name) {
- $config = Config::current();
- return in_array($name, $config->enabled_modules);
- }
-
- /**
- * Function: feather_enabled
- * Returns whether the given feather is enabled or not.
- *
- * Parameters:
- * $name - The folder name of the feather.
- *
- * Returns:
- * Whether or not the requested feather is enabled.
- */
- function feather_enabled($name) {
- $config = Config::current();
- return in_array($name, $config->enabled_feathers);
- }
-
- /**
- * Function: fallback
- * Sets a given variable if it is not set.
- *
- * The last of the arguments or the first non-empty value will be used.
- *
- * Parameters:
- * &$variable - The variable to return or set.
- *
- * Returns:
- * The value of whatever was chosen.
- */
- function fallback(&$variable) {
- if (is_bool($variable))
- return $variable;
-
- $set = (!isset($variable) or (is_string($variable) and trim($variable) === "") or $variable === array());
-
- $args = func_get_args();
- array_shift($args);
- if (count($args) > 1) {
- foreach ($args as $arg) {
- $fallback = $arg;
-
- if (isset($arg) and (!is_string($arg) or (is_string($arg) and trim($arg) !== "")) and $arg !== array())
- break;
- }
- } else
- $fallback = isset($args[0]) ? $args[0] : null ;
-
- if ($set)
- $variable = $fallback;
-
- return $set ? $fallback : $variable ;
- }
-
- /**
- * Function: oneof
- * Returns the first argument that is set and non-empty.
- *
- * It will guess where to stop based on the types of the arguments, e.g. "" has priority over array() but not 1.
- */
- function oneof() {
- $last = null;
- $args = func_get_args();
- foreach ($args as $index => $arg) {
- if (!isset($arg) or (is_string($arg) and trim($arg) === "") or $arg === array() or (is_object($arg) and empty($arg)) or ($arg === "0000-00-00 00:00:00"))
- $last = $arg;
- else
- return $arg;
-
- if ($index + 1 == count($args))
- break;
-
- $next = $args[$index + 1];
-
- $incomparable = ((is_array($arg) and !is_array($next)) or # This is a big check but it should cover most "incomparable" cases.
- (!is_array($arg) and is_array($next)) or # Using simple type comparison wouldn't work too well, for example
- (is_object($arg) and !is_object($next)) or # when "" would take priority over 1 in oneof("", 1) because they're
- (!is_object($arg) and is_object($next)) or # different types.
- (is_resource($arg) and !is_resource($next)) or
- (!is_resource($arg) and is_resource($next)));
-
- if (isset($arg) and isset($next) and $incomparable)
- return $arg;
- }
-
- return $last;
- }
-
- /**
- * Function: random
- * Returns a random string.
- *
- * Parameters:
- * $length - How long the string should be.
- * $specialchars - Use special characters in the resulting string?
- */
- function random($length, $specialchars = false) {
- $pattern = "1234567890abcdefghijklmnopqrstuvwxyz";
-
- if ($specialchars)
- $pattern.= "!@#$%^&*()?~";
-
- $len = strlen($pattern) - 1;
-
- $key = "";
- for($i = 0; $i < $length; $i++)
- $key.= $pattern[rand(0, $len)];
-
- return $key;
- }
-
- /**
- * Function: unique_filename
- * Makes a given filename unique for the uploads directory.
- *
- * Parameters:
- * $name - The name to check.
- * $path - Path to check in.
- * $num - Number suffix from which to start increasing if the filename exists.
- *
- * Returns:
- * A unique version of the given $name.
- */
- function unique_filename($name, $path = "", $num = 2) {
- $path = rtrim($path, "/");
- if (!file_exists(MAIN_DIR.Config::current()->uploads_path.$path."/".$name))
- return $name;
-
- $name = explode(".", $name);
-
- # Handle common double extensions
- foreach (array("tar.gz", "tar.bz", "tar.bz2") as $extension) {
- list($first, $second) = explode(".", $extension);
- $file_first =& $name[count($name) - 2];
- if ($file_first == $first and end($name) == $second) {
- $file_first = $first.".".$second;
- array_pop($name);
- }
- }
-
- $ext = ".".array_pop($name);
-
- $try = implode(".", $name)."-".$num.$ext;
- if (!file_exists(MAIN_DIR.Config::current()->uploads_path.$path."/".$try))
- return $try;
-
- return unique_filename(implode(".", $name).$ext, $path, $num + 1);
- }
-
- /**
- * Function: upload
- * Moves an uploaded file to the uploads directory.
- *
- * Parameters:
- * $file - The $_FILES value.
- * $extension - An array of valid extensions (case-insensitive).
- * $path - A sub-folder in the uploads directory (optional).
- * $put - Use copy() instead of move_uploaded_file()?
- *
- * Returns:
- * The resulting filename from the upload.
- */
- function upload($file, $extension = null, $path = "", $put = false) {
- $file_split = explode(".", $file['name']);
- $path = rtrim($path, "/");
- $dir = MAIN_DIR.Config::current()->uploads_path.$path;
-
- if (!file_exists($dir))
- mkdir($dir, 0777, true);
-
- $original_ext = end($file_split);
-
- # Handle common double extensions
- foreach (array("tar.gz", "tar.bz", "tar.bz2") as $ext) {
- list($first, $second) = explode(".", $ext);
- $file_first =& $file_split[count($file_split) - 2];
- if ($file_first == $first and end($file_split) == $second) {
- $file_first = $first.".".$second;
- array_pop($file_split);
- }
- }
-
- $file_ext = end($file_split);
-
- if (is_array($extension)) {
- if (!in_array(strtolower($file_ext), $extension) and !in_array(strtolower($original_ext), $extension)) {
- $list = "";
- for ($i = 0; $i < count($extension); $i++) {
- $comma = "";
- if (($i + 1) != count($extension)) $comma = ", ";
- if (($i + 2) == count($extension)) $comma = ", and ";
- $list.= "<code>*.".$extension[$i]."</code>".$comma;
- }
- error(__("Invalid Extension"), _f("Only %s files are accepted.", array($list)));
- }
- } elseif (isset($extension) and
- strtolower($file_ext) != strtolower($extension) and
- strtolower($original_ext) != strtolower($extension))
- error(__("Invalid Extension"), _f("Only %s files are supported.", array("*.".$extension)));
-
- array_pop($file_split);
- $file_clean = implode(".", $file_split);
- $file_clean = sanitize($file_clean, false).".".$file_ext;
- $filename = unique_filename($file_clean, $path);
-
- $message = __("Couldn't upload file. CHMOD <code>".$dir."</code> to 777 and try again. If this problem persists, it's probably timing out; in which case, you must contact your system administrator to increase the maximum POST and upload sizes.");
-
- if ($put) {
- if (!@copy($file['tmp_name'], $dir."/".$filename))
- error(__("Error"), $message);
- } elseif (!@move_uploaded_file($file['tmp_name'], $dir."/".$filename))
- error(__("Error"), $message);
-
- return ($path ? $path."/".$filename : $filename);
- }
-
- /**
- * Function: upload_from_url
- * Copy a file from a specified URL to their upload directory.
- *
- * Parameters:
- * $url - The URL to copy.
- * $extension - An array of valid extensions (case-insensitive).
- * $path - A sub-folder in the uploads directory (optional).
- *
- * See Also:
- * <upload>
- */
- function upload_from_url($url, $extension = null, $path = "") {
- $file = tempnam(null, "chyrp");
- file_put_contents($file, get_remote($url));
-
- $fake_file = array("name" => basename(parse_url($url, PHP_URL_PATH)),
- "tmp_name" => $file);
-
- return upload($fake_file, $extension, $path, true);
- }
-
- /**
- * Function: uploaded
- * Returns a URL to an uploaded file.
- *
- * Parameters:
- * $file - Filename relative to the uploads directory.
- */
- function uploaded($file, $url = true) {
- if (empty($file))
- return "";
-
- $config = Config::current();
- return ($url ? $config->chyrp_url.$config->uploads_path.$file : MAIN_DIR.$config->uploads_path.$file);
- }
-
- /**
- * Function: timer_start
- * Starts the timer.
- */
- function timer_start() {
- global $time_start;
- $mtime = explode(" ", microtime());
- $mtime = $mtime[1] + $mtime[0];
- $time_start = $mtime;
- }
-
- /**
- * Function: timer_stop
- * Stops the timer and returns the total time.
- *
- * Parameters:
- * $precision - Number of decimals places to round to.
- *
- * Returns:
- * A formatted number with the given $precision.
- */
- function timer_stop($precision = 3) {
- global $time_start;
- $mtime = microtime();
- $mtime = explode(" ", $mtime);
- $mtime = $mtime[1] + $mtime[0];
- $time_end = $mtime;
- $time_total = $time_end - $time_start;
- return number_format($time_total, $precision);
- }
-
- /**
- * Function: normalize
- * Attempts to normalize all newlines and whitespace into single spaces.
- *
- * Returns:
- * The normalized string.
- */
- function normalize($string) {
- $trimmed = trim($string);
- $newlines = str_replace("\n\n", " ", $trimmed);
- $newlines = str_replace("\n", "", $newlines);
- $normalized = preg_replace("/[\s\n\r\t]+/", " ", $newlines);
- return $normalized;
- }
-
- /**
- * Function: get_remote
- * Grabs the contents of a website/location.
- *
- * Parameters:
- * $url - The URL of the location to grab.
- *
- * Returns:
- * The response from the remote URL.
- */
- function get_remote($url) {
- extract(parse_url($url), EXTR_SKIP);
-
- if (ini_get("allow_url_fopen")) {
- $content = @file_get_contents($url);
- if ($http_response_header[0] != "HTTP/1.1 200 OK")
- $content = "Server returned a message: $http_response_header[0]";
- } elseif (function_exists("curl_init")) {
- $handle = curl_init();
- curl_setopt($handle, CURLOPT_URL, $url);
- curl_setopt($handle, CURLOPT_CONNECTTIMEOUT, 1);
- curl_setopt($handle, CURLOPT_RETURNTRANSFER, 1);
- curl_setopt($handle, CURLOPT_TIMEOUT, 60);
- $content = curl_exec($handle);
- $status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
- curl_close($handle);
- if ($status != 200)
- $content = "Server returned a message: $status";
- } else {
- $path = (!isset($path)) ? '/' : $path ;
- if (isset($query)) $path.= '?'.$query;
- $port = (isset($port)) ? $port : 80 ;
-
- $connect = @fsockopen($host, $port, $errno, $errstr, 2);
- if (!$connect) return false;
-
- # Send the GET headers
- fwrite($connect, "GET ".$path." HTTP/1.1\r\n");
- fwrite($connect, "Host: ".$host."\r\n");
- fwrite($connect, "User-Agent: Chyrp/".CHYRP_VERSION."\r\n\r\n");
-
- $content = "";
- while (!feof($connect)) {
- $line = fgets($connect, 128);
- if (preg_match("/\r\n/", $line)) continue;
-
- $content.= $line;
- }
-
- fclose($connect);
- }
-
- return $content;
- }
-
- /**
- * Function: self_url
- * Returns the current URL.
- */
- function self_url() {
- $split = explode("/", $_SERVER['SERVER_PROTOCOL']);
- $protocol = strtolower($split[0]);
- $default_port = ($protocol == "http") ? 80 : 443 ;
- $port = ($_SERVER['SERVER_PORT'] == $default_port) ? "" : ":".$_SERVER['SERVER_PORT'] ;
- return $protocol."://".$_SERVER['SERVER_NAME'].$port.$_SERVER['REQUEST_URI'];
- }
-
- /**
- * Function: show_404
- * Shows a 404 error message and immediately exits.
- *
- * Parameters:
- * $scope - An array of values to extract into the scope.
- */
- function show_404() {
- header("HTTP/1.1 404 Not Found");
-
- if (!defined('CHYRP_VERSION'))
- exit("404 Not Found");
-
- $theme = Theme::current();
- $main = MainController::current();
-
- Trigger::current()->call("not_found");
-
- if ($theme->file_exists("pages/404"))
- $main->display("pages/404", array(), "404");
- else
- error(__("404 Not Found"), __("The requested page could not be located."));
-
- exit;
- }
-
- /**
- * Function: set_locale
- * Set locale in a platform-independent way
- *
- * Parameters:
- * $locale - the locale name (@en_US@, @uk_UA@, @fr_FR@ etc.)
- *
- * Returns:
- * The encoding name used by locale-aware functions.
- */
- function set_locale($locale) { # originally via http://www.onphp5.com/article/22; heavily modified
- if ($locale == "en_US") return; # en_US is the default in Chyrp; their system may have
- # its own locale setting and no Chyrp translation available
- # for their locale, so let's just leave it alone.
-
- list($lang, $cty) = explode("_", $locale);
- $locales = array($locale.".UTF-8", $lang, "en_US.UTF-8", "en");
- $result = setlocale(LC_ALL, $locales);
-
- return (!strpos($result, 'UTF-8')) ? "CP".preg_replace('~\.(\d+)$~', "\\1", $result) : "UTF-8" ;
- }
-
- /**
- * Function: sanitize_input
- * Makes sure no inherently broken ideas such as magic_quotes break our application
- *
- * Parameters:
- * $data - The array to be sanitized, usually one of @$_GET@, @$_POST@, @$_COOKIE@, or @$_REQUEST@
- */
- function sanitize_input(&$data) {
- foreach ($data as &$value)
- if (is_array($value))
- sanitize_input($value);
- else
- $value = get_magic_quotes_gpc() ? stripslashes($value) : $value ;
- }
-
- /**
- * Function: match
- * Try to match a string against an array of regular expressions.
- *
- * Parameters:
- * $try - An array of regular expressions, or a single regular expression.
- * $haystack - The string to test.
- *
- * Returns:
- * Whether or not the match succeeded.
- */
- function match($try, $haystack) {
- if (is_string($try))
- return (bool) preg_match($try, $haystack);
-
- foreach ($try as $needle)
- if (preg_match($needle, $haystack))
- return true;
-
- return false;
- }
-
- /**
- * Function: cancel_module
- * Temporarily removes a module from $config->enabled_modules.
- *
- * Parameters:
- * $target - Module name to disable.
- */
- function cancel_module($target) {
- $this_disabled = array();
-
- if (isset(Modules::$instances[$target]))
- Modules::$instances[$target]->cancelled = true;
-
- $config = Config::current();
- foreach ($config->enabled_modules as $module)
- if ($module != $target)
- $this_disabled[] = $module;
-
- return $config->enabled_modules = $this_disabled;
- }
-
- /**
- * Function: time_in_timezone
- * Returns the appropriate time() for representing a timezone.
- */
- function time_in_timezone($timezone) {
- $orig = get_timezone();
- set_timezone($timezone);
- $time = date("F jS, Y, g:i A");
- set_timezone($orig);
- return strtotime($time);
- }
-
- /**
- * Function: timezones
- * Returns an array of timezones that have unique offsets. Doesn't count deprecated timezones.
- */
- function timezones() {
- $zones = array();
-
- $deprecated = array("Brazil/Acre", "Brazil/DeNoronha", "Brazil/East", "Brazil/West", "Canada/Atlantic", "Canada/Central", "Canada/East-Saskatchewan", "Canada/Eastern", "Canada/Mountain", "Canada/Newfoundland", "Canada/Pacific", "Canada/Saskatchewan", "Canada/Yukon", "CET", "Chile/Continental", "Chile/EasterIsland", "CST6CDT", "Cuba", "EET", "Egypt", "Eire", "EST", "EST5EDT", "Etc/GMT", "Etc/GMT+0", "Etc/GMT+1", "Etc/GMT+10", "Etc/GMT+11", "Etc/GMT+12", "Etc/GMT+2", "Etc/GMT+3", "Etc/GMT+4", "Etc/GMT+5", "Etc/GMT+6", "Etc/GMT+7", "Etc/GMT+8", "Etc/GMT+9", "Etc/GMT-0", "Etc/GMT-1", "Etc/GMT-10", "Etc/GMT-11", "Etc/GMT-12", "Etc/GMT-13", "Etc/GMT-14", "Etc/GMT-2", "Etc/GMT-3", "Etc/GMT-4", "Etc/GMT-5", "Etc/GMT-6", "Etc/GMT-7", "Etc/GMT-8", "Etc/GMT-9", "Etc/GMT0", "Etc/Greenwich", "Etc/UCT", "Etc/Universal", "Etc/UTC", "Etc/Zulu", "Factory", "GB", "GB-Eire", "GMT", "GMT+0", "GMT-0", "GMT0", "Greenwich", "Hongkong", "HST", "Iceland", "Iran", "Israel", "Jamaica", "Japan", "Kwajalein", "Libya", "MET", "Mexico/BajaNorte", "Mexico/BajaSur", "Mexico/General", "MST", "MST7MDT", "Navajo", "NZ", "NZ-CHAT", "Poland", "Portugal", "PRC", "PST8PDT", "ROC", "ROK", "Singapore", "Turkey", "UCT", "Universal", "US/Alaska", "US/Aleutian", "US/Arizona", "US/Central", "US/East-Indiana", "US/Eastern", "US/Hawaii", "US/Indiana-Starke", "US/Michigan", "US/Mountain", "US/Pacific", "US/Pacific-New", "US/Samoa", "UTC", "W-SU", "WET", "Zulu");
-
- foreach (timezone_identifiers_list() as $zone)
- if (!in_array($zone, $deprecated))
- $zones[] = array("name" => $zone,
- "now" => time_in_timezone($zone));
-
- function by_time($a, $b) {
- return (int) ($a["now"] > $b["now"]);
- }
-
- usort($zones, "by_time");
-
- return $zones;
- }
-
- /**
- * Function: set_timezone
- * Sets the timezone.
- *
- * Parameters:
- * $timezone - The timezone to set.
- */
- function set_timezone($timezone) {
- if (function_exists("date_default_timezone_set"))
- date_default_timezone_set($timezone);
- else
- ini_set("date.timezone", $timezone);
- }
-
- /**
- * Function: get_timezone()
- * Returns the current timezone.
- */
- function get_timezone() {
- if (function_exists("date_default_timezone_set"))
- return date_default_timezone_get();
- else
- return ini_get("date.timezone");
- }
-
- /**
- * Function: error_panicker
- * Exits and states where the error occurred.
- */
- function error_panicker($errno, $message, $file, $line) {
- if (error_reporting() === 0)
- return; # Suppressed error.
-
- exit("ERROR: ".$message." (".$file." on line ".$line.")");
- }
-
- /**
- * Function: keywords
- * Handle keyword-searching.
- *
- * Parameters:
- * $query - The query to parse.
- * $plain - WHERE syntax to search for non-keyword queries.
- * $table - If specified, the keywords will be checked against this table's columns for validity.
- *
- * Returns:
- * An array containing the "WHERE" queries and the corresponding parameters.
- */
- function keywords($query, $plain, $table = null) {
- if (!trim($query))
- return array(array(), array());
-
- $search = array();
- $matches = array();
- $where = array();
- $params = array();
-
- if ($table)
- $columns = SQL::current()->select($table)->fetch();
-
- $queries = explode(" ", $query);
- foreach ($queries as $query)
- if (!preg_match("/([a-z0-9_]+):(.+)/", $query))
- $search[] = $query;
- else
- $matches[] = $query;
-
- $times = array("year", "month", "day", "hour", "minute", "second");
-
- foreach ($matches as $match) {
- list($test, $equals,) = explode(":", $match);
-
- if ($equals[0] == '"') {
- if (substr($equals, -1) != '"')
- foreach ($search as $index => $part) {
- $equals.= " ".$part;
-
- unset($search[$index]);
-
- if (substr($part, -1) == '"')
- break;
- }
-
- $equals = ltrim(trim($equals, '"'), '"');
- }
-
- if (in_array($test, $times)) {
- if ($equals == "today")
- $where["created_at like"] = date("%Y-m-d %");
- elseif ($equals == "yesterday")
- $where["created_at like"] = date("%Y-m-d %", now("-1 day"));
- elseif ($equals == "tomorrow")
- error(__("Error"), "Unfortunately our flux capacitor is currently having issues. Try again yesterday.");
- else
- $where[strtoupper($test)."(created_at)"] = $equals;
- } elseif ($test == "author") {
- $user = new User(array("login" => $equals));
- if ($user->no_results and $equals == "me") {
- !($table == "users") ? $where["user_id"] = Visitor::current()->id : $where["id"] = Visitor::current()->id;
- } else
- !($table == "users") ? $where["user_id"] = $user->id : $where["id"] = $user->id;
- } elseif ($test == "group") {
- $group = new Group(array("name" => $equals));
- $where["group_id"] = $equals = ($group->no_results) ? 0 : $group->id;
- } else
- $where[$test] = $equals;
- }
-
- if ($table)
- foreach ($where as $col => $val)
- if (!isset($where[$col])) {
- if ($table == "posts") {
- $where["post_attributes.name"] = $col;
- $where["post_attributes.value like"] = "%".$val."%";
- }
-
- unset($where[$col]);
- }
-
- if (!empty($search)) {
- $where[] = $plain;
- $params[":query"] = "%".join(" ", $search)."%";
- }
-
- $keywords = array($where, $params);
-
- Trigger::current()->filter($keywords, "keyword_search", $query, $plain);
-
- return $keywords;
- }
-
- /**
- * Function: init_extensions
- * Initialize all Modules and Feathers.
- */
- function init_extensions() {
- $config = Config::current();
-
- # Instantiate all Modules.
- foreach ($config->enabled_modules as $index => $module) {
- if (!file_exists(MODULES_DIR."/".$module."/".$module.".php")) {
- unset($config->enabled_modules[$index]);
- continue;
- }
-
- if (file_exists(MODULES_DIR."/".$module."/locale/".$config->locale.".mo"))
- load_translator($module, MODULES_DIR."/".$module."/locale/".$config->locale.".mo");
-
- require MODULES_DIR."/".$module."/".$module.".php";
-
- $camelized = camelize($module);
- if (!class_exists($camelized))
- continue;
-
- Modules::$instances[$module] = new $camelized;
- Modules::$instances[$module]->safename = $module;
-
- foreach (YAML::load(MODULES_DIR."/".$module."/info.yaml") as $key => $val)
- Modules::$instances[$module]->$key = (is_string($val)) ? __($val, $module) : $val ;
- }
-
- # Instantiate all Feathers.
- foreach ($config->enabled_feathers as $index => $feather) {
- if (!file_exists(FEATHERS_DIR."/".$feather."/".$feather.".php")) {
- unset($config->enabled_feathers[$index]);
- continue;
- }
-
- if (file_exists(FEATHERS_DIR."/".$feather."/locale/".$config->locale.".mo"))
- load_translator($feather, FEATHERS_DIR."/".$feather."/locale/".$config->locale.".mo");
-
- require FEATHERS_DIR."/".$feather."/".$feather.".php";
-
- $camelized = camelize($feather);
- if (!class_exists($camelized))
- continue;
-
- Feathers::$instances[$feather] = new $camelized;
- Feathers::$instances[$feather]->safename = $feather;
-
- foreach (YAML::load(FEATHERS_DIR."/".$feather."/info.yaml") as $key => $val)
- Feathers::$instances[$feather]->$key = (is_string($val)) ? __($val, $feather) : $val ;
- }
-
- # Initialize all modules.
- foreach (Feathers::$instances as $feather)
- if (method_exists($feather, "__init"))
- $feather->__init();
-
- foreach (Modules::$instances as $module)
- if (method_exists($module, "__init"))
- $module->__init();
- }
-
- /**
- * Function: xml2arr
- * Recursively converts a SimpleXML object (and children) to an array.
- *
- * Parameters:
- * $parse - The SimpleXML object to convert into an array.
- */
- function xml2arr($parse) {
- if (empty($parse))
- return "";
-
- $parse = (array) $parse;
-
- foreach ($parse as &$val)
- if (get_class($val) == "SimpleXMLElement")
- $val = xml2arr($val);
-
- return $parse;
- }
-
- /**
- * Function: arr2xml
- * Recursively adds an array (or object I guess) to a SimpleXML object.
- *
- * Parameters:
- * &$object - The SimpleXML object to modify.
- * $data - The data to add to the SimpleXML object.
- */
- function arr2xml(&$object, $data) {
- foreach ($data as $key => $val) {
- if (is_int($key) and (empty($val) or (is_string($val) and trim($val) == ""))) {
- unset($data[$key]);
- continue;
- }
-
- if (is_array($val)) {
- if (in_array(0, array_keys($val))) { # Numeric-indexed things need to be added as duplicates
- foreach ($val as $dup) {
- $xml = $object->addChild($key);
- arr2xml($xml, $dup);
- }
- } else {
- $xml = $object->addChild($key);
- arr2xml($xml, $val);
- }
- } else
- $object->addChild($key, fix($val, false, false));
- }
- }
-
- /**
- * Function: relative_time
- * Returns the difference between the given timestamps or now.
- *
- * Parameters:
- * $time - Timestamp to compare to.
- * $from - Timestamp to compare from. If not specified, defaults to now.
- *
- * Returns:
- * A string formatted like "3 days ago" or "3 days from now".
- */
- function relative_time($when, $from = null) {
- fallback($from, time());
-
- $time = (is_numeric($when)) ? $when : strtotime($when) ;
-
- $difference = $from - $time;
-
- if ($difference < 0) {
- $word = "from now";
- $difference = -$difference;
- } elseif ($difference > 0)
- $word = "ago";
- else
- return "just now";
-
- $units = array("second" => 1,
- "minute" => 60,
- "hour" => 60 * 60,
- "day" => 60 * 60 * 24,
- "week" => 60 * 60 * 24 * 7,
- "month" => 60 * 60 * 24 * 30,
- "year" => 60 * 60 * 24 * 365,
- "decade" => 60 * 60 * 24 * 365 * 10,
- "century" => 60 * 60 * 24 * 365 * 100,
- "millennium" => 60 * 60 * 24 * 365 * 1000);
-
- $possible_units = array();
- foreach ($units as $name => $val)
- if (($name == "week" and $difference >= ($val * 2)) or # Only say "weeks" after two have passed.
- ($name != "week" and $difference >= $val))
- $unit = $possible_units[] = $name;
-
- $precision = (int) in_array("year", $possible_units);
- $amount = round($difference / $units[$unit], $precision);
-
- return $amount." ".pluralize($unit, $amount)." ".$word;
- }
-
- /**
- * Function: list_notate
- * Notates an array as a list of things.
- *
- * Parameters:
- * $array - An array of things to notate.
- * $quotes - Wrap quotes around strings?
- *
- * Returns:
- * A string like "foo, bar, and baz".
- */
- function list_notate($array, $quotes = false) {
- $count = 0;
- $items = array();
- foreach ($array as $item) {
- $string = (is_string($item) and $quotes) ? "“".$item."”" : $item ;
- if (count($array) == ++$count and $count !== 1)
- $items[] = __("and ").$string;
- else
- $items[] = $string;
- }
-
- return (count($array) == 2) ? implode(" ", $items) : implode(", ", $items) ;
- }
-
- /**
- * Function: email
- * Send an email. Function arguments are exactly the same as the PHP mail() function.
- *
- * This is intended so that modules can provide an email method if the server cannot use mail().
- */
- function email() {
- $function = "mail";
- Trigger::current()->filter($function, "send_mail");
- $args = func_get_args(); # Looks redundant, but it must be so in order to meet PHP's retardation requirements.
- return call_user_func_array($function, $args);
- }
-
- /**
- * Function: now
- * Alias to strtotime, for prettiness like now("+1 day").
- */
- function now($when) {
- return strtotime($when);
- }
-
- /**
- * Function: comma_sep
- * Convert a comma-seperated string into an array of the listed values.
- */
- function comma_sep($string) {
- $commas = explode(",", $string);
- $trimmed = array_map("trim", $commas);
- $cleaned = array_diff(array_unique($trimmed), array(""));
- return $cleaned;
- }
|