summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksander Machniak <alec@alec.pl>2013-04-21 17:06:46 +0200
committerAleksander Machniak <alec@alec.pl>2013-04-21 17:06:46 +0200
commit102b08c6a23a1388c95fae00d2ff850f1fa7e2c4 (patch)
tree4c33eff47ef24ad0146f1bf327cdd8c928b5d4b3
parentf55bfe096dd5f90ba93590dbc6d2832ebe1c3941 (diff)
Fix incorrect handling of leading spaces in text wrapping
-rw-r--r--CHANGELOG1
-rw-r--r--program/lib/Roundcube/rcube_mime.php148
-rw-r--r--tests/Framework/Mime.php46
3 files changed, 141 insertions, 54 deletions
diff --git a/CHANGELOG b/CHANGELOG
index cdcb714e8..511cb92e8 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
CHANGELOG Roundcube Webmail
===========================
+- Fix incorrect handling of leading spaces in text wrapping
- Fix unintentional messages list jumps on click in Internet Explorer (#1489056)
- Fix list of required configuration options (#1489055)
- Fix DB error when creating a new contact and a group is selected (#1489051)
diff --git a/program/lib/Roundcube/rcube_mime.php b/program/lib/Roundcube/rcube_mime.php
index 7cd520752..1e4fac84a 100644
--- a/program/lib/Roundcube/rcube_mime.php
+++ b/program/lib/Roundcube/rcube_mime.php
@@ -564,82 +564,122 @@ class rcube_mime
/**
- * Improved wordwrap function.
+ * Improved wordwrap function with multibyte support.
+ * The code is based on Zend_Text_MultiByte::wordWrap().
*
- * @param string $string Text to wrap
- * @param int $width Line width
- * @param string $break Line separator
- * @param bool $cut Enable to cut word
- * @param string $charset Charset of $string
+ * @param string $string Text to wrap
+ * @param int $width Line width
+ * @param string $break Line separator
+ * @param bool $cut Enable to cut word
+ * @param string $charset Charset of $string
+ * @param bool $wrap_quoted When enabled quoted lines will not be wrapped
*
* @return string Text
*/
- public static function wordwrap($string, $width=75, $break="\n", $cut=false, $charset=null)
+ public static function wordwrap($string, $width=75, $break="\n", $cut=false, $charset=null, $wrap_quoted=true)
{
- if ($charset && function_exists('mb_internal_encoding')) {
- mb_internal_encoding($charset);
+ if (!$charset) {
+ $charset = RCUBE_CHARSET;
}
- $para = preg_split('/\r?\n/', $string);
- $string = '';
-
- while (count($para)) {
- $line = array_shift($para);
- if ($line[0] == '>') {
- $string .= $line . (count($para) ? $break : '');
- continue;
+ // detect available functions
+ $strlen_func = function_exists('iconv_strlen') ? 'iconv_strlen' : 'mb_strlen';
+ $strpos_func = function_exists('iconv_strpos') ? 'iconv_strpos' : 'mb_strpos';
+ $strrpos_func = function_exists('iconv_strrpos') ? 'iconv_strrpos' : 'mb_strrpos';
+ $substr_func = function_exists('iconv_substr') ? 'iconv_substr' : 'mb_substr';
+
+ // Convert \r\n to \n, this is our line-separator
+ $string = str_replace("\r\n", "\n", $string);
+ $separator = "\n"; // must be 1 character length
+ $result = array();
+
+ while (($stringLength = $strlen_func($string, $charset)) > 0) {
+ $breakPos = $strpos_func($string, $separator, 0, $charset);
+
+ // quoted line (do not wrap)
+ if ($wrap_quoted && $string[0] == '>') {
+ if ($breakPos === $stringLength - 1 || $breakPos === false) {
+ $subString = $string;
+ $cutLength = null;
+ }
+ else {
+ $subString = $substr_func($string, 0, $breakPos, $charset);
+ $cutLength = $breakPos + 1;
+ }
}
+ // next line found and current line is shorter than the limit
+ else if ($breakPos !== false && $breakPos < $width) {
+ if ($breakPos === $stringLength - 1) {
+ $subString = $string;
+ $cutLength = null;
+ }
+ else {
+ $subString = $substr_func($string, 0, $breakPos, $charset);
+ $cutLength = $breakPos + 1;
+ }
+ }
+ else {
+ $subString = $substr_func($string, 0, $width, $charset);
- $list = explode(' ', $line);
- $len = 0;
- while (count($list)) {
- $line = array_shift($list);
- $l = mb_strlen($line);
- $space = $len ? 1 : 0;
- $newlen = $len + $l + $space;
-
- if ($newlen <= $width) {
- $string .= ($space ? ' ' : '').$line;
- $len += ($space + $l);
+ // last line
+ if ($subString === $string) {
+ $cutLength = null;
}
else {
- if ($l > $width) {
- if ($cut) {
- $start = 0;
- while ($l) {
- $str = mb_substr($line, $start, $width);
- $strlen = mb_strlen($str);
- $string .= ($len ? $break : '').$str;
- $start += $strlen;
- $l -= $strlen;
- $len = $strlen;
- }
+ $nextChar = $substr_func($string, $width, 1, $charset);
+
+ if ($nextChar === ' ' || $nextChar === $separator) {
+ $afterNextChar = $substr_func($string, $width + 1, 1, $charset);
+
+ if ($afterNextChar === false) {
+ $subString .= $nextChar;
+ }
+
+ $cutLength = $strlen_func($subString, $charset) + 1;
+ }
+ else {
+ if ($strrpos_func[0] == 'm') {
+ $spacePos = $strrpos_func($subString, ' ', 0, $charset);
}
else {
- $string .= ($len ? $break : '').$line;
- if (count($list)) {
- $string .= $break;
+ $spacePos = $strrpos_func($subString, ' ', $charset);
+ }
+
+ if ($spacePos !== false) {
+ $subString = $substr_func($subString, 0, $spacePos, $charset);
+ $cutLength = $spacePos + 1;
+ }
+ else if ($cut === false) {
+ $spacePos = $strpos_func($string, ' ', 0, $charset);
+
+ if ($spacePos !== false) {
+ $subString = $substr_func($string, 0, $spacePos, $charset);
+ $cutLength = $spacePos + 1;
+ }
+ else {
+ $subString = $string;
+ $cutLength = null;
}
- $len = 0;
}
- }
- else {
- $string .= $break.$line;
- $len = $l;
+ else {
+ $subString = $substr_func($subString, 0, $width, $charset);
+ $cutLength = $width;
+ }
}
}
}
- if (count($para)) {
- $string .= $break;
- }
- }
+ $result[] = $subString;
- if ($charset && function_exists('mb_internal_encoding')) {
- mb_internal_encoding(RCUBE_CHARSET);
+ if ($cutLength !== null) {
+ $string = $substr_func($string, $cutLength, ($stringLength - $cutLength), $charset);
+ }
+ else {
+ break;
+ }
}
- return $string;
+ return implode($break, $result);
}
diff --git a/tests/Framework/Mime.php b/tests/Framework/Mime.php
index 1f9a8c58f..f15379e5f 100644
--- a/tests/Framework/Mime.php
+++ b/tests/Framework/Mime.php
@@ -142,4 +142,50 @@ class Framework_Mime extends PHPUnit_Framework_TestCase
$this->assertEquals($unfolded, rcube_mime::unfold_flowed($flowed), "Test correct unfolding of quoted lines");
}
+
+ /**
+ * Test wordwrap()
+ */
+ function test_wordwrap()
+ {
+ $samples = array(
+ array(
+ array("aaaa aaaa\n aaaa"),
+ "aaaa aaaa\n aaaa",
+ ),
+ array(
+ array("123456789 123456789 123456789 123", 29),
+ "123456789 123456789 123456789\n123",
+ ),
+ array(
+ array("123456789 3456789 123456789", 29),
+ "123456789 3456789 123456789",
+ ),
+ array(
+ array("123456789 123456789 123456789 123", 29),
+ "123456789 123456789 123456789\n 123",
+ ),
+ array(
+ array("abc", 1, "\n", true),
+ "a\nb\nc",
+ ),
+ array(
+ array("ąść", 1, "\n", true, 'UTF-8'),
+ "ą\nś\nć",
+ ),
+ array(
+ array(">abc\n>def", 2, "\n", true),
+ ">abc\n>def",
+ ),
+ array(
+ array("abc def", 3, "-"),
+ "abc-def",
+ ),
+ );
+
+ foreach ($samples as $sample) {
+ $this->assertEquals($sample[1], call_user_func_array(array('rcube_mime', 'wordwrap'), $sample[0]), "Test text wrapping");
+ }
+ }
+
}