Add base url to output of configtest.php.
[squirrelmail.git] / functions / strings.php
index 157656a48c5a06084202c218de33ba690d5cbc7e..b9b357f8207c1297cb0ae6a2c195cc72a312737c 100644 (file)
@@ -3,13 +3,13 @@
 /**
  * strings.php
  *
- * Copyright (c) 1999-2004 The SquirrelMail Project Team
+ * Copyright (c) 1999-2005 The SquirrelMail Project Team
  * Licensed under the GNU GPL. For full terms see the file COPYING.
  *
  * This code provides various string manipulation functions that are
- * used by the rest of the Squirrelmail code.
+ * used by the rest of the SquirrelMail code.
  *
- * $Id$
+ * @version $Id$
  * @package squirrelmail
  */
 
@@ -33,6 +33,317 @@ $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
+                       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
  *
@@ -50,9 +361,9 @@ function sqWordWrap(&$line, $wrap) {
     global $languages, $squirrelmail_language;
 
     if (isset($languages[$squirrelmail_language]['XTRA_CODE']) &&
-        function_exists($languages[$squirrelmail_language]['XTRA_CODE'])) {
+        function_exists($languages[$squirrelmail_language]['XTRA_CODE'] . '_wordwrap')) {
         if (mb_detect_encoding($line) != 'ASCII') {
-            $line = $languages[$squirrelmail_language]['XTRA_CODE']('wordwrap', $line, $wrap);
+            $line = call_user_func($languages[$squirrelmail_language]['XTRA_CODE'] . '_wordwrap', $line, $wrap);
             return;
         }
     }
@@ -310,8 +621,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
@@ -330,7 +641,7 @@ function sq_mt_seed($Val) {
         $Val *= -1;
     }
 
-    if ($Val = 0) {
+    if ($Val == 0) {
         return;
     }
 
@@ -515,17 +826,17 @@ 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
+    // if javascript is on, use onclick event to handle it
     if($javascript_on) {
         sqgetGlobalVar('base_uri', $base_uri, SQ_SESSION);
         return '<a href="javascript:void(0)" onclick="comp_in_new(\''.$base_uri.$url.'\')">'. $text.'</a>';
@@ -566,14 +877,199 @@ 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);
+}
+
+/**
+ * Tests if string contains 8bit symbols.
+ *
+ * If charset is not set, function defaults to default_charset.
+ * $default_charset global must be set correctly if $charset is
+ * not used.
+ * @param string $string tested string
+ * @param string $charset charset used in a string
+ * @return bool true if 8bit symbols are detected
+ * @since 1.5.1
+ */
+function sq_is8bit($string,$charset='') {
+    global $default_charset;
+
+    if ($charset=='') $charset=$default_charset;
+
+    /**
+     * Don't use \240 in ranges. Sometimes RH 7.2 doesn't like it.
+     * Don't use \200-\237 for iso-8859-x charsets. This ranges
+     * stores control symbols in those charsets.
+     * Use preg_match instead of ereg in order to avoid problems
+     * with mbstring overloading
+     */
+    if (preg_match("/^iso-8859/i",$charset)) {
+        $needle='/\240|[\241-\377]/';
+    } else {
+        $needle='/[\200-\237]|\240|[\241-\377]/';
+    }
+    return preg_match("$needle",$string);
+}
+
+/**
+ * Replacement of mb_list_encodings function
+ *
+ * This function provides replacement for function that is available only
+ * in php 5.x. Function does not test all mbstring encodings. Only the ones
+ * that might be used in SM translations.
+ *
+ * Supported arrays are stored in session in order to reduce number of
+ * mb_internal_encoding function calls.
+ *
+ * If you want to test all mbstring encodings - fill $list_of_encodings
+ * array.
+ * @return array list of encodings supported by mbstring
+ * @since 1.5.1
+ */
+function sq_mb_list_encodings() {
+    if (! function_exists('mb_internal_encoding'))
+        return array();
+
+    // don't try to test encodings, if they are already stored in session
+    if (sqgetGlobalVar('mb_supported_encodings',$mb_supported_encodings,SQ_SESSION))
+        return $mb_supported_encodings;
+
+    // save original encoding
+    $orig_encoding=mb_internal_encoding();
+
+    $list_of_encoding=array(
+        'pass',
+        'auto',
+        'ascii',
+        'jis',
+        'utf-8',
+        'sjis',
+        'euc-jp',
+        'iso-8859-1',
+        'iso-8859-2',
+        'iso-8859-7',
+        'iso-8859-9',
+        'iso-8859-15',
+        'koi8-r',
+        'koi8-u',
+        'big5',
+        'gb2312',
+        'windows-1251',
+        'windows-1255',
+        'windows-1256',
+        'tis-620',
+        'iso-2022-jp',
+        'euc-kr',
+        'utf7-imap');
+
+    $supported_encodings=array();
+
+    foreach ($list_of_encoding as $encoding) {
+        // try setting encodings. suppress warning messages
+        if (@mb_internal_encoding($encoding))
+            $supported_encodings[]=$encoding;
+    }
+
+    // restore original encoding
+    mb_internal_encoding($orig_encoding);
+
+    // register list in session
+    sqsession_register($supported_encodings,'mb_supported_encodings');
+
+    return $supported_encodings;
 }
 
 $PHP_SELF = php_self();