forked from tine20/tine20
/
Message.php
373 lines (329 loc) · 11.5 KB
/
Message.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
<?php
/**
* Tine 2.0
*
* @package Felamimail
* @license http://www.gnu.org/licenses/agpl.html AGPL Version 3
* @author Lars Kneschke <l.kneschke@metaways.de>
* @copyright Copyright (c) 2009-2010 Metaways Infosystems GmbH (http://www.metaways.de)
*
*/
/**
* message model for Felamimail
*
* @package Felamimail
* @subpackage Model
*/
class Felamimail_Message extends Zend_Mail_Message
{
/**
* date formats for convertDate()
*
* @var array
*/
public static $dateFormats = array(
'D, j M Y H:i:s O',
'd-M-Y H:i:s O',
// TODO try to handle date format like this:
// Wed, 04 Jan 2017 16:02:58 0000
);
/**
* Public constructor
*
* In addition to the parameters of Zend_Mail_Message::__construct() this constructor supports:
* - uid use UID FETCH if ftru
*
* @param array $params list of parameters
* @throws Zend_Mail_Exception
*/
public function __construct(array $params)
{
if (isset($params['uid'])) {
$this->_useUid = (bool)$params['uid'];
}
parent::__construct($params);
}
/**
* convert text
*
* @param string $_string
* @param boolean $_isHeader (if not, use base64 decode)
* @param integer $_ellipsis use substring (0 ... value) if value is > 0
* @return string
*
* @todo make it work for message body (use table for quoted printables?)
*/
public static function convertText($_string, $_isHeader = TRUE, $_ellipsis = 0)
{
$string = $_string;
if (preg_match('/=?[\d,\w,-]*?[q,Q,b,B]?.*?=/', $string)) {
$string = preg_replace_callback('/(=[1-9,a-f]{2})/', function ($matches) {
return strtoupper($matches[1]);
}, $string);
if ($_isHeader) {
$string = iconv_mime_decode($string, 2);
}
}
if ($_ellipsis > 0 && strlen($string) > $_ellipsis) {
Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' String to long, cutting it to ' . $_ellipsis . ' chars.');
$string = substr($string, 0, $_ellipsis);
}
return $string;
}
/**
* convert date from sent/received
*
* @param string $_dateString
* @return Zend_Date
*/
public static function convertDate($_dateString)
{
try {
$date = new Tinebase_DateTime($_dateString ? $_dateString : '@0');
$date->setTimezone('UTC');
} catch (Exception $e) {
// try to fix missing timezone char
if (preg_match('/UT$/', $_dateString)) {
$_dateString .= 'C';
}
// try some explicit formats
foreach (self::$dateFormats as $format) {
$date = DateTime::createFromFormat($format, $_dateString);
if ($date) break;
}
if (! $date) {
Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " Date $_dateString could not be converted to DateTime -> using 1970-01-01 00:00:00.");
$date = new Tinebase_DateTime('@0');
}
}
return $date;
}
/**
* convert addresses into array with name/address
*
* @param string $_addresses
* @param idna_convert $_punycodeConverter
* @return array
*/
public static function convertAddresses($_addresses, $_punycodeConverter = NULL)
{
$result = array();
if (!empty($_addresses)) {
$addresses = Tinebase_Mail::parseAdresslist($_addresses);
if (is_array($addresses)) {
foreach($addresses as $address) {
if ($_punycodeConverter !== NULL && preg_match('/@xn--/', $address['address'])) {
$email = $_punycodeConverter->decode($address['address']);
if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
' Converted email from punycode ' . $address['address'] . ' to ' . $email);
} else {
$email = $address['address'];
}
$result[] = array(
'email' => trim($email),
'name' => $address['name']
);
}
}
}
return $result;
}
/**
* convert between content types (text/plain => text/html for example)
*
* @param string $_from
* @param string $_to
* @param string $_text
* @param string $_eol
* @param boolean $_addMarkup
* @return string
*
* @todo we should use Felamimail_Model_Message::getPlainTextBody here / move all conversion to one place
* @todo remove addHtmlMarkup?
*/
public static function convertContentType($_from, $_to, $_text)
{
// nothing todo
if ($_from == $_to) {
return $_text;
}
if ($_from == Zend_Mime::TYPE_TEXT && $_to == Zend_Mime::TYPE_HTML) {
$text = Tinebase_Mail::convertFromTextToHTML($_text, 'felamimail-body-blockquote');
$text = self::addHtmlMarkup($text);
} else {
$text = self::convertFromHTMLToText($_text);
}
return $text;
}
/**
* convert html to text
*
* @param string $_html
* @param string $_eol
* @return string
*/
public static function convertFromHTMLToText($_html, $_eol = "\r\n")
{
$text = preg_replace('/\<br *\/*\>/', $_eol, $_html);
$text = str_replace(' ', ' ', $text);
$text = strip_tags($text);
$text = html_entity_decode($text, ENT_NOQUOTES, 'UTF-8');
return $text;
}
/**
* add html markup to message body
*
* @param string $_body
* @return string
*/
public static function addHtmlMarkup($_body)
{
$result = '<html>'
. '<head>'
. '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">'
. '<title></title>'
. '<style type="text/css">'
. '.felamimail-body-blockquote {'
. 'margin: 5px 10px 0 3px;'
. 'padding-left: 10px;'
. 'border-left: 2px solid #000088;'
. '} '
. '</style>'
. '</head>'
. '<body>'
. $_body
. '</body></html>';
return $result;
}
/**
* replace uris with links
*
* @param string $_content
* @return string
*/
public static function replaceUris($_content)
{
$pattern = '@(https?://|ftp://)([^\s<>\)]+)@u';
$result = preg_replace($pattern, "<a href=\"\\1\\2\" target=\"_blank\">\\1\\2</a>", $_content);
// special handling for > at the end of an uri
$result = preg_replace('/>" target="/', '" target="', $result);
return $result;
}
/**
* replace emails with links
*
* @param string $_content
* @return string
*/
public static function replaceEmails($_content)
{
// add anchor to email addresses (remove mailto hrefs first)
$mailtoPattern = '/<a[="a-z\-0-9 ]*href="mailto:([a-z0-9_\+-\.]+@[a-z0-9-\.]+\.[a-z]{2,4})"[^>]*>.*<\/a>/iU';
$result = preg_replace($mailtoPattern, "\\1", $_content);
$emailRegex = Tinebase_Mail::EMAIL_ADDRESS_CONTAINED_REGEXP;
// don't match emails with '=' as used in subscription uris (?email=blabla@aha.com)
$emailRegex = str_replace('/(', '/^=(', $emailRegex);
$result = preg_replace($emailRegex, "<a href=\"#\" id=\"123:\\1\" class=\"tinebase-email-link\">\\1</a>", $result);
return $result;
}
/**
* replace targets in links
*
* @param string $_content
* @return string
*/
public static function replaceTargets($_content)
{
// uris
$pattern = "/target=[\'\"][^\'\"]*[\'\"]/";
$result = preg_replace($pattern, "target=\"_blank\"", $_content);
return $result;
}
/**
* create Felamimail message from Zend_Mail_Message
*
* @param Zend_Mail_Message $_zendMailMessage
* @return Felamimail_Model_Message
*/
public static function createMessageFromZendMailMessage(Zend_Mail_Message $_zendMailMessage)
{
$message = new Felamimail_Model_Message();
foreach ($_zendMailMessage->getHeaders() as $headerName => $headerValue) {
switch($headerName) {
case 'subject':
$message->$headerName = $headerValue;
break;
case 'from':
// do nothing
break;
case 'to':
case 'bcc':
case 'cc':
$receipients = array();
$addresses = Tinebase_Mail::parseAdresslist($headerValue);
foreach ($addresses as $address) {
$receipients[] = $address['address'];
}
$message->$headerName = $receipients;
break;
}
}
$contentType = $_zendMailMessage->getHeaderField('content-type', 0);
$message->content_type = $contentType;
// @todo convert to utf-8 if needed
//$charset = $_zendMailMessage->getHeaderField('content-type', 'charset');
$message->body = self::getDecodedContent($_zendMailMessage);
return $message;
}
public static function getDecodedContent($partOrMessage)
{
$encoding = $partOrMessage->headerExists('content-transfer-encoding')
? $partOrMessage->getHeaderField('content-transfer-encoding')
: Zend_Mime::ENCODING_QUOTEDPRINTABLE; // TODO best default?
switch ($encoding) {
case Zend_Mime::ENCODING_QUOTEDPRINTABLE:
$result = quoted_printable_decode($partOrMessage->getContent());
break;
case Zend_Mime::ENCODING_BASE64:
$result = base64_decode($partOrMessage->getContent());
break;
default:
$result = $partOrMessage->getContent();
break;
}
return $result;
}
/**
* getDecodedPartContent
*
* @param $partId
* @return string
* @throws Zend_Mail_Exception
*/
public function getDecodedPartContent($partId)
{
$part = $this->getPart($partId);
return self::getDecodedContent($part);
}
/**
* Get part of multipart message
*
* @param int|string $num number of part starting with 1 for first part
* @return Zend_Mail_Part wanted part
* @throws Zend_Mail_Exception
*/
public function getPart($num)
{
if (strpos($num, '.') !== false) {
// recurse into sub-parts (for example 1.1, 1.1.2, ...)
$parts = explode('.', $num);
$part = $this;
while (count($parts) > 0) {
$part = $part->getPart(array_shift($parts));
}
} else {
$part = parent::getPart((integer) $num);
}
return $part;
}
}