1<?php 2 3/** 4 * Core modules 5 * @package modules 6 * @subpackage core 7 */ 8 9/** 10 * Format a message body that has HMTL markup 11 * @subpackage core/functions 12 * @param string $str message HTML 13 * @param bool $images allow external images 14 * @return string 15 */ 16if (!hm_exists('format_msg_html')) { 17function format_msg_html($str, $images=false) { 18 $str = str_ireplace('</body>', '', $str); 19 require_once VENDOR_PATH.'autoload.php'; 20 $config = HTMLPurifier_Config::createDefault(); 21 $config->set('Cache.DefinitionImpl', null); 22 if (!$images) { 23 $config->set('URI.DisableExternalResources', true); 24 } 25 $config->set('URI.AllowedSchemes', array('mailto' => true, 'data' => true, 'http' => true, 'https' => true)); 26 $config->set('Filter.ExtractStyleBlocks.TidyImpl', true); 27 $purifier = new HTMLPurifier($config); 28 return @$purifier->purify($str); 29}} 30 31/** 32 * Convert HTML to plain text 33 * @param string $html content to convert 34 * @return string 35 */ 36if (!hm_exists('convert_html_to_text')) { 37function convert_html_to_text($html) { 38 $html = new HTMLToText($html); 39 return $html->text; 40}} 41 42/** 43 * Format image data 44 * @subpackage core/functions 45 * @param string $str binary image data 46 * @param string $mime_type type of image 47 * return string 48 */ 49if (!hm_exists('format_msg_image')) { 50function format_msg_image($str, $mime_type) { 51 return '<img class="msg_img" alt="" src="data:image/'.$mime_type.';base64,'.chunk_split(base64_encode($str)).'" />'; 52}} 53 54/** 55 * Format a plain text message 56 * @subpackage core/functions 57 * @param string $str message text 58 * @param object $output_mod Hm_Output_Module 59 */ 60if (!hm_exists('format_msg_text')) { 61function format_msg_text($str, $output_mod, $links=true) { 62 $str = str_replace("\t", ' ', $str); 63 $str = nl2br(str_replace(' ', '<wbr>', ($output_mod->html_safe($str)))).'<br />'; 64 $str = preg_replace("/(&(?!amp)[^;]+;)/", " $1", $str); 65 if ($links) { 66 $link_regex = "/((http|ftp|rtsp)s?:\/\/(%[[:digit:]A-Fa-f][[:digit:]A-Fa-f]|[-_\.!~\*';\/\?#:@&=\+$,%[:alnum:]])+)/m"; 67 $str = preg_replace($link_regex, "<a href=\"$1\">$1</a>", $str); 68 } 69 $str = preg_replace("/ (&[^;]+;)/", "$1", $str); 70 $str = str_replace('<wbr>', ' <wbr>', $str); 71 return preg_replace("/^(>.*<br \/>)/m", "<span class=\"reply_quote\">$1</span>", $str); 72}} 73 74/** 75 * Format reply text 76 * @subpackage core/functions 77 * @param string $txt message text 78 * @return string 79 */ 80if (!hm_exists('format_reply_text')) { 81function format_reply_text($txt) { 82 $lines = explode("\n", $txt); 83 $new_lines = array(); 84 foreach ($lines as $line) { 85 $pre = '> '; 86 if (preg_match("/^(>\s*)+/", $line, $matches)) { 87 $pre .= $matches[1]; 88 } 89 $wrap = 75 + strlen($pre); 90 $new_lines[] = preg_replace("/$pre /", "$pre", "> ".wordwrap($line, $wrap, "\n$pre")); 91 } 92 return implode("\n", $new_lines); 93}} 94 95/** 96 * Get reply to address 97 * @subpackage core/functions 98 * @param array $headers message headers 99 * @param string $type type (forward, reply, reply_all) 100 * @return string 101 */ 102if (!hm_exists('reply_to_address')) { 103function reply_to_address($headers, $type) { 104 $msg_to = ''; 105 $msg_cc = ''; 106 $headers = lc_headers($headers); 107 $parsed = array(); 108 $delivered_address = false; 109 if (array_key_exists('delivered-to', $headers)) { 110 $delivered_address = array('email' => $headers['delivered-to'], 111 'comment' => '', 'label' => ''); 112 } 113 114 if ($type == 'forward') { 115 return $msg_to; 116 } 117 foreach (array('reply-to', 'from', 'sender', 'return-path') as $fld) { 118 if (array_key_exists($fld, $headers)) { 119 list($parsed, $msg_to) = format_reply_address($headers[$fld], $parsed); 120 if ($msg_to) { 121 break; 122 } 123 } 124 } 125 if ($type == 'reply_all') { 126 if ($delivered_address) { 127 $parsed[] = $delivered_address; 128 } 129 if (array_key_exists('cc', $headers)) { 130 list($cc_parsed, $msg_cc) = format_reply_address($headers['cc'], $parsed); 131 $parsed += $cc_parsed; 132 } 133 if (array_key_exists('to', $headers)) { 134 list($parsed, $recips) = format_reply_address($headers['to'], $parsed); 135 if ($recips) { 136 if ($msg_cc) { 137 $msg_cc .= ', '.$recips; 138 } 139 else { 140 $msg_cc = $recips; 141 } 142 } 143 } 144 } 145 return array($msg_to, $msg_cc); 146}} 147 148/* 149 * Format a reply address line 150 * @param string $fld the field values from the E-mail being replied to 151 * @param array $excluded list of parsed addresses to exclude 152 * @return string 153 */ 154if (!hm_exists('format_reply_address')) { 155function format_reply_address($fld, $excluded) { 156 $addr = process_address_fld(trim($fld)); 157 $res = array(); 158 foreach ($addr as $v) { 159 $skip = false; 160 foreach ($excluded as $ex) { 161 if (strtolower($v['email']) == strtolower($ex['email'])) { 162 $skip = true; 163 break; 164 } 165 } 166 if (!$skip) { 167 $res[] = $v; 168 } 169 } 170 if ($res) { 171 return array($addr, implode(', ', array_map(function($v) { 172 if (trim($v['label'])) { 173 return $v['label'].' '.$v['email']; 174 } 175 else { 176 return $v['email']; 177 } 178 }, $res))); 179 } 180 return array($addr, ''); 181}} 182 183/** 184 * Get reply to subject 185 * @subpackage core/functions 186 * @param array $headers message headers 187 * @param string $type type (forward, reply, reply_all) 188 * @return string 189 */ 190if (!hm_exists('reply_to_subject')) { 191function reply_to_subject($headers, $type) { 192 $subject = ''; 193 if (array_key_exists('Subject', $headers)) { 194 if ($type == 'reply' || $type == 'reply_all') { 195 if (!preg_match("/^re:/i", trim($headers['Subject']))) { 196 $subject = sprintf("Re: %s", $headers['Subject']); 197 } 198 } 199 elseif ($type == 'forward') { 200 if (!preg_match("/^fwd:/i", trim($headers['Subject']))) { 201 $subject = sprintf("Fwd: %s", $headers['Subject']); 202 } 203 } 204 if (!$subject) { 205 $subject = $headers['Subject']; 206 } 207 } 208 return $subject; 209}} 210 211/** 212 * Get reply message lead in 213 * @subpackage core/functions 214 * @param array $headers message headers 215 * @param string $type type (forward, reply, reply_all) 216 * @param string $to reply to value 217 * @param object $output_mod output module object 218 * @return string 219 */ 220if (!hm_exists('reply_lead_in')) { 221function reply_lead_in($headers, $type, $to, $output_mod) { 222 $lead_in = ''; 223 if ($type == 'reply' || $type == 'reply_all') { 224 if (array_key_exists('Date', $headers)) { 225 if ($to) { 226 $lead_in = sprintf($output_mod->trans('On %s %s said')."\n\n", $headers['Date'], $to); 227 } 228 else { 229 $lead_in = sprintf($output_mod->trans('On %s, somebody said')."\n\n", $headers['Date']); 230 } 231 } 232 } 233 elseif ($type == 'forward') { 234 $flds = array(); 235 foreach( array('From', 'Date', 'Subject') as $fld) { 236 if (array_key_exists($fld, $headers)) { 237 $flds[$fld] = $headers[$fld]; 238 } 239 } 240 $lead_in = "\n\n----- ".$output_mod->trans('begin forwarded message')." -----\n\n"; 241 foreach ($flds as $fld => $val) { 242 $lead_in .= $fld.': '.$val."\n"; 243 } 244 $lead_in .= "\n"; 245 } 246 return $lead_in; 247}} 248 249/** 250 * Format reply field details 251 * @subpackage core/functions 252 * @param array $headers message headers 253 * @param string $body message body 254 * @param string $lead_in body lead in text 255 * @param string $reply_type type (forward, reply, reply_all) 256 * @param array $struct message structure details 257 * @param int $html set to 1 if the output should be HTML 258 * @return array 259 */ 260if (!hm_exists('reply_format_body')) { 261function reply_format_body($headers, $body, $lead_in, $reply_type, $struct, $html) { 262 $msg = ''; 263 $type = 'textplain'; 264 if (array_key_exists('type', $struct) && array_key_exists('subtype', $struct)) { 265 $type = strtolower($struct['type']).strtolower($struct['subtype']); 266 } 267 if ($html == 1) { 268 $msg = format_reply_as_html($body, $type, $reply_type, $lead_in); 269 } 270 else { 271 $msg = format_reply_as_text($body, $type, $reply_type, $lead_in); 272 } 273 return $msg; 274}} 275 276/** 277 * Format reply text as HTML 278 * @subpackage core/functions 279 * @param string $body message body 280 * @param string $type MIME type 281 * @param string $reply_type type (forward, reply, reply_all) 282 * @param string $lead_in body lead in text 283 * @return string 284 */ 285if (!hm_exists('format_reply_as_html')) { 286function format_reply_as_html($body, $type, $reply_type, $lead_in) { 287 if ($type == 'textplain') { 288 if ($reply_type == 'reply' || $reply_type == 'reply_all') { 289 $msg = nl2br($lead_in.format_reply_text($body)); 290 } 291 elseif ($reply_type == 'forward') { 292 $msg = nl2br($lead_in.$body); 293 } 294 } 295 elseif ($type == 'texthtml') { 296 $msg = nl2br($lead_in).'<hr /><blockquote>'.format_msg_html($body).'</blockquote>'; 297 } 298 return $msg; 299}} 300 301/** 302 * Format reply text as text 303 * @subpackage core/functions 304 * @param string $body message body 305 * @param string $type MIME type 306 * @param string $reply_type type (forward, reply, reply_all) 307 * @param string $lead_in body lead in text 308 * @return string 309 */ 310if (!hm_exists('format_reply_as_text')) { 311function format_reply_as_text($body, $type, $reply_type, $lead_in) { 312 $msg = ''; 313 if ($type == 'texthtml') { 314 if ($reply_type == 'reply' || $reply_type == 'reply_all') { 315 $msg = $lead_in.format_reply_text(convert_html_to_text($body)); 316 } 317 elseif ($reply_type == 'forward') { 318 $msg = $lead_in.convert_html_to_text($body); 319 } 320 } 321 elseif ($type == 'textplain') { 322 if ($reply_type == 'reply' || $reply_type == 'reply_all') { 323 $msg = $lead_in.format_reply_text($body); 324 } 325 else { 326 $msg = $lead_in.$body; 327 } 328 } 329 return $msg; 330}} 331 332/** 333 * Convert header keys to lowercase versions 334 * @param array $headers message headers 335 * @return array 336 */ 337if (!hm_exists('lc_headers')) { 338function lc_headers($headers) { 339 return array_change_key_case($headers, CASE_LOWER); 340}} 341 342/** 343 * Get the in-reply-to message id for replied 344 * @subpackage core/functions 345 * @param array $headers message headers 346 * @param string $type reply type 347 * @return string 348 */ 349if (!hm_exists('reply_to_id')) { 350function reply_to_id($headers, $type) { 351 $id = ''; 352 $headers = lc_headers($headers); 353 if ($type != 'forward' && array_key_exists('message-id', $headers)) { 354 $id = $headers['message-id']; 355 } 356 return $id; 357}} 358 359/** 360 * Get reply field details 361 * @subpackage core/functions 362 * @param string $body message body 363 * @param array $headers message headers 364 * @param array $struct message structure details 365 * @param int $html set to 1 if the output should be HTML 366 * @param string $type optional type (forward, reply, reply_all) 367 * @param object $output_mod output module object 368 * @param string $type the reply type 369 * @return array 370 */ 371if (!hm_exists('format_reply_fields')) { 372function format_reply_fields($body, $headers, $struct, $html, $output_mod, $type='reply') { 373 $msg_to = ''; 374 $msg = ''; 375 $subject = reply_to_subject($headers, $type); 376 $msg_id = reply_to_id($headers, $type); 377 list($msg_to, $msg_cc) = reply_to_address($headers, $type); 378 $lead_in = reply_lead_in($headers, $type, $msg_to, $output_mod); 379 $msg = reply_format_body($headers, $body, $lead_in, $type, $struct, $html); 380 return array($msg_to, $msg_cc, $subject, $msg, $msg_id); 381}} 382 383/** 384 * decode mail fields to human readable text 385 * @param string $string field to decode 386 * @return string decoded field 387 */ 388if (!hm_exists('decode_fld')) { 389function decode_fld($string) { 390 if (strpos($string, '=?') === false) { 391 return $string; 392 } 393 $string = preg_replace("/\?=\s+=\?/", '?==?', $string); 394 if (preg_match_all("/(=\?[^\?]+\?(q|b)\?[^\?]+\?=)/i", $string, $matches)) { 395 foreach ($matches[1] as $v) { 396 $fld = substr($v, 2, -2); 397 $charset = strtolower(substr($fld, 0, strpos($fld, '?'))); 398 $fld = substr($fld, (strlen($charset) + 1)); 399 $encoding = $fld[0]; 400 $fld = substr($fld, (strpos($fld, '?') + 1)); 401 if (strtoupper($encoding) == 'B') { 402 $fld = mb_convert_encoding(base64_decode($fld), 'UTF-8', $charset); 403 } 404 elseif (strtoupper($encoding) == 'Q') { 405 $fld = mb_convert_encoding(quoted_printable_decode(str_replace('_', ' ', $fld)), 'UTF-8', $charset); 406 } 407 $string = str_replace($v, $fld, $string); 408 } 409 } 410 return trim($string); 411}} 412 413/** 414 * @subpackage core/class 415 */ 416class HTMLToText { 417 418 public $text = ''; 419 private $current = false; 420 private $blocks = array('table', 'li', 'div', 'h1', 'h2', 'br', 'h3', 'h4', 'h5', 'p', 'tr'); 421 private $skips = array('head', 'script', 'style'); 422 423 function __construct($html) { 424 $doc = new DOMDocument(); 425 @$doc->loadHTML($html); 426 if (trim($html) && $doc->hasChildNodes()) { 427 $this->parse_nodes($doc->childNodes); 428 } 429 $this->text = trim(strip_tags(html_entity_decode(preg_replace("/\n{2,}/m", 430 "\n\n", $this->text), ENT_QUOTES, "UTF-8"))); 431 } 432 433 function block($tag) { 434 in_array($tag, $this->blocks) && $this->current != $tag ? $this->text .= "\n" : false; 435 $this->current = $tag; 436 } 437 438 function parse_nodes($nodes) { 439 $trims = chr(160).chr(194)." \t\n\r\0\x0B"; 440 foreach ($nodes as $node) { 441 if (!in_array($node->nodeName, $this->skips)) { 442 $this->block($node->nodeName); 443 if ($node->nodeName == '#text' && trim($node->textContent, $trims)) { 444 $this->text .= trim($node->textContent, $trims)." "; 445 } 446 $node->hasChildNodes() ? $this->parse_nodes($node->childNodes) : false; 447 } 448 } 449 } 450} 451 452/** 453 * trim a potential E-mail value 454 * @param $val string E-mail value 455 * @return string trimmed value 456 */ 457if (!hm_exists('addr_split')) { 458function trim_email($val) { 459 $seps = array(',', ';'); 460 $misc = array('"', "'", '>', '<'); 461 return trim($val, implode(array_merge($misc, $seps))); 462}} 463 464/** 465 * Split an address field 466 * @param $str string field value 467 * @param $seps array break chars 468 * @return array results 469 */ 470if (!hm_exists('addr_split')) { 471function addr_split($str, $seps = array(',', ';')) { 472 $str = preg_replace('/(\s){2,}/', ' ', $str); 473 $max = strlen($str); 474 $word = ''; 475 $words = array(); 476 $capture = false; 477 $capture_chars = array('"' => '"', '(' => ')', '<' => '>'); 478 for ($i=0;$i<$max;$i++) { 479 if ($capture && $capture_chars[$capture] == $str[$i]) { 480 $capture = false; 481 } 482 elseif (!$capture && in_array($str[$i], array_keys($capture_chars))) { 483 $capture = $str[$i]; 484 } 485 486 if (!$capture && in_array($str[$i], $seps)) { 487 $words[] = trim($word); 488 $word = ''; 489 } 490 else { 491 $word .= $str[$i]; 492 } 493 } 494 $words[] = trim($word); 495 return $words; 496}} 497 498/** 499 * Parse an address field 500 * @param $str string field value 501 * @return array results 502 */ 503if (!hm_exists('addr_parse')) { 504function addr_parse($str) { 505 $label = array(); 506 $email = ''; 507 $comment = array(); 508 foreach (addr_split($str, array(' ')) as $token) { 509 if (is_email_address(trim_email($token))) { 510 $email = trim_email($token); 511 } 512 else { 513 $label[] = $token; 514 } 515 } 516 $label = implode(' ', $label); 517 if (preg_match('/\([^)]+\)/', $label, $matches)) { 518 foreach ($matches as $match) { 519 $comment[] = $match; 520 $label = str_replace($match, '', $label); 521 } 522 $comment = implode(',', $comment); 523 } 524 else { 525 $comment = ''; 526 } 527 return array('email' => $email, 'label' => trim($label, ' \'"'), 'comment' => $comment); 528}} 529 530/** 531 * Parse an address field 532 * @param $fld string field value 533 * @return array results 534 */ 535if (!hm_exists('process_address_fld')) { 536function process_address_fld($fld) { 537 $res = array(); 538 $count = 0; 539 $pre = false; 540 foreach (addr_split($fld) as $str) { 541 $addr = addr_parse($str); 542 if ($addr['email']) { 543 if ($pre) { 544 $addr['label'] = $pre.' '.$addr['label']; 545 $pre = false; 546 } 547 $res[$count] = $addr; 548 } 549 elseif ($addr['label']) { 550 $pre = $addr['label']; 551 } 552 $count++; 553 } 554 return $res; 555}} 556