Fixes vim mode line.
[banana.git] / banana / message.func.inc.php
index 04ad7cb..ee03660 100644 (file)
@@ -39,13 +39,9 @@ function banana_removeQuotes($line, &$quote_level, $strict = true)
 function banana_quote($line, $level, $mark = '>')
 {
     $lines = explode("\n", $line);
+    $quote = str_repeat($mark, $level);
     foreach ($lines as &$line) {
-        if ($level > 0 && substr($line, 0, strlen($mark)) != $mark) {
-            $line = ' ' . $line;
-        }
-        for ($i = 0 ; $i < $level ; $i++) {
-            $line = $mark . $line;
-        }
+        $line = $quote . ' ' . $line;
     }
     return implode("\n", $lines);
 }
@@ -59,41 +55,67 @@ function banana_unflowed($text)
         $line = banana_removeQuotes($line, $level);
         while (banana_isFlowed($line)) {
             $lvl = 0;
-            if (is_null($nl = array_shift($lines))) {
+            if (empty($lines)) {
                 break;
             }
+            $nl  = $lines[0];
             $nl = banana_removeQuotes($nl, $lvl);
-            $line .= $nl;
+            if ($lvl == $level) {
+                $line .= $nl;
+                array_shift($lines);
+            } else {
+                break;
+            }
         }
         $text .= banana_quote($line, $level) . "\n";
     }
     return $text;
 }
 
-function banana_wordwrap($text, $quote_level)
+function banana_wordwrap($text, $quote_level = 0)
 {
     if ($quote_level > 0) {
-        $length = Banana::$wrap - $quote_level - 1;
+        $length = Banana::$msgshow_wrap - $quote_level - 1;
         return banana_quote(wordwrap($text, $length), $quote_level);
-    
     }
-    return wordwrap($text, Banana::$wrap);
+    return wordwrap($text, Banana::$msgshow_wrap);
 }
 
 function banana_catchFormats($text)
 {
-    $formatting = Array('/' => 'em', // match / first in order not to match closing markups </...> <> </>
-                        '_' => 'u',
-                        '*' => 'strong');
-    $url = Banana::$url_regexp;
-    preg_match_all("/$url/i", $text, $urls);
-    $text = str_replace($urls[0], "&&&urls&&&", $text);
-    foreach ($formatting as $limit=>$mark) {
-        $limit = preg_quote($limit, '/');
-        $text = preg_replace("/$limit\\b(.*?)\\b$limit/s",
+    $formatting = Array('em' => array('\B\/\b', '\b\/\B'),
+                        'u' =>  array('\b_', '_\b'),
+                        'strong' => array('\B\*\b', '\b\*\B'));
+    $url = Banana::$msgshow_url;
+    preg_match_all("/$url/ui", $text, $urls);
+    $urls = $urls[0];
+    $replace = $urls;
+    rsort($replace);
+    $text = str_replace($replace, "&&&urls&&&", $text);
+    foreach ($formatting as $mark=>$limit) {
+        list($ll, $lr) = $limit;
+        $text = preg_replace('/' . $ll . '(\w+?)' . $lr . '/us',
                              "<$mark>\\1</$mark>", $text);
     }
-    return preg_replace('/&&&urls&&&/e', 'array_shift($urls[0])', $text);
+    return preg_replace('/&&&urls&&&/e', 'array_shift($urls)', $text);
+}
+
+/** Build a flowed text from plain text
+ */
+function banana_flow($text)
+{
+    $lines = explode("\n", $text);
+    $text  = '';
+    while (!is_null($line = array_shift($lines))) {
+        if ($line != '-- ') {
+            $level = 0;
+            $line  = banana_removeQuotes($line, $level);
+            $text .= rtrim(str_replace("\n", " \n", banana_wordwrap($line, $level))) . "\n";
+        } else {
+            $text .= $line . "\n";
+        }
+    }
+    return $text;
 }
 
 // {{{ URL Catcher tools
@@ -101,8 +123,8 @@ function banana_catchFormats($text)
 function banana__cutlink($link)
 {
     $link = banana_html_entity_decode($link, ENT_QUOTES);
-    if (strlen($link) > Banana::$wrap) {
-        $link = substr($link, 0, Banana::$wrap - 3) . "...";
+    if (strlen($link) > Banana::$msgshow_wrap) {
+        $link = substr($link, 0, Banana::$msgshow_wrap - 3) . "...";
     }
     return banana_htmlentities($link, ENT_QUOTES);
 }
@@ -121,7 +143,7 @@ function banana__catchMailLink($email)
     $mid = '<' . $email . '>';
     if (isset(Banana::$spool->ids[$mid])) {
         return Banana::$page->makeLink(Array('group' => Banana::$group,
-                                             'artid' => Banana::$spool->ids[$mid],
+                                             'artid' => Banana::$spool->ids[$mid]->id,
                                              'text'  => $email));
     } elseif (strpos($email, '$') !== false) {
         return $email;
@@ -133,10 +155,10 @@ function banana__catchMailLink($email)
 
 function banana_catchURLs($text)
 {
-    $url  = Banana::$url_regexp;
+    $url  = Banana::$msgshow_url;
 
     $res  = preg_replace("/&(lt|gt|quot);/", " &\\1; ", $text);
-    $res  = preg_replace("/$url/ie", "'\\1'.banana__cleanurl('\\2').'\\3'", $res);
+    $res  = preg_replace("/$url/uie", "'\\1'.banana__cleanurl('\\2').'\\3'", $res);
     $res  = preg_replace('/(["\[])?(?:mailto:|news:)?([a-z0-9.\-+_\$]+@([\-.+_]?[a-z0-9])+)(["\]])?/ie',
                          "'\\1' . banana__catchMailLink('\\2') . '\\4'",
                           $res);
@@ -185,7 +207,7 @@ function banana_plainTextToHtml($text, $strict = true)
     $text = banana_catchURLs($text);
     $text = banana_catchQuotes($text, $strict);
     $text = banana_catchSignature($text);
-    return banana_cleanHtml('<pre>' . $text . '</pre>');
+    return '<pre>' . $text . '</pre>';
 }
 
 function banana_wrap($text, $base_level = 0, $strict = true)
@@ -197,10 +219,12 @@ function banana_wrap($text, $base_level = 0, $strict = true)
     while (!is_null($line = array_shift($lines))) {
         $lvl = 0;
         $line = banana_removeQuotes($line, $lvl, $strict);
-        if($lvl != $level && !empty($buffer)) {
-            $text  .= banana_wordwrap(implode("\n", $buffer), $level + $base_level) . "\n";
+        if($lvl != $level) {
+            if (!empty($buffer)) {
+                $text  .= banana_wordwrap(implode("\n", $buffer), $level + $base_level) . "\n";
+                $buffer = array();
+            }
             $level  = $lvl;
-            $buffer = array();
         }
         $buffer[] = $line;
     }
@@ -210,23 +234,27 @@ function banana_wrap($text, $base_level = 0, $strict = true)
     return $text;
 }
 
-function banana_formatPlainText(BananaMimePart &$part, $base_level = 0)
+function banana_formatPlainText(BananaMimePart $part, $base_level = 0)
 {
     $text = $part->getText();
     if ($part->isFlowed()) {
         $text = banana_unflowed($text);
     }
-    $text = banana_wrap($text, $base_level, $part->isFlowed());
-    return banana_plainTextToHtml($text, $part->isFlowed());
+    if (function_exists('hook_formatPart') && ($ret = hook_formatPart($text, $part, $base_level))) {
+        return $ret;
+    } else {
+        $text = banana_wrap($text, $base_level, $part->isFlowed());
+        return banana_plainTextToHtml($text, $part->isFlowed());
+    }
 }
 
-function banana_quotePlainText(BananaMimePart &$part)
+function banana_quotePlainText(BananaMimePart $part)
 {
     $text = $part->getText();
     if ($part->isFlowed()) {
         $text = banana_unflowed($text);
     }
-    return banana_wrap($text, 1);
+    return banana_quote($text, 1);
 }
 
 // }}}
@@ -247,40 +275,147 @@ function banana_removeEvilAttributes($tagSource)
     $stripAttrib = 'javascript:|onclick|ondblclick|onmousedown|onmouseup|onmouseover|'.
                    'onmousemove|onmouseout|onkeypress|onkeydown|onkeyup';
     return stripslashes(preg_replace("/$stripAttrib/i", '', $tagSource));
-}   
-    
+}
+
+function banana_cleanStyles($tag, $attributes)
+{
+    static $td_style, $conv, $size_conv;
+    if (!isset($td_style)) {
+        $conv = array('style' => 'style', 'width' => 'width', 'height' => 'height', 'border' => 'border-size',
+                      'size' => 'font-size', 'align' => 'text-align', 'valign' => 'vertical-align', 'face' => 'font',
+                      'bgcolor' => 'background-color', 'color' => 'color', 'style' => 'style',
+                      'cellpadding' => 'padding', 'cellspacing' => 'border-spacing');
+        $size_conv = array(1 => 'xx-small', 2 => 'x-small', 3 => 'small', 4 => 'medium', 5 => 'large',
+                           6 => 'x-large',  7 => 'xx-large',
+                           '-2' => 'xx-small', '-1' => 'x-small', '+1' => 'medium', '+2' => 'large',
+                           '+3' => 'x-large', '+4' => 'xx-large');
+        $td_style = array();
+    }
+    if ($tag == 'table') {
+        array_unshift($td_style, '');
+    }
+    if ($tag == '/table') {
+        array_shift($td_style);
+    }
+    if ($tag{0} == '/') {
+        return '';
+    }
+    if ($tag == 'td') {
+        $style = $td_style[0];
+    } else {
+        $style = '';
+    }
+    $attributes = str_replace(array("\n", "\r"), ' ', stripslashes($attributes));
+    $attributes = str_replace(array('= "', '= \''), array('="', '=\''), $attributes);
+    foreach ($conv as $att=>$stl) {
+        $pattern = '/\b' . preg_quote($att, '/') . '=([\'"])?(.+?)(?(1)\1|(?:$| ))/i';
+        if (preg_match($pattern, $attributes, $matches)) {
+            $attributes = preg_replace($pattern, '', $attributes);
+            $val = $matches[2];
+            if ($att == 'cellspacing' && strpos($style, 'border-collapse') === false) {
+                $style = "border-collapse: separate; border-spacing: $val $val; " . $style;
+            } elseif ($att == 'cellpadding' && $tag == 'table') {
+                $td_style[0] = "$stl: {$val}px; ";
+            } elseif ($att == 'style') {
+                $val = rtrim($val, ' ;');
+                $style .= "$val; ";
+            } elseif ($att == 'size') {
+                $val = $size_conv[$val];
+                $style = "$stl: $val; " . $style;
+            } elseif (is_numeric($val)) {
+                $style = "$stl: {$val}px; " . $style;
+            } else {
+                $style = "$stl: $val; " . $style;
+            }
+        }
+    }
+    if (!empty($style)) {
+        $style = 'style="' . $style . '" ';
+    }
+    return ' ' . $style . trim($attributes);
+}
+
+function banana__filterCss($text)
+{
+    $text = preg_replace("/(,[\s\n\r]*)/s", '\1 .banana .message .body .html ', $text);
+    return '.banana .message .body .html ' . $text;
+}
+
+function banana_filterCss($css)
+{
+    preg_match_all("/(^|\n|,\s*)\s*([\#\.@\w][^;\{\}\<]*?[\{])/s", $css, $matches);
+    $css = preg_replace("/(^|\n)\s*([\#\.@\w][^;\{\}\<]*?)([\{])/se", '"\1" . banana__filterCss("\2") . "\3"', $css);
+    $css = preg_replace('/ body\b/i', '', $css);
+    if (!Banana::$msgshow_externalimages) {
+        if (preg_match('!url\([^:\)]+:(//|\\\).*?\)!i', $css)) {
+            $css = preg_replace('!url\([^:\)]+:(//|\\\).*?\)!i', 'url(invalid-image.png)', $css);
+            Banana::$msgshow_hasextimages = true;
+        }
+    }
+    return $css;
+}
+
 /**
  * @return string
  * @param string
  * @desc Strip forbidden tags and delegate tag-source check to removeEvilAttributes()
  */
-function banana_cleanHtml($source)
+function banana_cleanHtml($source, $to_xhtml = false)
 {
-    $allowedTags = '<h1><b><i><a><ul><li><pre><hr><blockquote><img><br><font><div>'
-                 . '<p><small><big><sup><sub><code><em><strong><table><tr><td><th>';
-    $source = strip_tags($source, $allowedTags);
-    $source = preg_replace('/<(.*?)>/ie', "'<'.banana_removeEvilAttributes('\\1').'>'", $source);
-        
     if (function_exists('tidy_repair_string')) {
-        $tidy_on = Array(
-            'drop-empty-paras', 'drop-proprietary-attributes',
-            'hide-comments', 'logical-emphasis', 'output-xhtml',
-            'replace-color', 'show-body-only'
-        );
-        $tidy_off = Array('join-classes', 'clean'); // 'clean' may be a good idea, but it is too aggressive
-
-        foreach($tidy_on as $opt) {
-            tidy_setopt($opt, true);
+        $tidy_config = array('drop-empty-paras' => true,
+                             'drop-proprietary-attributes' => true,
+                             'hide-comments' => true,
+                             'logical-emphasis' => true,
+                             'output-xhtml' => true,
+                             'replace-color' => true,
+                             'join-classes'  => false,
+                             'clean' => false,
+                             'show-body-only' => false,
+                             'alt-text' => '[ inserted by TIDY ]',
+                             'wrap' => 120);
+        if (function_exists('tidy_setopt')) { // Tidy 1.0
+            foreach ($tidy_config as $field=>$value) {
+                tidy_setopt($field, $value);
+            }
+            tidy_set_encoding('utf8');
+            $source = tidy_repair_string($source);
+
+        } else { // Tidy 2.0
+            $source = tidy_repair_string($source, $tidy_config, 'utf8');
         }
-        foreach($tidy_off as $opt) {
-            tidy_setopt($opt, false);
+    }
+
+    // To XHTML
+    if ($to_xhtml) {
+        // catch inline CSS
+        $css = null;
+        if (preg_match('/<head.*?>(.*?)<\/head>/is', $source, $matches)) {
+            $source = preg_replace('/<head.*?>.*?<\/head>/is', '', $source);
+            preg_match_all('/<style(?:.*?type="text\/css".*?)?>(.*?)<\/style>/is', $matches[1], $matches);
+            foreach ($matches[1] as &$match) {
+                $css .= $match;
+            }
+            $css = banana_filterCss($css);
+            Banana::$page->addCssInline($css);
         }
-        tidy_setopt('alt-text', '[ inserted by TIDY ]');
-        tidy_setopt('wrap', '120');
-        tidy_set_encoding('utf8');
-        return tidy_repair_string($source);
+
+        // clean DTD
+        $source = str_replace('<font', '<span', $source);
+        $source = preg_replace('/<u\b/', '<span style="text-decoration: underline"', $source);
+        $source = preg_replace('/<\/(font|u)>/', '</span>', $source);
+        $source = str_replace('<body', $css ? '<div class="html"' : '<div class="html default"', $source);
+        $source = str_replace('</body>', '</div>', $source);
     }
-    return $source;
+    $allowedTags = '<h1><h2><h3><b><i><a><ul><li><pre><hr><blockquote><img><br><div><span>'
+                 . '<p><small><big><sup><sub><code><em><strong><table><tr><td><th>';
+    $source = strip_tags($source, $allowedTags);
+
+    // Use inlined style instead of old html attributes
+    if ($to_xhtml) {
+        $source = preg_replace('/<(\/?\w+)(.*?)(\/?>)/muise', "'<\\1' . banana_cleanStyles('\\1', '\\2') . '\\3'", $source);
+    }
+    return preg_replace('/<(.*?)>/ie', "'<'.banana_removeEvilAttributes('\\1').'>'", $source);
 }
 
 function banana_catchHtmlSignature($res)
@@ -307,14 +442,22 @@ function banana__linkAttachment($cid)
 
 function banana_hideExternalImages($text)
 {
-    return preg_replace("/<img[^>]*?src=['\"](?!cid).*?>/i",
-                        Banana::$page->makeImg(array('img' => 'invalid')),
-                        $text);
+    if (preg_match("/<img([^>]*?)src=['\"](?!cid).*?['\"](.*?)>/i", $text)) {
+        Banana::$msgshow_hasextimages = true;
+        return preg_replace("/<img([^>]*?)src=['\"](?!cid).*?['\"](.*?)>/i",
+                            '<img\1src="invalid"\2>',
+                            $text);
+    }
+    return $text;
 }
 
 function banana_catchPartLinks($text)
 {
-    return preg_replace('/cid:([^\'" ]+)/e', "banana__linkAttachment('\\1')", $text);
+    $article = Banana::$page->makeURL(array('group' => Banana::$group, 'artid' => Banana::$artid, 'part' => Banana::$part));
+    $article = banana_htmlentities($article);
+    $text = preg_replace('/cid:([^\'" ]+)/e', "banana__linkAttachment('\\1')", $text);
+    $text = preg_replace('/href="(#.*?)"/i', 'href="' . $article . '\1"', $text);
+    return $text;
 }
 
 // {{{ HTML to Plain Text tools
@@ -346,23 +489,25 @@ function banana_htmlToPlainText($res)
     $res = trim(strip_tags($res, '<div><br><p><blockquote>'));
     $res = preg_replace("@</?(br|p|div).*?>@si", "\n", $res);
     $res = banana__convertQuotes($res);
-    return banana_html_entity_decode($res);    
+    return banana_html_entity_decode($res);
 }
 
-function banana_formatHtml(BananaMimePart &$part)
+function banana_formatHtml(BananaMimePart $part)
 {
     $text = $part->getText();
     $text = banana_catchHtmlSignature($text);
-    $text = banana_hideExternalImages($text);
+    if (!Banana::$msgshow_externalimages) {
+        $text = banana_hideExternalImages($text);
+    }
     $text = banana_catchPartLinks($text);
-    return banana_cleanHtml($text);
+    return banana_cleanHtml($text, true);
 }
 
-function banana_quoteHtml(BananaMimePart &$part)
+function banana_quoteHtml(BananaMimePart $part)
 {
     $text = $part->getText();
     $text = banana_htmlToPlainText($text);
-    return banana_wrap($text, 1);
+    return banana_quote($text, 1);
 }
 
 // }}}
@@ -401,7 +546,7 @@ function banana_richtextToHtml($source)
     return banana_cleanHtml($source);
 }
 
-function banana_formatRichText(BananaMimePart &$part)
+function banana_formatRichText(BananaMimePart $part)
 {
     $text = $part->getText();
     $text = banana_richtextToHtml($text);
@@ -409,7 +554,15 @@ function banana_formatRichText(BananaMimePart &$part)
     return banana_cleanHtml($text);
 }
 
+function banana_quoteRichtText(BananaMimePart $part)
+{
+    $text = $part->getText();
+    $text = banana_richtextToHtml($text);
+    $text = banana_htmlToPlainText($text);
+    return banana_quote($text, 1);
+}
+
 // }}}
 
-// vim:set et sw=4 sts=4 ts=4:
+// vim:set et sw=4 sts=4 ts=4 fenc=utf-8:
 ?>