Index: branches/1.1dev/lib/Utilities.inc.php
===================================================================
--- branches/1.1dev/lib/Utilities.inc.php	(revision 633)
+++ branches/1.1dev/lib/Utilities.inc.php	(revision 646)
@@ -66,8 +66,8 @@
 
 /**
- * Returns text with appropriate html translations. 
+ * Returns text with appropriate html translations.
  *
  * @param  string $txt              Text to clean.
- * @param  bool   $preserve_html    If set to true, oTxt will not translage <, >, ", or ' 
+ * @param  bool   $preserve_html    If set to true, oTxt will not translage <, >, ", or '
  *                                  characters into HTML entities. This allows HTML to pass
  *                                  through unmunged.
@@ -90,11 +90,11 @@
         $search['retain_left_angle']       = '/&lt;/';
         $replace['retain_left_angle']      = '<';
-        
+
         $search['retain_right_angle']      = '/&gt;/';
         $replace['retain_right_angle']     = '>';
-        
+
         $search['retain_single_quote']     = '/&#039;/';
         $replace['retain_single_quote']    = "'";
-        
+
         $search['retain_double_quote']     = '/&quot;/';
         $replace['retain_double_quote']    = '"';
@@ -109,5 +109,5 @@
 
 /**
- * Returns text with stylistic modifications. 
+ * Returns text with stylistic modifications.
  *
  * @param  string   $txt  Text to clean.
@@ -131,9 +131,9 @@
     $search['single_quotes']    = '/(^|[^\w=])(?:\'|&#39;|&lsquo;)([^\']+?)(?:\'|&#39;|&rsquo;)([^\w]|$)/';
     $replace['single_quotes']   = '\\1&lsquo;\\2&rsquo;\\3';
-    
+
     // em--dashes  become em&mdash;dashes
     $search['em_dash']          = '/(\s*[^!<-])--([^>-]\s*)/';
     $replace['em_dash']         = '\\1&mdash;\\2';
-    
+
     // & becomes &amp;
     $search['ampersand']        = '/&((?![\w\d#]{1,10};))/';
@@ -141,4 +141,65 @@
 
     return preg_replace($search, $replace, $txt);
+}
+
+/*
+* Finds all URLs in text and hyperlinks them.
+*
+* @access   public
+* @param    string  $text   Text to search for URLs.
+* @param    bool    $strict True to only include URLs starting with a scheme (http:// ftp:// im://), or false to include URLs starting with 'www.'.
+* @param    mixed   $length Number of characters to truncate URL, or NULL to disable truncating.
+* @param    string  $delim  Delimiter to append, indicate truncation.
+* @return   string          Same input text, but URLs hyperlinked.
+* @author   Quinn Comendant <quinn@strangecode.com>
+* @version  2.1
+* @since    22 Mar 2015 23:29:04
+*/
+function hyperlinkTxt($text, $strict=false, $length=null, $delim='âŠ')
+{
+    // A list of schemes we allow at the beginning of a URL.
+    $schemes = 'mailto:|tel:|skype:|callto:|facetime:|bitcoin:|geo:|magnet:\?|sip:|sms:|xmpp:|view-source:(?:https?://)?|[\w-]{2,}://';
+
+    // Capture the full URL into the first match and only the first X characters into the second match.
+    // This will match URLs not preceded by " ' or = (URLs inside an attribute) or ` (Markdown quoted) or double-scheme (http://http://www.asdf.com)
+    // Valid URL characters: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~:/?#[]@!$&'()*+,;=
+    $regex = '@
+        \b                              # Start with a word-boundary.
+        (?<!"|\'|=|>|`|\]\(|[:/]/) # Negative look-behind to exclude URLs already in <a> tag, <tags>beween</tags>, `Markdown quoted`, [Markdown](link), and avoid broken:/ and doubled://schemes://
+        (                               # Begin match 1
+            (                           # Begin match 2
+                (?:%s)                  # URL starts with known scheme or www. if strict = false
+                [^\s/$.?#]+             # Any domain-valid characters
+                [^\s"`<>]{1,%s}         # Match 2 is limited to a maximum of LENGTH valid URL characters
+            )
+            [^\s"`<>]*                  # Match 1 continues with any further valid URL characters
+            ([^\P{Any}\sâŠ<>Â«Â»"ââ%s])    # Final character not a space or common end-of-sentence punctuation (.,:;?!, etc). Using double negation set, see http://stackoverflow.com/a/4786560/277303
+        )
+        @Suxi
+    ';
+    $regex = sprintf($regex,
+        ($strict ? $schemes : $schemes . '|www\.'), // Strict=false adds "www." to the list of allowed start-of-URL.
+        ($length ? $length : ''),
+        ($strict ? '' : '?!.,:;)\'-') // Strict=false excludes some "URL-valid" characters from the last character of URL. (Hyphen must remain last character in this class.)
+    );
+
+    // Use a callback function to decide when to append the delim.
+    // Also encode special chars with oTxt().
+    return preg_replace_callback($regex, function ($m) use ($length, $delim) {
+        $url = $m[1];
+        $truncated_url = $m[2] . $m[3];
+        $absolute_url = preg_replace('!^www\.!', 'http://www.', $url);
+        if (is_null($length) || $url == $truncated_url) {
+            // If not truncating, or URL was not truncated.
+            // Remove http schemas, and any single trailing / to make the display URL.
+            $display_url = preg_replace(['!^https?://!', '!^([^/]+)/$!'], ['', '$1'], $url);
+            return sprintf('<a href="%s">%s</a>', oTxt($absolute_url), $display_url);
+        } else {
+            // Truncated URL.
+            // Remove http schemas, and any single trailing / to make the display URL.
+            $display_url = preg_replace(['!^https?://!', '!^([^/]+)/$!'], ['', '$1'], trim($truncated_url));
+            return sprintf('<a href="%s">%s%s</a>', oTxt($absolute_url), $display_url, $delim);
+        }
+    }, $text);
 }
 
@@ -168,5 +229,5 @@
     case 'middle' :
         return preg_replace(array(sprintf('/^(.{%s}).{4,}(.{%s})$/sU', $part1, $part2), '/\s*\.{3,}\s*/sU'), array('$1' . $delim . '$2', $delim), $str);
-        break;    
+        break;
     case 'end' :
         return preg_replace(array(sprintf('/^(.{%s}).{4,}$/sU', $part1 + $part2), '/\s*\.{3,}\s*/sU'), array('$1' . $delim, $delim), $str);
@@ -174,5 +235,5 @@
     }
 }
-    
+
 /**
  * Generates a hexadecimal html color based on provided word.
@@ -264,5 +325,5 @@
     $units = array('B', 'KB', 'MB', 'GB', 'TB');
     $ii = count($units) - 1;
-  
+
     // Max unit
     $unit = array_search((string) $unit, $units);
@@ -270,5 +331,5 @@
         $unit = $ii;
     }
-  
+
     // Loop
     $i = 0;
@@ -277,5 +338,5 @@
         $i++;
     }
-  
+
     return sprintf($format, $size, $units[$i]);
 }
@@ -287,5 +348,5 @@
  * @param  mixed $var       The variable that is being set.
  * @param  mixed $default   What to set it to if $val is not currently set.
- * @return mixed            The resulting value of $var. 
+ * @return mixed            The resulting value of $var.
  */
 function setDefault(&$var, $default='')
@@ -332,8 +393,8 @@
  */
 function urlEncodeArray($data, $prefix='', $_return=true) {
-   
+
     // Data is stored in static variable.
     static $args;
-    
+
     if (is_array($data)) {
         foreach ($data as $key => $val) {
@@ -348,5 +409,5 @@
         $args[$prefix] = urlencode($data);
     }
-    
+
     if ($_return) {
         // This is not a recursive execution. All recursion is complete.
@@ -369,5 +430,5 @@
  */
 function urlEncodeArrayToString($data, $prefix='') {
-    
+
     $array_args = urlEncodeArray($data, $prefix);
     $url_args = '';
@@ -381,5 +442,5 @@
 
 /**
- * An alternative to "shuffle()" that actually works. 
+ * An alternative to "shuffle()" that actually works.
  *
  * @param  array $array1   input array
@@ -393,11 +454,11 @@
 		return array();
 	}
-	
+
 	srand((double) microtime() * 10000000);
 	$rand = array_rand($array1);
-	
+
 	$array2[] = $array1[$rand];
 	unset ($array1[$rand]);
-	
+
 	return $array2 + arrayRand($array1, $array2);
 }
@@ -428,5 +489,5 @@
  * Prints the word "checked" if a variable is set, and optionally matches
  * the desired value, otherwise prints nothing,
- * used for printing the word "checked" in a checkbox form input. 
+ * used for printing the word "checked" in a checkbox form input.
  *
  * @param  mixed $var     the variable to compare
@@ -454,6 +515,6 @@
  * prints the word "selected" if a variable is set, and optionally matches
  * the desired value, otherwise prints nothing,
- * otherwise prints nothing, used for printing the word "checked" in a 
- * select form input 
+ * otherwise prints nothing, used for printing the word "checked" in a
+ * select form input
  *
  * @param  mixed $var     the variable to compare
@@ -500,5 +561,5 @@
  *
  * @param  array $date     String date to convert.
- * @param  array $format   Date format to pass to date(). 
+ * @param  array $format   Date format to pass to date().
  *                         Default produces MySQL datetime: 0000-00-00 00:00:00.
  *
@@ -513,5 +574,5 @@
         $sql_date = date($format, strtotime($date));
     }
-    
+
     return $sql_date;
 }
@@ -528,9 +589,9 @@
 {
     static $magic_quotes_gpc;
-    
+
     if (!isset($magic_quotes_gpc)) {
         $magic_quotes_gpc = get_magic_quotes_gpc();
     }
-    
+
     if ($magic_quotes_gpc) {
         if (!is_array($var)) {
@@ -614,9 +675,9 @@
 {
     global $CFG;
-    
+
     if ('' == $val) {
         logMsg(sprintf('Adding signature to empty string.', null), LOG_NOTICE, __FILE__, __LINE__);
     }
-    
+
     if (!isset($key)) {
     	$key = $CFG->signing_key;
@@ -671,5 +732,5 @@
     /**
     * Translates text
-    * 
+    *
     * @access public
     * @param string $text the text to be translated
@@ -679,8 +740,8 @@
         return $text;
     }
-    
+
     /**
     * Translates text
-    * 
+    *
     * @access public
     * @param string $text the text to be translated
@@ -690,8 +751,8 @@
         return $text;
     }
-    
+
     /**
     * Translates text by domain
-    * 
+    *
     * @access public
     * @param string $domain the language to translate the text into
@@ -702,8 +763,8 @@
         return $text;
     }
-    
+
     /**
     * Translates text by domain and category
-    * 
+    *
     * @access public
     * @param string $domain the language to translate the text into
@@ -715,11 +776,11 @@
         return $text;
     }
-    
+
     /**
     * Binds the text domain
-    * 
+    *
     * @access public
     * @param string $domain the language to translate the text into
-    * @param string 
+    * @param string
     * @return string translated text
     */
@@ -727,8 +788,8 @@
         return $domain;
     }
-    
+
     /**
     * Sets the text domain
-    * 
+    *
     * @access public
     * @param string $domain the language to translate the text into
@@ -742,5 +803,5 @@
 /**
  * Sends empty output to the browser and flushes the php buffer so the client
- * will see data before the page is finished processing. 
+ * will see data before the page is finished processing.
  */
 function flushBuffer() {
