Adding FIXME to comment
[squirrelmail.git] / functions / strings.php
index 157656a48c5a06084202c218de33ba690d5cbc7e..242a29b967e45b34d8f390a63f15f7096b6e197b 100644 (file)
@@ -9,7 +9,7 @@
  * This code provides various string manipulation functions that are
  * used by the rest of the Squirrelmail code.
  *
- * $Id$
+ * @version $Id$
  * @package squirrelmail
  */
 
@@ -33,6 +33,318 @@ $SQM_INTERNAL_VERSION = array(1,5,1);
  */
 require_once(SM_PATH . 'functions/global.php');
 
+/**
+ * Appends citation markers to the string.
+ * Also appends a trailing space.
+ *
+ * @author Justus Pendleton
+ *
+ * @param string str The string to append to
+ * @param int citeLevel the number of markers to append
+ * @return null
+ */
+function sqMakeCite (&$str, $citeLevel) {
+    for ($i = 0; $i < $citeLevel; $i++) {
+        $str .= '>';
+    }
+    if ($citeLevel != 0) {
+        $str .= ' ';
+    }
+}
+
+/**
+ * Create a newline in the string, adding citation
+ * markers to the newline as necessary.
+ *
+ * @author Justus Pendleton
+ *
+ * @param string str the string to make a newline in
+ * @param int citeLevel the citation level the newline is at
+ * @param int column starting column of the newline
+ * @return null
+ */
+function sqMakeNewLine (&$str, $citeLevel, &$column) {
+    $str .= "\n";
+    $column = 0;
+    if ($citeLevel > 0) {
+        sqMakeCite ($str, $citeLevel);
+        $column = $citeLevel + 1;
+    } else {
+        $column = 0;
+    }
+}
+
+/**
+ * Checks for spaces in strings - only used if PHP doesn't have native ctype support
+ *
+ * @author Tomas Kuliavas
+ *
+ * You might be able to rewrite the function by adding short evaluation form.
+ *
+ * possible problems:
+ *  - iso-2022-xx charsets  - hex 20 might be part of other symbol. I might
+ * be wrong. 0x20 is not used in iso-2022-jp. I haven't checked iso-2022-kr
+ * and iso-2022-cn mappings.
+ *
+ *  - no-break space (&nbsp;) - it is 8bit symbol, that depends on charset.
+ * there are at least three different charset groups that have nbsp in
+ * different places.
+ *
+ * I don't see any charset/nbsp options in php ctype either.
+ *
+ * @param string $string tested string
+ * @return bool true when only whitespace symbols are present in test string
+ */
+function sm_ctype_space($string) {
+  if ( preg_match('/^[\x09-\x0D]|^\x20/', $string) || $string=='') {
+    return true;
+  } else {
+    return false;
+  }
+}
+
+/**
+ * Wraps text at $wrap characters.  While sqWordWrap takes
+ * a single line of text and wraps it, this function works
+ * on the entire corpus at once, this allows it to be a little
+ * bit smarter and when and how to wrap.
+ *
+ * @author Justus Pendleton
+ *
+ * @param string body the entire body of text
+ * @param int wrap the maximum line length
+ * @return string the wrapped text
+ */
+function &sqBodyWrap (&$body, $wrap) {
+    //check for ctype support, and fake it if it doesn't exist
+    if (!function_exists('ctype_space')) {
+        function ctype_space ($string) {
+            return sm_ctype_space($string);
+        }
+    }
+
+    // the newly wrapped text
+    $outString = '';
+    // current column since the last newline in the outstring
+    $outStringCol = 0;
+    $length = strlen($body);
+    // where we are in the original string
+    $pos = 0;
+    // the number of >>> citation markers we are currently at
+    $citeLevel = 0;
+
+    // the main loop, whenever we start a newline of input text
+    // we start from here
+    while ($pos < $length) {
+       // we're at the beginning of a line, get the new cite level
+       $newCiteLevel = 0;
+
+       while (($pos < $length) && ($body{$pos} == '>')) {
+           $newCiteLevel++;
+           $pos++;
+
+           // skip over any spaces interleaved among the cite markers
+           while (($pos < $length) && ($body{$pos} == ' ')) {
+
+               $pos++;
+
+           }
+           if ($pos >= $length) {
+               break;
+           }
+       }
+
+       // special case: if this is a blank line then maintain it
+       // (i.e. try to preserve original paragraph breaks)
+       // unless they occur at the very beginning of the text
+       if (($body{$pos} == "\n" ) && (strlen($outString) != 0)) {
+           $outStringLast = $outString{strlen($outString) - 1};
+           if ($outStringLast != "\n") {
+               $outString .= "\n";
+           }
+           sqMakeCite ($outString, $newCiteLevel);
+           $outString .= "\n";
+           $pos++;
+           $outStringCol = 0;
+           continue;
+       }
+
+       // if the cite level has changed, then start a new line
+       // with the new cite level.
+       if (($citeLevel != $newCiteLevel) && ($pos > ($newCiteLevel + 1)) && ($outStringCol != 0)) {
+           sqMakeNewLine ($outString, 0, $outStringCol);
+       }
+
+       $citeLevel = $newCiteLevel;
+
+       // prepend the quote level if necessary
+       if ($outStringCol == 0) {
+           sqMakeCite ($outString, $citeLevel);
+           // if we added a citation then move the column
+           // out by citelevel + 1 (the cite markers + the space)
+           $outStringCol = $citeLevel + ($citeLevel ? 1 : 0);
+       } else if ($outStringCol > $citeLevel) {
+           // not a cite and we're not at the beginning of a line
+           // in the output.  add a space to separate the new text
+           // from previous text.
+           $outString .= ' ';
+           $outStringCol++;
+       }
+
+       // find the next newline -- we don't want to go further than that
+       $nextNewline = strpos ($body, "\n", $pos);
+       if ($nextNewline === FALSE) {
+           $nextNewline = $length;
+       }
+
+       // Don't wrap unquoted lines at all.  For now the textarea
+       // will work fine for this.  Maybe revisit this later though
+       // (for completeness more than anything else, I think)
+       if ($citeLevel == 0) {
+           $outString .= substr ($body, $pos, ($nextNewline - $pos));
+           $outStringCol = $nextNewline - $pos;
+           if ($nextNewline != $length) {
+               sqMakeNewLine ($outString, 0, $outStringCol);
+           }
+           $pos = $nextNewline + 1;
+           continue;
+       }
+       /**
+        * Set this to false to stop appending short strings to previous lines
+        */
+       $smartwrap = true;
+       // inner loop, (obviously) handles wrapping up to
+       // the next newline
+       while ($pos < $nextNewline) {
+           // skip over initial spaces
+           while (($pos < $nextNewline) && (ctype_space ($body{$pos}))) {
+               $pos++;
+           }
+           // if this is a short line then just append it and continue outer loop
+           if (($outStringCol + $nextNewline - $pos) <= ($wrap - $citeLevel - 1) ) {
+               // if this is the final line in the input string then include
+               // any trailing newlines
+               //      echo substr($body,$pos,$wrap). "<br />";
+               if (($nextNewline + 1 == $length) && ($body{$nextNewline} == "\n")) {
+                   $nextNewline++;
+               }
+
+               // trim trailing spaces
+               $lastRealChar = $nextNewline;
+               while (($lastRealChar > $pos && $lastRealChar < $length) && (ctype_space ($body{$lastRealChar}))) {
+                   $lastRealChar--;
+               }
+               // decide if appending the short string is what we want
+               if (($nextNewline < $length && $body{$nextNewline} == "\n") &&
+                     isset($lastRealChar)) {
+                   $mypos = $pos;
+                   //check the first word:
+                   while (($mypos < $length) && ($body{$mypos} == '>')) {
+                       $mypos++;
+                       // skip over any spaces interleaved among the cite markers
+                       $oldpos = $mypos;
+                       while (($mypos < $length) && ($body{$mypos} == ' ')) {
+                           $mypos++;
+                       }
+                   }
+/*
+                     $ldnspacecnt = 0;
+                     if ($mypos == $nextNewline+1) {
+                        while (($mypos < $length) && ($body{$mypos} == ' ')) {
+                         $ldnspacecnt++;
+                        }
+                     }
+*/
+
+                   $firstword = substr($body,$mypos,strpos($body,' ',$mypos) - $mypos);
+                   //if ($dowrap || $ldnspacecnt > 1 || ($firstword && (
+                   if (!$smartwrap || $firstword && (
+                                        $firstword{0} == '-' ||
+                                        $firstword{0} == '+' ||
+                                        $firstword{0} == '*' ||
+                                        $firstword{0} == strtoupper($firstword{0}) ||
+                                        strpos($firstword,':'))) {
+                        $outString .= substr($body,$pos,($lastRealChar - $pos+1));
+                        $outStringCol += ($lastRealChar - $pos);
+                        sqMakeNewLine($outString,$citeLevel,$outStringCol);
+                        $nextNewline++;
+                        $pos = $nextNewline;
+                        $outStringCol--;
+                        continue;
+                   }
+
+               }
+
+               $outString .= substr ($body, $pos, ($lastRealChar - $pos + 1));
+               $outStringCol += ($lastRealChar - $pos);
+               $pos = $nextNewline + 1;
+               continue;
+           }
+
+           $eol = $pos + $wrap - $citeLevel - $outStringCol;
+           // eol is the tentative end of line.
+           // look backwards for there for a whitespace to break at.
+           // if it's already less than our current position then
+           // our current line is already too long, break immediately
+           // and restart outer loop
+           if ($eol <= $pos) {
+               sqMakeNewLine ($outString, $citeLevel, $outStringCol);
+               continue;
+           }
+
+           // start looking backwards for whitespace to break at.
+           $breakPoint = $eol;
+           while (($breakPoint > $pos) && (! ctype_space ($body{$breakPoint}))) {
+               $breakPoint--;
+           }
+
+           // if we didn't find a breakpoint by looking backward then we
+           // need to figure out what to do about that
+           if ($breakPoint == $pos) {
+               // if we are not at the beginning then end this line
+               // and start a new loop
+               if ($outStringCol > ($citeLevel + 1)) {
+                   sqMakeNewLine ($outString, $citeLevel, $outStringCol);
+                   continue;
+               } else {
+                   // just hard break here.  most likely we are breaking
+                   // a really long URL.  could also try searching
+                   // forward for a break point, which is what Mozilla
+                   // does.  don't bother for now.
+                   $breakPoint = $eol;
+               }
+           }
+
+           // special case: maybe we should have wrapped last
+           // time.  if the first breakpoint here makes the
+           // current line too long and there is already text on
+           // the current line, break and loop again if at
+           // beginning of current line, don't force break
+           $SLOP = 6;
+           if ((($outStringCol + ($breakPoint - $pos)) > ($wrap + $SLOP)) && ($outStringCol > ($citeLevel + 1))) {
+               sqMakeNewLine ($outString, $citeLevel, $outStringCol);
+               continue;
+           }
+
+           // skip newlines or whitespace at the beginning of the string
+           $substring = substr ($body, $pos, ($breakPoint - $pos));
+           $substring = rtrim ($substring); // do rtrim and ctype_space have the same ideas about whitespace?
+           $outString .= $substring;
+           $outStringCol += strlen ($substring);
+           // advance past the whitespace which caused the wrap
+           $pos = $breakPoint;
+           while (($pos < $length) && (ctype_space ($body{$pos}))) {
+               $pos++;
+           }
+           if ($pos < $length) {
+               sqMakeNewLine ($outString, $citeLevel, $outStringCol);
+           }
+       }
+    }
+
+    return $outString;
+}
+
 /**
  * Wraps text at $wrap characters
  *
@@ -310,8 +622,8 @@ function OneTimePadDecrypt ($string, $epad) {
 /**
  * Randomizes the mt_rand() function.
  *
- * Toss this in strings or integers and it will seed the generator 
- * appropriately. With strings, it is better to get them long. 
+ * Toss this in strings or integers and it will seed the generator
+ * appropriately. With strings, it is better to get them long.
  * Use md5() to lengthen smaller strings.
  *
  * @param mixed val a value to seed the random number generator
@@ -515,14 +827,14 @@ function makeComposeLink($url, $text = null, $target='')
     }
 
 
-    // if not using "compose in new window", make 
+    // if not using "compose in new window", make
     // regular link and be done with it
     if($compose_new_win != '1') {
         return makeInternalLink($url, $text, $target);
     }
 
 
-    // build the compose in new window link...  
+    // build the compose in new window link...
 
 
     // if javascript is on, use onClick event to handle it
@@ -566,14 +878,100 @@ function sm_print_r() {
  * version of fwrite which checks for failure
  */
 function sq_fwrite($fp, $string) {
-       // write to file
-       $count = @fwrite($fp,$string);
-       // the number of bytes written should be the length of the string
-       if($count != strlen($string)) {
-               return FALSE;
-       }
-
-       return $count;
+        // write to file
+        $count = @fwrite($fp,$string);
+        // the number of bytes written should be the length of the string
+        if($count != strlen($string)) {
+                return FALSE;
+        }
+
+        return $count;
+}
+
+/**
+ * sq_get_html_translation_table
+ *
+ * Returns the translation table used by sq_htmlentities()
+ *
+ * @param integer $table html translation table. Possible values (without quotes):
+ *             <ul>
+ *                <li>HTML_ENTITIES - full html entities table defined by charset</li>
+ *                <li>HTML_SPECIALCHARS - html special characters table</li>
+ *             </ul>
+ * @param integer $quote_style quote encoding style. Possible values (without quotes):
+ *              <ul>
+ *                <li>ENT_COMPAT - (default) encode double quotes</li>
+ *                <li>ENT_NOQUOTES -  don't encode double or single quotes</li>
+ *                <li>ENT_QUOTES - encode double and single quotes</li>
+ *              </ul>
+ * @param string $charset charset used for encoding. default to us-ascii, 'auto' uses $default_charset global value.
+ * @return array html translation array
+ */
+function sq_get_html_translation_table($table,$quote_style=ENT_COMPAT,$charset='us-ascii') {
+  global $default_charset;
+
+  if ($table == HTML_SPECIALCHARS) $charset='us-ascii';
+
+  // Start array with ampersand
+  $sq_html_ent_table = array( "&" => '&amp;' );
+
+  // < and >
+  $sq_html_ent_table = array_merge($sq_html_ent_table,
+                        array("<" => '&lt;',
+                              ">" => '&gt;')
+                        );
+  // double quotes
+  if ($quote_style == ENT_COMPAT)
+     $sq_html_ent_table = array_merge($sq_html_ent_table,
+                            array("\"" => '&quot;')
+                            );
+
+  // double and single quotes
+  if ($quote_style == ENT_QUOTES)
+     $sq_html_ent_table = array_merge($sq_html_ent_table,
+                            array("\"" => '&quot;',
+                              "'" => '&#39;')
+                            );
+
+  if ($charset=='auto') $charset=$default_charset;
+
+  // add entities that depend on charset
+  switch($charset){
+  case 'iso-8859-1':
+    include_once(SM_PATH . 'functions/htmlentities/iso-8859-1.php');
+    break;
+  case 'utf-8':
+    include_once(SM_PATH . 'functions/htmlentities/utf-8.php');
+    break;
+  case 'us-ascii':
+  default:
+    break;
+  }
+  // return table
+  return $sq_html_ent_table;
+}
+
+/**
+ * sq_htmlentities
+ *
+ * Convert all applicable characters to HTML entities.
+ * Minimal php requirement - v.4.0.5
+ *
+ * @param string $string string that has to be sanitized
+ * @param integer $quote_style quote encoding style. Possible values (without quotes):
+ *              <ul>
+ *                <li>ENT_COMPAT - (default) encode double quotes</li>
+ *                <li>ENT_NOQUOTES - don't encode double or single quotes</li>
+ *                <li>ENT_QUOTES - encode double and single quotes</li>
+ *              </ul>
+ * @param string $charset charset used for encoding. defaults to 'us-ascii', 'auto' uses $default_charset global value.
+ * @return string sanitized string
+ */
+function sq_htmlentities($string,$quote_style=ENT_COMPAT,$charset='us-ascii') {
+  // get translation table
+  $sq_html_ent_table=sq_get_html_translation_table(HTML_ENTITIES,$quote_style,$charset);
+  // convert characters
+  return str_replace(array_keys($sq_html_ent_table),array_values($sq_html_ent_table),$string);
 }
 
 $PHP_SELF = php_self();