diff --git a/inc/email_functions.inc.php b/inc/email_functions.inc.php index db4599c1..9a1581f0 100644 --- a/inc/email_functions.inc.php +++ b/inc/email_functions.inc.php @@ -666,11 +666,20 @@ function hesk_processMessage($msg, $ticket, $is_admin, $is_ticket, $just_message $msg = str_replace('%%SITE_URL%%', $hesk_settings['site_url'], $msg); if (isset($ticket['message'])) { + // If HTML is enabled, let's unescape everything, and call html2text. if ($isForHtml) { $htmlMessage = nl2br($ticket['message']); $msg = str_replace('%%MESSAGE_NO_ATTACHMENTS%%', $htmlMessage, $msg); return str_replace('%%MESSAGE%%', $htmlMessage, $msg); } + $message_has_html = checkForHtml($ticket); + if ($message_has_html) { + if (!function_exists('convert_html_to_text')) { + require(HESK_PATH . 'inc/html2text/html2text.php'); + } + $ticket['message'] = convert_html_to_text($ticket['message']); + $ticket['message'] = fix_newlines($ticket['message']); + } $msg = str_replace('%%MESSAGE_NO_ATTACHMENTS%%', $ticket['message'], $msg); return str_replace('%%MESSAGE%%', $ticket['message'], $msg); } else { @@ -741,6 +750,7 @@ function hesk_processMessage($msg, $ticket, $is_admin, $is_ticket, $just_message } } + // Is message tag in email template? if (strpos($msg, '%%MESSAGE%%') !== false) { // Replace message @@ -748,7 +758,16 @@ function hesk_processMessage($msg, $ticket, $is_admin, $is_ticket, $just_message $htmlMessage = nl2br($ticket['message']); $msg = str_replace('%%MESSAGE%%', $htmlMessage, $msg); } else { - $msg = str_replace('%%MESSAGE%%', $ticket['message'], $msg); + $plainTextMessage = $ticket['message']; + $message_has_html = checkForHtml($ticket); + if ($message_has_html) { + if (!function_exists('convert_html_to_text')) { + require(HESK_PATH . 'inc/html2text/html2text.php'); + } + $plainTextMessage = convert_html_to_text($plainTextMessage); + $plainTextMessage = fix_newlines($plainTextMessage); + } + $msg = str_replace('%%MESSAGE%%', $plainTextMessage, $msg); } // Add direct links to any attachments at the bottom of the email message OR add them as attachments, depending on the settings @@ -823,3 +842,14 @@ function processDirectAttachments($emailMethod, $postfields = NULL, $boundary = return $attachments; } } + +function checkForHtml($ticket) { + global $hesk_settings; + + $repliesRs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "replies` WHERE `replyto` = ".intval($ticket['id']) . " ORDER BY `id` DESC LIMIT 1"); + if (hesk_dbNumRows($repliesRs) != 1) { + return $ticket['html']; + } + $reply = hesk_dbFetchAssoc($repliesRs); + return $reply['html']; +} \ No newline at end of file diff --git a/inc/html2text/html2text.php b/inc/html2text/html2text.php new file mode 100755 index 00000000..f3afbef6 --- /dev/null +++ b/inc/html2text/html2text.php @@ -0,0 +1,32 @@ +In particular, it tries to maintain the following features: + * + * + * @param string html the input HTML + * @return string the HTML converted, as best as possible, to text + * @throws Html2TextException if the HTML could not be loaded as a {@link DOMDocument} + */ + static function convert($html) { + // replace   with spaces + $html = str_replace(" ", " ", $html); + + $html = static::fixNewlines($html); + + $doc = new \DOMDocument(); + if (!$doc->loadHTML($html)) { + throw new Html2TextException("Could not load HTML - badly formed?", $html); + } + + $output = static::iterateOverNode($doc); + + // remove leading and trailing spaces on each line + $output = preg_replace("/[ \t]*\n[ \t]*/im", "\n", $output); + + // remove leading and trailing whitespace + $output = trim($output); + + return $output; + } + + /** + * Unify newlines; in particular, \r\n becomes \n, and + * then \r becomes \n. This means that all newlines (Unix, Windows, Mac) + * all become \ns. + * + * @param string text text with any number of \r, \r\n and \n combinations + * @return string the fixed text + */ + static function fixNewlines($text) { + // replace \r\n to \n + $text = str_replace("\r\n", "\n", $text); + // remove \rs + $text = str_replace("\r", "\n", $text); + + return $text; + } + + static function nextChildName($node) { + // get the next child + $nextNode = $node->nextSibling; + while ($nextNode != null) { + if ($nextNode instanceof \DOMElement) { + break; + } + $nextNode = $nextNode->nextSibling; + } + $nextName = null; + if ($nextNode instanceof \DOMElement && $nextNode != null) { + $nextName = strtolower($nextNode->nodeName); + } + + return $nextName; + } + + static function prevChildName($node) { + // get the previous child + $nextNode = $node->previousSibling; + while ($nextNode != null) { + if ($nextNode instanceof \DOMElement) { + break; + } + $nextNode = $nextNode->previousSibling; + } + $nextName = null; + if ($nextNode instanceof \DOMElement && $nextNode != null) { + $nextName = strtolower($nextNode->nodeName); + } + + return $nextName; + } + + static function iterateOverNode($node) { + if ($node instanceof \DOMText) { + // Replace whitespace characters with a space (equivilant to \s) + return preg_replace("/[\\t\\n\\f\\r ]+/im", " ", $node->wholeText); + } + if ($node instanceof \DOMDocumentType) { + // ignore + return ""; + } + + $nextName = static::nextChildName($node); + $prevName = static::prevChildName($node); + + $name = strtolower($node->nodeName); + + // start whitespace + switch ($name) { + case "hr": + return "------\n"; + + case "style": + case "head": + case "title": + case "meta": + case "script": + // ignore these tags + return ""; + + case "h1": + case "h2": + case "h3": + case "h4": + case "h5": + case "h6": + case "ol": + case "ul": + // add two newlines, second line is added below + $output = "\n"; + break; + + case "td": + case "th": + // add tab char to separate table fields + $output = "\t"; + break; + + case "tr": + case "p": + case "div": + // add one line + $output = "\n"; + break; + + case "li": + $output = "- "; + break; + + default: + // print out contents of unknown tags + $output = ""; + break; + } + + // debug + //$output .= "[$name,$nextName]"; + + if (isset($node->childNodes)) { + for ($i = 0; $i < $node->childNodes->length; $i++) { + $n = $node->childNodes->item($i); + + $text = static::iterateOverNode($n); + + $output .= $text; + } + } + + // end whitespace + switch ($name) { + case "style": + case "head": + case "title": + case "meta": + case "script": + // ignore these tags + return ""; + + case "h1": + case "h2": + case "h3": + case "h4": + case "h5": + case "h6": + $output .= "\n"; + break; + + case "p": + case "br": + // add one line + if ($nextName != "div") + $output .= "\n"; + break; + + case "div": + // add one line only if the next child isn't a div + if ($nextName != "div" && $nextName != null) + $output .= "\n"; + break; + + case "a": + // links are returned in [text](link) format + $href = $node->getAttribute("href"); + if ($href == null) { + // it doesn't link anywhere + if ($node->getAttribute("name") != null) { + $output = "[$output]"; + } + } else { + if ($href == $output || $href == "mailto:$output" || $href == "http://$output" || $href == "https://$output") { + // link to the same address: just use link + $output; + } else { + // replace it + $output = "[$output]($href)"; + } + } + + // does the next node require additional whitespace? + switch ($nextName) { + case "h1": case "h2": case "h3": case "h4": case "h5": case "h6": + $output .= "\n"; + break; + } + break; + + case "li": + $output .= "\n"; + break; + + default: + // do nothing + } + + return $output; + } + +} diff --git a/inc/html2text/src/Html2TextException.php b/inc/html2text/src/Html2TextException.php new file mode 100755 index 00000000..ddfa8658 --- /dev/null +++ b/inc/html2text/src/Html2TextException.php @@ -0,0 +1,28 @@ +more_info = $more_info; + } +} diff --git a/inc/posting_functions.inc.php b/inc/posting_functions.inc.php index 3bc629a7..bc4559d7 100644 --- a/inc/posting_functions.inc.php +++ b/inc/posting_functions.inc.php @@ -168,7 +168,8 @@ function hesk_newTicket($ticket, $isVerified = true) 'dt' => hesk_date(), 'lastchange' => hesk_date(), 'id' => hesk_dbInsertID(), - 'language' => $language + 'language' => $language, + 'html' => $ticket['html'] ); // Add custom fields to the array diff --git a/install/mods-for-hesk/sql/uninstallSql.php b/install/mods-for-hesk/sql/uninstallSql.php index a330eeef..bc95edb9 100644 --- a/install/mods-for-hesk/sql/uninstallSql.php +++ b/install/mods-for-hesk/sql/uninstallSql.php @@ -81,6 +81,8 @@ function removeOtherColumns() executeQuery("ALTER TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` DROP COLUMN `html`"); executeQuery("ALTER TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "stage_tickets` DROP COLUMN `html`"); executeQuery("ALTER TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "replies` DROP COLUMN `html`"); + executeQuery("ALTER TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "std_replies` DROP COLUMN `html`"); + executeQuery("ALTER TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "ticket_templates` DROP COLUMN `html`"); // These queries are ran in case someone used an unfortunate installation they may have not properly cleaned up tables