PageRenderTime 68ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 1ms

/tine20/Tinebase/Mail.php

https://github.com/tine20/Tine-2.0-Open-Source-Groupware-and-CRM
PHP | 736 lines | 446 code | 83 blank | 207 comment | 95 complexity | dccddd79782cf798e0ce3be311ef2a7c MD5 | raw file
  1. <?php
  2. /**
  3. * Tine 2.0
  4. *
  5. * @package Tinebase
  6. * @subpackage Mail
  7. * @license http://www.gnu.org/licenses/agpl.html AGPL Version 3
  8. * @copyright Copyright (c) 2008-2018 Metaways Infosystems GmbH (http://www.metaways.de)
  9. * @author Lars Kneschke <l.kneschke@metaways.de>
  10. */
  11. /**
  12. * This class extends the Zend_Mail class
  13. *
  14. * @package Tinebase
  15. * @subpackage Mail
  16. */
  17. class Tinebase_Mail extends Zend_Mail
  18. {
  19. /**
  20. * email address regexp
  21. *
  22. * NOTE: we currently do not support umlauts
  23. * see https://stackoverflow.com/questions/15121359/are-international-characters-e-g-umlaut-characters-valid-in-the-local-part-of
  24. */
  25. const EMAIL_ADDRESS_REGEXP = '/^([a-z0-9_\+-\.&]+@[a-z0-9-\.]+\.[a-z]{2,63})$/i';
  26. /**
  27. * email address regexp (which might be contained in a longer text)
  28. */
  29. const EMAIL_ADDRESS_CONTAINED_REGEXP = '/([a-z0-9_\+-\.&]+@[a-z0-9-\.]+\.[a-z]{2,63})/i';
  30. /**
  31. * Sender: address
  32. * @var string
  33. */
  34. protected $_sender = null;
  35. /**
  36. * fallback charset constant
  37. *
  38. * @var string
  39. */
  40. const DEFAULT_FALLBACK_CHARSET = 'iso-8859-15';
  41. /**
  42. * create Tinebase_Mail from Zend_Mail_Message
  43. *
  44. * @param Zend_Mail_Message $_zmm
  45. * @param string $_replyBody
  46. * @return Tinebase_Mail
  47. */
  48. public static function createFromZMM(Zend_Mail_Message $_zmm, $_replyBody = null, $_signature = null)
  49. {
  50. if (empty($_signature)) {
  51. $content = $_zmm->getContent();
  52. } else {
  53. $content = self::_getZMMContentWithSignature($_zmm, $_signature);
  54. }
  55. $contentStream = fopen("php://temp", 'r+');
  56. fputs($contentStream, $content);
  57. rewind($contentStream);
  58. $mp = new Zend_Mime_Part($contentStream);
  59. self::_getMetaDataFromZMM($_zmm, $mp);
  60. // append old body when no multipart/mixed
  61. if ($_replyBody !== null && $_zmm->headerExists('content-transfer-encoding')) {
  62. $mp = self::_appendReplyBody($mp, $_replyBody);
  63. } else {
  64. $mp->decodeContent();
  65. if ($_zmm->headerExists('content-transfer-encoding')) {
  66. switch ($_zmm->getHeader('content-transfer-encoding')) {
  67. case Zend_Mime::ENCODING_BASE64:
  68. // BASE64 encode has a bug that swallows the last char(s)
  69. $bodyEncoding = Zend_Mime::ENCODING_7BIT;
  70. break;
  71. default:
  72. $bodyEncoding = $_zmm->getHeader('content-transfer-encoding');
  73. }
  74. } else {
  75. $bodyEncoding = Zend_Mime::ENCODING_7BIT;
  76. }
  77. if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
  78. . ' Using encoding: ' . $bodyEncoding);
  79. $mp->encoding = $bodyEncoding;
  80. }
  81. $result = new Tinebase_Mail('utf-8');
  82. $result->setBodyText($mp);
  83. $result->setHeadersFromZMM($_zmm);
  84. return $result;
  85. }
  86. /**
  87. * get content from Zend_Mail_Message with attached mail signature
  88. *
  89. * @param Zend_Mail_Message $zmm
  90. * @param string $signature
  91. * @return string
  92. */
  93. protected static function _getZMMContentWithSignature(Zend_Mail_Message $zmm, $signature)
  94. {
  95. if (stripos($zmm->contentType, 'multipart/') === 0) {
  96. // Multipart message
  97. $zmm->rewind();
  98. $boundary = $zmm->getHeaderField('content-type', 'boundary');
  99. $rawHeaders = [];
  100. foreach (Zend_Mime_Decode::splitMime($zmm->getContent(), $boundary) as $mimePart) {
  101. $mimePart = str_replace("\r", '', $mimePart);
  102. array_push($rawHeaders, substr($mimePart, 0, strpos($mimePart, "\n\n")));
  103. }
  104. $content = '';
  105. for ($num = 1; $num <= $zmm->countParts(); $num++) {
  106. $zmp = $zmm->getPart($num);
  107. $content .= "\r\n--" . $boundary . "\r\n";
  108. $content .= $rawHeaders[$num-1] . "\r\n\r\n";
  109. $content .= self::_getPartContentWithSignature($zmp, $signature);
  110. }
  111. $content .= "\r\n--" . $boundary . "--\r\n";
  112. return $content;
  113. }
  114. else {
  115. $content = self::_getPartContentWithSignature($zmm, $signature);
  116. }
  117. return $content;
  118. }
  119. /**
  120. * get content from Zend_Mail_Part with attached mail signature
  121. *
  122. * @param Zend_Mail_Part $zmp
  123. * @param string $signature
  124. * @return string
  125. */
  126. public static function _getPartContentWithSignature($zmp, $signature)
  127. {
  128. $contentType = $zmp->getHeaderField('content-type', 0);
  129. if (($contentType != 'text/html') && ($contentType != 'text/plain')) {
  130. // Modify text parts only
  131. return $zmp->getContent();
  132. }
  133. if (($zmp->headerExists('Content-Disposition')) && (stripos($zmp->contentDisposition, 'attachment;') === 0)) {
  134. // Do not modify attachment
  135. return $zmp->getContent();
  136. }
  137. if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
  138. . ' Attaching signature to ' . $contentType . ' mime part');
  139. $content = $zmp->getContent();
  140. if ($zmp->contentTransferEncoding == Zend_Mime::ENCODING_BASE64) {
  141. $content = base64_decode($content);
  142. }
  143. else if ($zmp->contentTransferEncoding == Zend_Mime::ENCODING_QUOTEDPRINTABLE) {
  144. $content = quoted_printable_decode($content);
  145. }
  146. if ($contentType == "text/html") {
  147. $signature = "<br />&minus;&minus;<br />" . $signature;
  148. }
  149. else {
  150. $signature = Felamimail_Message::convertFromHTMLToText($signature, "\n");
  151. $signature = "\n--\n" . $signature;
  152. }
  153. $content .= $signature;
  154. return Zend_Mime::encode($content, $zmp->contentTransferEncoding);
  155. }
  156. /**
  157. * get meta data (like contentype, charset, ...) from zmm and set it in zmp
  158. *
  159. * @param Zend_Mail_Message $zmm
  160. * @param Zend_Mime_Part $zmp
  161. */
  162. protected static function _getMetaDataFromZMM(Zend_Mail_Message $zmm, Zend_Mime_Part $zmp)
  163. {
  164. if ($zmm->headerExists('content-transfer-encoding')) {
  165. $zmp->encoding = $zmm->getHeader('content-transfer-encoding');
  166. } else {
  167. $zmp->encoding = Zend_Mime::ENCODING_7BIT;
  168. }
  169. if ($zmm->headerExists('content-type')) {
  170. $contentTypeHeader = Zend_Mime_Decode::splitHeaderField($zmm->getHeader('content-type'));
  171. $zmp->type = $contentTypeHeader[0];
  172. if (isset($contentTypeHeader['boundary'])) {
  173. $zmp->boundary = $contentTypeHeader['boundary'];
  174. }
  175. if (isset($contentTypeHeader['charset'])) {
  176. $zmp->charset = $contentTypeHeader['charset'];
  177. }
  178. } else {
  179. $zmp->type = Zend_Mime::TYPE_TEXT;
  180. }
  181. if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
  182. . ' Encoding: ' . $zmp->encoding . ' / type: ' . $zmp->type . ' / charset: ' . $zmp->charset);
  183. }
  184. /**
  185. * appends old body to mime part
  186. *
  187. * @param Zend_Mime_Part $mp
  188. * @param string $replyBody plain/text reply body
  189. * @return Zend_Mime_Part
  190. */
  191. protected static function _appendReplyBody(Zend_Mime_Part $mp, $replyBody)
  192. {
  193. $decodedContent = Tinebase_Mail::getDecodedContent($mp, NULL, FALSE);
  194. $type = $mp->type;
  195. if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) {
  196. Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . " mp content: " . $decodedContent);
  197. Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . " reply body: " . $replyBody);
  198. }
  199. if ($type === Zend_Mime::TYPE_HTML && /* checks if $replyBody does not contains tags */ $replyBody === strip_tags($replyBody)) {
  200. if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
  201. . " Converting plain/text reply body to HTML");
  202. $replyBody = self::convertFromTextToHTML($replyBody);
  203. }
  204. if ($type === Zend_Mime::TYPE_HTML && preg_match('/(<\/body>[\s\r\n]*<\/html>)/i', $decodedContent, $matches)) {
  205. if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
  206. . ' Appending reply body to html body.');
  207. $decodedContent = str_replace($matches[1], $replyBody . $matches[1], $decodedContent);
  208. } else {
  209. if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
  210. . " Appending reply body to mime text part.");
  211. $decodedContent .= $replyBody;
  212. }
  213. $mp = new Zend_Mime_Part($decodedContent);
  214. $mp->charset = 'utf-8';
  215. $mp->type = $type;
  216. return $mp;
  217. }
  218. /**
  219. * Sets the HTML body for the message
  220. *
  221. * @param string|Zend_Mime_Part $html
  222. * @param string $charset
  223. * @param string $encoding
  224. * @return Zend_Mail Provides fluent interface
  225. */
  226. public function setBodyHtml($html, $charset = null, $encoding = Zend_Mime::ENCODING_QUOTEDPRINTABLE)
  227. {
  228. if ($html instanceof Zend_Mime_Part) {
  229. $mp = $html;
  230. } else {
  231. if ($charset === null) {
  232. $charset = $this->_charset;
  233. }
  234. $mp = new Zend_Mime_Part($html);
  235. $mp->encoding = $encoding;
  236. $mp->type = Zend_Mime::TYPE_HTML;
  237. $mp->disposition = Zend_Mime::DISPOSITION_INLINE;
  238. $mp->charset = $charset;
  239. }
  240. $this->_bodyHtml = $mp;
  241. return $this;
  242. }
  243. /**
  244. * Sets the text body for the message.
  245. *
  246. * @param string|Zend_Mime_Part $txt
  247. * @param string $charset
  248. * @param string $encoding
  249. * @return Zend_Mail Provides fluent interface
  250. */
  251. public function setBodyText($txt, $charset = null, $encoding = Zend_Mime::ENCODING_QUOTEDPRINTABLE)
  252. {
  253. if ($txt instanceof Zend_Mime_Part) {
  254. $mp = $txt;
  255. } else {
  256. if ($charset === null) {
  257. $charset = $this->_charset;
  258. }
  259. $mp = new Zend_Mime_Part($txt);
  260. $mp->encoding = $encoding;
  261. $mp->type = Zend_Mime::TYPE_TEXT;
  262. $mp->disposition = Zend_Mime::DISPOSITION_INLINE;
  263. $mp->charset = $charset;
  264. }
  265. $this->_bodyText = $mp;
  266. return $this;
  267. }
  268. public function setBodyPGPMime($amored)
  269. {
  270. $this->_type = 'multipart/encrypted; protocol="application/pgp-encrypted"';
  271. // PGP/MIME Versions Identification
  272. $pgpIdent = new Zend_Mime_Part('Version: 1');
  273. $pgpIdent->encoding = '7bit';
  274. $pgpIdent->type = 'application/pgp-encrypted';
  275. $pgpIdent->description = 'PGP/MIME Versions Identification';
  276. $this->_bodyText = $pgpIdent;
  277. // OpenPGP encrypted message
  278. $pgpMessage = new Zend_Mime_Part($amored);
  279. $pgpMessage->encoding = '7bit';
  280. $pgpMessage->disposition = 'inline; filename=encrypted.asc';
  281. $pgpMessage->type = 'application/octet-stream; name=encrypted.asc';
  282. $pgpMessage->description = 'OpenPGP encrypted message';
  283. $this->_bodyHtml = $pgpMessage;
  284. }
  285. /**
  286. * set headers
  287. *
  288. * @param Zend_Mail_Message $_zmm
  289. * @return Zend_Mail Provides fluent interface
  290. */
  291. public function setHeadersFromZMM(Zend_Mail_Message $_zmm)
  292. {
  293. foreach ($_zmm->getHeaders() as $header => $values) {
  294. foreach ((array)$values as $value) {
  295. switch ($header) {
  296. case 'content-transfer-encoding':
  297. // these are implicitly set by Zend_Mail_Transport_Abstract::_getHeaders()
  298. case 'content-type':
  299. case 'mime-version':
  300. // do nothing
  301. break;
  302. case 'bcc':
  303. $addresses = self::parseAdresslist($value);
  304. foreach ($addresses as $address) {
  305. $this->addBcc($address['address'], $address['name']);
  306. }
  307. break;
  308. case 'cc':
  309. $addresses = self::parseAdresslist($value);
  310. foreach ($addresses as $address) {
  311. $this->addCc($address['address'], $address['name']);
  312. }
  313. break;
  314. case 'date':
  315. try {
  316. $this->setDate($value);
  317. } catch (Zend_Mail_Exception $zme) {
  318. if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE))
  319. Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . " Could not set date: " . $value);
  320. if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE))
  321. Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . " " . $zme);
  322. $this->setDate();
  323. }
  324. break;
  325. case 'from':
  326. $addresses = self::parseAdresslist($value);
  327. foreach ($addresses as $address) {
  328. $this->setFrom($address['address'], $address['name']);
  329. }
  330. break;
  331. case 'message-id':
  332. $this->setMessageId(trim($value,"<>"));
  333. break;
  334. case 'return-path':
  335. $this->setReturnPath($value);
  336. break;
  337. case 'subject':
  338. $this->setSubject($value);
  339. break;
  340. case 'to':
  341. $addresses = self::parseAdresslist($value);
  342. foreach ($addresses as $address) {
  343. $this->addTo($address['address'], $address['name']);
  344. }
  345. break;
  346. case 'reply-to':
  347. $this->setReplyTo($value);
  348. break;
  349. default:
  350. $this->addHeader($header, $value);
  351. break;
  352. }
  353. }
  354. }
  355. return $this;
  356. }
  357. /**
  358. * Sets Sender-header and sender of the message
  359. *
  360. * @param string $email
  361. * @param string $name
  362. * @return Zend_Mail Provides fluent interface
  363. * @throws Zend_Mail_Exception if called subsequent times
  364. */
  365. public function setSender($email, $name = '')
  366. {
  367. if ($this->_sender === null) {
  368. $email = strtr($email,"\r\n\t",'???');
  369. $this->_from = $email;
  370. $this->_storeHeader('Sender', $this->_encodeHeader('"'.$name.'"').' <'.$email.'>', true);
  371. } else {
  372. throw new Zend_Mail_Exception('Sender Header set twice');
  373. }
  374. return $this;
  375. }
  376. /**
  377. * Formats e-mail address
  378. *
  379. * NOTE: we always add quotes to the name as this caused problems when name is encoded
  380. * @see Zend_Mail::_formatAddress
  381. *
  382. * @param string $email
  383. * @param string $name
  384. * @return string
  385. */
  386. protected function _formatAddress($email, $name)
  387. {
  388. if ($name === '' || $name === null || $name === $email) {
  389. return $email;
  390. } else {
  391. $encodedName = $this->_encodeHeader($name);
  392. $format = '"%s" <%s>';
  393. return sprintf($format, $encodedName, $email);
  394. }
  395. }
  396. /**
  397. * check if Zend_Mail_Message is/contains calendar iMIP message
  398. *
  399. * @param Zend_Mail_Message $zmm
  400. * @return boolean
  401. */
  402. public static function isiMIPMail(Zend_Mail_Message $zmm)
  403. {
  404. foreach ($zmm as $part) {
  405. if (preg_match('/text\/calendar/', $part->contentType)) {
  406. return TRUE;
  407. }
  408. }
  409. return FALSE;
  410. }
  411. /**
  412. * get decoded body content
  413. *
  414. * @param Zend_Mime_Part $zmp
  415. * @param array $partStructure
  416. * @param boolean $appendCharsetFilter
  417. * @return string
  418. */
  419. public static function getDecodedContent(Zend_Mime_Part $zmp, $_partStructure = NULL, $appendCharsetFilter = TRUE)
  420. {
  421. $charset = self::_getCharset($zmp, $_partStructure);
  422. if ($appendCharsetFilter) {
  423. $charset = self::_appendCharsetFilter($zmp, $charset);
  424. }
  425. $encoding = (is_array($_partStructure) && ! empty($_partStructure['encoding']))
  426. ? $_partStructure['encoding']
  427. : $zmp->encoding;
  428. if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
  429. . " Trying to decode mime part content. Encoding/charset: " . $encoding . ' / ' . $charset);
  430. // need to set error handler because stream_get_contents just throws a E_WARNING
  431. set_error_handler('Tinebase_Mail::decodingErrorHandler', E_WARNING);
  432. try {
  433. $body = $zmp->getDecodedContent();
  434. restore_error_handler();
  435. } catch (Tinebase_Exception $e) {
  436. if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
  437. . " Decoding of " . $zmp->encoding . '/' . $encoding . ' encoded message failed: ' . $e->getMessage());
  438. // trying to fix decoding problems
  439. restore_error_handler();
  440. $zmp->resetStream();
  441. if (preg_match('/convert\.quoted-printable-decode/', $e->getMessage())) {
  442. if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' Trying workaround for http://bugs.php.net/50363.');
  443. $body = quoted_printable_decode(stream_get_contents($zmp->getRawStream()));
  444. $body = iconv($charset, 'utf-8', $body);
  445. } else {
  446. if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' Try again with fallback encoding.');
  447. $zmp->appendDecodeFilter(self::_getDecodeFilter());
  448. set_error_handler('Tinebase_Mail::decodingErrorHandler', E_WARNING);
  449. try {
  450. $body = $zmp->getDecodedContent();
  451. restore_error_handler();
  452. } catch (Tinebase_Exception $e) {
  453. restore_error_handler();
  454. if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' Fallback encoding failed. Trying base64_decode().');
  455. $zmp->resetStream();
  456. $decodedBody = base64_decode(stream_get_contents($zmp->getRawStream()));
  457. $body = @iconv($charset, 'utf-8', $decodedBody);
  458. if (empty($body)) {
  459. // if iconv above still fails we do mb_convert and replace all special chars ...
  460. $body = Tinebase_Helper::mbConvertTo($decodedBody);
  461. $body = Tinebase_Helper::replaceSpecialChars($body, false);
  462. }
  463. }
  464. }
  465. }
  466. return $body;
  467. }
  468. /**
  469. * convert charset (and return charset)
  470. *
  471. * @param Zend_Mime_Part $_part
  472. * @param array $_structure
  473. * @return string
  474. */
  475. protected static function _getCharset(Zend_Mime_Part $_part, $_structure = NULL)
  476. {
  477. return ($_structure && isset($_structure['parameters']['charset']))
  478. ? $_structure['parameters']['charset']
  479. : ($_part->charset ? $_part->charset : self::DEFAULT_FALLBACK_CHARSET);
  480. }
  481. /**
  482. * convert charset (and return charset)
  483. *
  484. * @param Zend_Mime_Part $_part
  485. * @param string $charset
  486. * @return string
  487. */
  488. protected static function _appendCharsetFilter(Zend_Mime_Part $_part, $charset)
  489. {
  490. if ('utf8' === $charset) {
  491. $charset = 'utf-8';
  492. } elseif ('us-ascii' === $charset) {
  493. // us-ascii caused problems with iconv encoding to utf-8
  494. $charset = self::DEFAULT_FALLBACK_CHARSET;
  495. } elseif (strpos($charset, '.') !== false) {
  496. // the stream filter does not like charsets with a dot in its name
  497. // stream_filter_append(): unable to create or locate filter "convert.iconv.ansi_x3.4-1968/utf-8//IGNORE"
  498. $charset = self::DEFAULT_FALLBACK_CHARSET;
  499. } elseif (@iconv($charset, 'utf-8', '') === false) {
  500. // check if charset is supported by iconv
  501. $charset = self::DEFAULT_FALLBACK_CHARSET;
  502. }
  503. $_part->appendDecodeFilter(self::_getDecodeFilter($charset));
  504. return $charset;
  505. }
  506. /**
  507. * get decode filter for stream_filter_append
  508. *
  509. * @param string $_charset
  510. * @return string
  511. */
  512. protected static function _getDecodeFilter($_charset = self::DEFAULT_FALLBACK_CHARSET)
  513. {
  514. if (in_array(strtolower($_charset), array('iso-8859-1', 'windows-1252', 'iso-8859-15')) && extension_loaded('mbstring')) {
  515. require_once 'StreamFilter/ConvertMbstring.php';
  516. $filter = 'convert.mbstring';
  517. } else {
  518. // //IGNORE works only as of PHP7.2 -> the code expects an error to occur, don't use //IGNORE
  519. $filter = "convert.iconv.$_charset/utf-8";
  520. }
  521. if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Appending decode filter: ' . $filter);
  522. return $filter;
  523. }
  524. /**
  525. * error exception handler for iconv decoding errors / only gets E_WARNINGs
  526. *
  527. * NOTE: PHP < 5.3 don't throws exceptions for Catchable fatal errors per default,
  528. * so we convert them into exceptions manually
  529. *
  530. * @param integer $severity
  531. * @param string $errstr
  532. * @param string $errfile
  533. * @param integer $errline
  534. * @throws Tinebase_Exception
  535. *
  536. * @todo maybe we can remove that because php 5.3+ is required now
  537. */
  538. public static function decodingErrorHandler($severity, $errstr, $errfile, $errline)
  539. {
  540. Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . " $errstr in {$errfile}::{$errline} ($severity)");
  541. throw new Tinebase_Exception($errstr);
  542. }
  543. /**
  544. * parse address list
  545. *
  546. * @param string $_adressList
  547. * @return array
  548. */
  549. public static function parseAdresslist($_addressList)
  550. {
  551. if (strpos($_addressList, ',') !== FALSE && substr_count($_addressList, '@') == 1) {
  552. // we have a comma in the name -> do not split string!
  553. $addresses = array($_addressList);
  554. } else {
  555. // create stream to be used with fgetcsv
  556. $stream = fopen("php://temp", 'r+');
  557. fputs($stream, $_addressList);
  558. rewind($stream);
  559. // alternative solution to create stream; yet untested
  560. #$stream = fopen('data://text/plain;base64,' . base64_encode($_addressList), 'r');
  561. // split addresses
  562. $addresses = fgetcsv($stream);
  563. }
  564. if (! is_array($addresses)) {
  565. if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ .
  566. ' Could not parse addresses: ' . var_export($addresses, TRUE));
  567. return array();
  568. }
  569. foreach ($addresses as $key => $address) {
  570. if (preg_match('/(.*)<(.+@[^@]+)>/', $address, $matches)) {
  571. $name = trim(trim($matches[1]), '"');
  572. $address = trim($matches[2]);
  573. $addresses[$key] = array('name' => substr($name, 0, 250), 'address' => $address);
  574. } else if (strpos($address, '@') !== false) {
  575. $address = preg_replace('/[,;]*/i', '', $address);
  576. $addresses[$key] = array('name' => null, 'address' => trim($address));
  577. } else {
  578. // skip this - no email address found
  579. unset($addresses[$key]);
  580. }
  581. }
  582. return $addresses;
  583. }
  584. /**
  585. * convert text to html
  586. * - replace quotes ('> ') with blockquotes
  587. * - does htmlspecialchars()
  588. * - converts linebreaks to <br />
  589. *
  590. * @param string $text
  591. * @param string $blockquoteClass
  592. * @return string
  593. */
  594. public static function convertFromTextToHTML($text, $blockquoteClass = null)
  595. {
  596. if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' Input: ' . $text);
  597. $lines = preg_split('/\r\n|\n|\r/', $text);
  598. $result = array();
  599. $indention = 0;
  600. foreach ($lines as $line) {
  601. // get indention level and remove quotes
  602. if (preg_match('/^>[> ]*/', $line, $matches)) {
  603. $indentionLevel = substr_count($matches[0], '>');
  604. $line = str_replace($matches[0], '', $line);
  605. } else {
  606. $indentionLevel = 0;
  607. }
  608. // convert html special chars
  609. $line = htmlspecialchars($line, ENT_COMPAT, 'UTF-8');
  610. // set blockquote tags for current indentionLevel
  611. while ($indention < $indentionLevel) {
  612. $class = $blockquoteClass ? 'class="' . $blockquoteClass . '"' : '';
  613. $line = '<blockquote ' . $class . '>' . $line;
  614. $indention++;
  615. }
  616. while ($indention > $indentionLevel) {
  617. $line = '</blockquote>' . $line;
  618. $indention--;
  619. }
  620. $result[] = $line;
  621. if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' Line: ' . $line);
  622. }
  623. $result = implode('<br />', $result);
  624. if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' Result: ' . $result);
  625. return $result;
  626. }
  627. /**
  628. * get imap/smtp connection options
  629. *
  630. * do we verify imap/smtp peers?
  631. *
  632. * @param integer $timeout connection timeout
  633. * @return array
  634. *
  635. * TODO use separate configs for imap/smtp/sieve...
  636. */
  637. public static function getConnectionOptions($timeout = 30)
  638. {
  639. $connectionOptions = array(
  640. 'timeout' => $timeout,
  641. );
  642. $tinebaseImapConfig = Tinebase_Config::getInstance()->get(Tinebase_Config::IMAP);
  643. if (isset($tinebaseImapConfig->verifyPeer) && $tinebaseImapConfig->verifyPeer == false) {
  644. $connectionOptions['context'] = array(
  645. 'ssl' => array(
  646. 'verify_peer' => false,
  647. 'verify_peer_name' => false
  648. ),
  649. );
  650. }
  651. return $connectionOptions;
  652. }
  653. }