diff --git a/include/class.api.php b/include/class.api.php index b3e4ea7b349805b8d0489c23dabf0b22b1af7f77..65f0a90e89abaf42ecd5fa980d0b09299b738f3a 100644 --- a/include/class.api.php +++ b/include/class.api.php @@ -337,20 +337,12 @@ class ApiXmlDataParser extends XmlDataParser { } if (isset($value['encoding'])) $value['body'] = Format::utf8encode($value['body'], $value['encoding']); - // HTML-ize text if html is enabled - if ($cfg->isHtmlThreadEnabled() - && (!isset($value['type']) - || strcasecmp($value['type'], 'text/html'))) - $value = sprintf('<pre>%s</pre>', - Format::htmlchars($value['body'])); - // Text-ify html if html is disabled - elseif (!$cfg->isHtmlThreadEnabled() - && !strcasecmp($value['type'], 'text/html')) - $value = Format::html2text(Format::safe_html( - $value['body']), 100, false); - // Noop if they content-type matches the html setting + + if (!strcasecmp($value['type'], 'text/html')) + $value = new HtmlThreadBody($value['body']); else - $value = $value['body']; + $value = new TextThreadBody($value['body']); + } else if ($key == "attachments") { if(!isset($value['file'][':text'])) $value = $value['file']; @@ -390,11 +382,12 @@ class ApiJsonDataParser extends JsonDataParser { } elseif ($key == "message") { // Allow message specified in RFC 2397 format $data = Format::parseRfc2397($value, 'utf-8'); - if (!isset($data['type']) || $data['type'] != 'text/html') - $value = sprintf('<pre>%s</pre>', - Format::htmlchars($data['data'])); + + if (isset($data['type']) && $data['type'] == 'text/html') + $value = new HtmlThreadBody($data['data']); else - $value = $data['data']; + $value = new TextThreadBody($data['data']); + } else if ($key == "attachments") { foreach ($value as &$info) { $data = reset($info); @@ -408,10 +401,8 @@ class ApiJsonDataParser extends JsonDataParser { } unset($info); } - if (is_array($value)) { - $value = $this->fixup($value); - } } + unset($value); return $current; } @@ -432,9 +423,6 @@ class ApiEmailDataParser extends EmailDataParser { $data['source'] = 'Email'; - if(!$data['message']) - $data['message'] = '--'; - if(!$data['subject']) $data['subject'] = '[No Subject]'; diff --git a/include/class.mailfetch.php b/include/class.mailfetch.php index f1ce7fdc75bf590efa9c46eaff30045e78e6795a..9c4afe9b4005e4a3844c6b536473ff9718a5eb69 100644 --- a/include/class.mailfetch.php +++ b/include/class.mailfetch.php @@ -472,10 +472,8 @@ class MailFetcher { foreach ($struct->parameters as $p) { if (strtolower($p->attribute) == 'report-type' && $p->value == 'delivery-status') { - return sprintf('<pre>%s</pre>', - Format::htmlchars( - $this->getPart($mid, 'text/plain', $this->charset, $struct, false, 1) - )); + return new TextThreadBody( $this->getPart( + $mid, 'text/plain', $this->charset, $struct, false, 1)); } } } @@ -501,27 +499,23 @@ class MailFetcher { global $cfg; if ($cfg->isHtmlThreadEnabled()) { - if ($body=$this->getPart($mid, 'text/html', $this->charset)) { - //Cleanup the html. - $body = (trim($body, " <>br/\t\n\r")) - ? Format::safe_html($body) - : '--'; - } - elseif ($body=$this->getPart($mid, 'text/plain', $this->charset)) { - $body = trim($body) - ? sprintf('<pre>%s</pre>', - Format::htmlchars($body)) - : '--'; - } - } - else { - if (!($body=$this->getPart($mid, 'text/plain', $this->charset))) { - if ($body=$this->getPart($mid, 'text/html', $this->charset)) { - $body = Format::html2text(Format::safe_html($body), 100, false); - } - } - $body = trim($body) ? $body : '--'; + if ($html=$this->getPart($mid, 'text/html', $this->charset)) + $body = new HtmlThreadBody($html); + elseif ($text=$this->getPart($mid, 'text/plain', $this->charset)) + $body = new TextThreadBody($text); } + elseif ($text=$this->getPart($mid, 'text/plain', $this->charset)) + $body = new TextThreadBody($text); + elseif ($html=$this->getPart($mid, 'text/html', $this->charset)) + $body = new TextThreadBody( + Format::html2text(Format::safe_html($html), + 100, false)); + else + $body = new TextThreadBody(''); + + if ($cfg->stripQuotedReply()) + $body->stripQuotedReply($cfg->getReplySeparator()); + return $body; } @@ -572,7 +566,7 @@ class MailFetcher { $vars['thread-type'] = 'N'; } else { - $vars['message'] = Format::stripEmptyLines($this->getBody($mid)); + $vars['message'] = $this->getBody($mid); } diff --git a/include/class.mailparse.php b/include/class.mailparse.php index eacd62d2a24b44db7a6f66c38411ca9bf5a885f5..eca76a0b753ebba8167e71b78f5f6411d65e0e7c 100644 --- a/include/class.mailparse.php +++ b/include/class.mailparse.php @@ -229,27 +229,23 @@ class Mail_Parse { global $cfg; if ($cfg->isHtmlThreadEnabled()) { - if ($body=$this->getPart($this->struct,'text/html')) { - // Cleanup the html -- Balance html tags & neutralize unsafe tags. - $body = (trim($body, " <>br/\t\n\r")) - ? Format::safe_html($body) - : '--'; - } - elseif ($body=$this->getPart($this->struct,'text/plain')) { - $body = trim($body) - ? sprintf('<pre>%s</pre>', - Format::htmlchars($body)) - : '--'; - } - } - else { - if (!($body=$this->getPart($this->struct,'text/plain'))) { - if ($body=$this->getPart($this->struct,'text/html')) { - $body = Format::html2text(Format::safe_html($body), 100, false); - } - } - $body = trim($body) ? $body : '--'; + if ($html=$this->getPart($this->struct,'text/html')) + $body = new HtmlThreadBody($html); + elseif ($text=$this->getPart($this->struct,'text/plain')) + $body = new TextThreadBody($text); } + elseif ($text=$this->getPart($this->struct,'text/plain')) + $body = new TextThreadBody($text); + elseif ($html=$this->getPart($this->struct,'text/html')) + $body = new TextThreadBody( + Format::html2text(Format::safe_html($html), + 100, false)); + else + $body = new TextThreadBody(''); + + if ($cfg->stripQuotedReply()) + $body->stripQuotedReply($cfg->getReplySeparator()); + return $body; } @@ -481,7 +477,7 @@ class EmailDataParser { } else { // Typical email - $data['message'] = Format::stripEmptyLines($parser->getBody()); + $data['message'] = $parser->getBody(); $data['in-reply-to'] = $parser->struct->headers['in-reply-to']; $data['references'] = $parser->struct->headers['references']; } diff --git a/include/class.thread.php b/include/class.thread.php index b0748f69e7522f3ac4113591efab38e0bbea56a9..fec7c52c40d9284a18ef3f4a6fca9191db3a0713 100644 --- a/include/class.thread.php +++ b/include/class.thread.php @@ -865,23 +865,17 @@ Class ThreadEntry { if(!$vars['ticketId'] || !$vars['type'] || !in_array($vars['type'], array('M','R','N'))) return false; - //Strip quoted reply...on emailed messages - if($vars['origin'] - && !strcasecmp($vars['origin'], 'Email') - && $cfg->stripQuotedReply() - && ($tag=$cfg->getReplySeparator()) - && strpos($vars['body'], $tag)) - if((list($msg) = explode($tag, $vars['body'], 2)) && trim($msg)) - $vars['body'] = $msg; - - if (!$cfg->isHtmlThreadEnabled()) { - // Data in the database is assumed to be HTML, change special - // plain text XML characters - $vars['title'] = Format::htmlchars($vars['title']); - $vars['body'] = sprintf('<pre>%s</pre>', - Format::htmlchars($vars['body'])); + + if (!$vars['body'] instanceof ThreadBody) { + if ($cfg->isHtmlThreadEnabled()) + $vars['body'] = new HtmlThreadBody($vars['body']); + else + $vars['body'] = new TextThreadBody($vars['body']); } - $vars['body'] = Format::sanitize($vars['body']); + + if (!($body = Format::sanitize( + (string) $vars['body']->convertTo('html')))) + $body = '-'; //Special tag used to signify empty message as stored. $poster = $vars['poster']; if ($poster && is_object($poster)) @@ -899,7 +893,7 @@ Class ThreadEntry { if (!isset($vars['attachments']) || !$vars['attachments']) // Otherwise, body will be configured in a block below (after // inline attachments are saved and updated in the database) - $sql.=' ,body='.db_input($vars['body']); + $sql.=' ,body='.db_input($body); if(isset($vars['pid'])) $sql.=' ,pid='.db_input($vars['pid']); @@ -936,12 +930,12 @@ Class ThreadEntry { // content-id will be discarded, only the unique hash-code // will be available to retrieve the image later if ($a['cid'] && $a['key']) { - $vars['body'] = str_replace('src="cid:'.$a['cid'].'"', - 'src="cid:'.$a['key'].'"', $vars['body']); + $body = str_replace('src="cid:'.$a['cid'].'"', + 'src="cid:'.$a['key'].'"', $body); } } unset($a); - $sql = 'UPDATE '.TICKET_THREAD_TABLE.' SET body='.db_input($vars['body']) + $sql = 'UPDATE '.TICKET_THREAD_TABLE.' SET body='.db_input($body) .' WHERE `id`='.db_input($entry->getId()); if (!db_query($sql) || !db_affected_rows()) return false; @@ -954,7 +948,7 @@ Class ThreadEntry { $entry->saveEmailInfo($vars); // Inline images (attached to the draft) - $entry->saveAttachments(Draft::getAttachmentIds($vars['body'])); + $entry->saveAttachments(Draft::getAttachmentIds($body)); return $entry; } @@ -1123,4 +1117,61 @@ class Note extends ThreadEntry { )?$n:null; } } + +class ThreadBody /* extends SplString */ { + + static $types = array('text', 'html'); + + var $body; + var $type; + + function __construct($body, $type='text') { + $type = strtolower($type); + if (!in_array($type, static::$types)) + throw new Exception($type.': Unsupported ThreadBody type'); + $this->body = (string) $body; + $this->type = $type; + } + + function convertTo($type) { + if ($type === $this->type) + return $this; + + $conv = $this->type . ':' . strtolower($type); + switch ($conv) { + case 'text:html': + return new ThreadBody(sprintf('<pre>%s</pre>', + Format::htmlchars($this->body)), $type); + case 'html:text': + return new ThreadBody(Format::html2text((string) $this), $type); + } + } + + function stripQuotedReply($tag) { + + //Strip quoted reply...on emailed messages + if (!$tag || strpos($this->body, $tag) === false) + return; + + if ((list($msg) = explode($tag, $this->body, 2)) && trim($msg)) + $this->body = $msg; + + } + + function __toString() { + return $this->body; + } +} + +class TextThreadBody extends ThreadBody { + function __construct($body) { + parent::__construct(Format::stripEmptyLines($body), 'text'); + } +} +class HtmlThreadBody extends ThreadBody { + function __construct($body) { + $body = trim($body, " <>br/\t\n\r") ? Format::safe_html($body) : ''; + parent::__construct($body, 'html'); + } +} ?> diff --git a/include/class.ticket.php b/include/class.ticket.php index 7bfcdc7a662c7227878da332c9c6659e1122403a..de0befdff87b7ed5de59a8f27fc2e790fbde0e61 100644 --- a/include/class.ticket.php +++ b/include/class.ticket.php @@ -1752,11 +1752,19 @@ class Ticket { function logNote($title, $note, $poster='SYSTEM', $alert=true) { $errors = array(); + //Unless specified otherwise, assume HTML + if ($note && is_string($note)) + $note = new HtmlThreadBody($note); + return $this->postNote( - array('title' => $title, 'note' => $note), + array( + 'title' => $title, + 'note' => $note, + ), $errors, $poster, - $alert); + $alert + ); } function postNote($vars, &$errors, $poster, $alert=true) { @@ -2164,7 +2172,7 @@ class Ticket { $id=0; $fields=array(); - $fields['message'] = array('type'=>'text', 'required'=>1, 'error'=>'Message required'); + $fields['message'] = array('type'=>'*', 'required'=>1, 'error'=>'Message required'); switch (strtolower($origin)) { case 'web': $fields['topicId'] = array('type'=>'int', 'required'=>1, 'error'=>'Select help topic'); diff --git a/include/class.validator.php b/include/class.validator.php index f01bcb38ca85f799c9a7d8a187624907228d8941..c41058e057f51731a994387517d0835de40f137d 100644 --- a/include/class.validator.php +++ b/include/class.validator.php @@ -61,6 +61,10 @@ class Validator { $this->errors[$k]=$field['error']; continue; } + + //We don't care about the type. + if ($field['type'] == '*') continue; + //Do the actual validation based on the type. switch(strtolower($field['type'])): case 'integer': @@ -73,7 +77,7 @@ class Validator { case 'double': if(!is_numeric($this->input[$k])) $this->errors[$k]=$field['error']; - break; + break; case 'text': case 'string': if(!is_string($this->input[$k])) @@ -84,9 +88,9 @@ class Validator { $this->errors[$k]=$field['error']; break; case 'radio': - if(!isset($this->input[$k])) - $this->errors[$k]=$field['error']; - break; + if(!isset($this->input[$k])) + $this->errors[$k]=$field['error']; + break; case 'date': //TODO...make sure it is really in GNU date format.. if(strtotime($this->input[$k])===false) $this->errors[$k]=$field['error'];