Merge branch '2015.11' into 2015.11-develop
[tine20] / tine20 / Felamimail / Controller / Message / Send.php
1 <?php
2 /**
3  * Tine 2.0
4  *
5  * @package     Felamimail
6  * @subpackage  Controller
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @author      Philipp Schüle <p.schuele@metaways.de>
9  * @copyright   Copyright (c) 2011-2014 Metaways Infosystems GmbH (http://www.metaways.de)
10  */
11
12 /**
13  * send message controller for Felamimail
14  *
15  * @package     Felamimail
16  * @subpackage  Controller
17  */
18 class Felamimail_Controller_Message_Send extends Felamimail_Controller_Message
19 {
20     /**
21      * holds the instance of the singleton
22      *
23      * @var Felamimail_Controller_Message_Send
24      */
25     private static $_instance = NULL;
26     
27     /**
28      * the constructor
29      *
30      * don't use the constructor. use the singleton
31      */
32     private function __construct() 
33     {
34         $this->_backend = new Felamimail_Backend_Cache_Sql_Message();
35     }
36     
37     /**
38      * don't clone. Use the singleton.
39      *
40      */
41     private function __clone() 
42     {
43     }
44     
45     /**
46      * the singleton pattern
47      *
48      * @return Felamimail_Controller_Message_Send
49      */
50     public static function getInstance() 
51     {
52         if (self::$_instance === NULL) {
53             self::$_instance = new Felamimail_Controller_Message_Send();
54         }
55         
56         return self::$_instance;
57     }
58     
59     /**
60      * send one message through smtp
61      * 
62      * @param Felamimail_Model_Message $_message
63      * @return Felamimail_Model_Message
64      * @throws Tinebase_Exception_SystemGeneric
65      */
66     public function sendMessage(Felamimail_Model_Message $_message)
67     {
68         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . 
69             ' Sending message with subject ' . $_message->subject . ' to ' . print_r($_message->to, TRUE));
70         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($_message->toArray(), TRUE));
71         
72         // increase execution time (sending message with attachments can take a long time)
73         $oldMaxExcecutionTime = Tinebase_Core::setExecutionLifeTime(300); // 5 minutes
74         
75         $account = Felamimail_Controller_Account::getInstance()->get($_message->account_id);
76         try {
77             $this->_resolveOriginalMessage($_message);
78             $mail = $this->createMailForSending($_message, $account, $nonPrivateRecipients);
79             $this->_sendMailViaTransport($mail, $account, $_message, true, $nonPrivateRecipients);
80         } catch (Exception $e) {
81             Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Could not send message: ' . $e);
82             $translation = Tinebase_Translation::getTranslation('Felamimail');
83             if (preg_match('/^501 5\.1\.3/', $e->getMessage())) {
84                 $messageText = $translation->_('Bad recipient address syntax');
85             } else if (preg_match('/^550 5\.1\.1 <(.*?)>/', $e->getMessage(), $match)) {
86                 $messageText = '<' . $match[1] . '>: ' . $translation->_('Recipient address rejected');
87             } else {
88                 $messageText = $e->getMessage();
89             }
90             $tesg = $this->_getErrorException($messageText);
91             throw $tesg;
92         }
93         
94         // reset max execution time to old value
95         Tinebase_Core::setExecutionLifeTime($oldMaxExcecutionTime);
96         
97         return $_message;
98     }
99     
100     /**
101      * places a Felamimail_Model_Message in original_id field of given message (if it had an original_id set)
102      * 
103      * @param Felamimail_Model_Message $_message
104      */
105     protected function _resolveOriginalMessage(Felamimail_Model_Message $_message)
106     {
107         if (! $_message->original_id || $_message->original_id instanceof Felamimail_Model_Message) {
108             return;
109         }
110         
111         $originalMessageId = $_message->original_id;
112         if (is_string($originalMessageId) && strpos($originalMessageId, '_') !== FALSE ) {
113             list($originalMessageId, $partId) = explode('_', $originalMessageId);
114         } else if (is_array($originalMessageId)) {
115             if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
116                     . ' Something strange happened. original_id is an array: ' . print_r($originalMessageId, true));
117             return;
118         } else {
119             $partId = NULL;
120         }
121         
122         try {
123             $originalMessage = ($originalMessageId) ? $this->get($originalMessageId) : NULL;
124         } catch (Tinebase_Exception_NotFound $tenf) {
125             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
126                 . ' Did not find original message (' . $originalMessageId . ')');
127             $originalMessage = NULL;
128         }
129         
130         $_message->original_id      = $originalMessage;
131         $_message->original_part_id = $partId;
132     }
133     
134     /**
135      * save message in folder (target folder can be within a different account)
136      * 
137      * @param string|Felamimail_Model_Folder $_folder globalname or folder record
138      * @param Felamimail_Model_Message $_message
139      * @return Felamimail_Model_Message
140      */
141     public function saveMessageInFolder($_folder, $_message)
142     {
143         $sourceAccount = Felamimail_Controller_Account::getInstance()->get($_message->account_id);
144         
145         if (is_string($_folder) && ($_folder === $sourceAccount->templates_folder || $_folder === $sourceAccount->drafts_folder)) {
146             // make sure that system folder exists
147             $systemFolder = $_folder === $sourceAccount->templates_folder ? Felamimail_Model_Folder::FOLDER_TEMPLATES : Felamimail_Model_Folder::FOLDER_DRAFTS;
148             $folder = Felamimail_Controller_Account::getInstance()->getSystemFolder($sourceAccount, $systemFolder);
149         } else if ($_folder instanceof Felamimail_Model_Folder) {
150             $folder = $_folder;
151         } else {
152             $folder = Felamimail_Controller_Folder::getInstance()->getByBackendAndGlobalName($_message->account_id, $_folder);
153         }
154         
155         $targetAccount = ($_message->account_id == $folder->account_id) ? $sourceAccount : Felamimail_Controller_Account::getInstance()->get($folder->account_id);
156         
157         $mailToAppend = $this->createMailForSending($_message, $sourceAccount);
158         
159         $transport = new Felamimail_Transport();
160         $mailAsString = $transport->getRawMessage($mailToAppend, $this->_getAdditionalHeaders($_message));
161         $flags = ($folder->globalname === $targetAccount->drafts_folder) ? array(Zend_Mail_Storage::FLAG_DRAFT) : null;
162         
163         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . 
164             ' Appending message ' . $_message->subject . ' to folder ' . $folder->globalname . ' in account ' . $targetAccount->name);
165         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . 
166             ' ' . $mailAsString);
167         
168         Felamimail_Backend_ImapFactory::factory($targetAccount)->appendMessage(
169             $mailAsString,
170             Felamimail_Model_Folder::encodeFolderName($folder->globalname),
171             $flags
172         );
173         
174         return $_message;
175     }
176     
177     /**
178      * Bcc recipients need to be added separately because they are removed by default
179      * 
180      * @param Felamimail_Model_Message $message
181      * @return array
182      */
183     protected function _getAdditionalHeaders($message)
184     {
185         $additionalHeaders = ($message && ! empty($message->bcc)) ? array('Bcc' => $message->bcc) : array();
186         return $additionalHeaders;
187     }
188     
189     /**
190      * create new mail for sending via SMTP
191      * 
192      * @param Felamimail_Model_Message $_message
193      * @param Felamimail_Model_Account $_account
194      * @param array $_nonPrivateRecipients
195      * @return Tinebase_Mail
196      */
197     public function createMailForSending(Felamimail_Model_Message $_message, Felamimail_Model_Account $_account, &$_nonPrivateRecipients = array())
198     {
199         // create new mail to send
200         $mail = new Tinebase_Mail('UTF-8');
201         $mail->setSubject($_message->subject);
202         
203         $this->_setMailBody($mail, $_message);
204         $this->_setMailFrom($mail, $_account, $_message);
205         $_nonPrivateRecipients = $this->_setMailRecipients($mail, $_message);
206         $this->_setMailHeaders($mail, $_account, $_message);
207         
208         $this->_addAttachments($mail, $_message);
209         
210         return $mail;
211     }
212     
213     /**
214      * send mail via transport (smtp)
215      * 
216      * @param Zend_Mail $_mail
217      * @param Felamimail_Model_Account $_account
218      * @param boolean $_saveInSent
219      * @param Felamimail_Model_Message $_message
220      * @param array $_nonPrivateRecipients
221      */
222     protected function _sendMailViaTransport(Zend_Mail $_mail, Felamimail_Model_Account $_account, Felamimail_Model_Message $_message = null, $_saveInSent = false, $_nonPrivateRecipients = array())
223     {
224         $smtpConfig = $_account->getSmtpConfig();
225         if (! empty($smtpConfig) && (isset($smtpConfig['hostname']) || array_key_exists('hostname', $smtpConfig))) {
226             $transport = new Felamimail_Transport($smtpConfig['hostname'], $smtpConfig);
227             
228             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
229                 $debugConfig = $smtpConfig;
230                 $whiteList = array('hostname', 'username', 'port', 'auth', 'ssl');
231                 foreach ($debugConfig as $key => $value) {
232                     if (! in_array($key, $whiteList)) {
233                         unset($debugConfig[$key]);
234                     }
235                 }
236                 Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
237                     . ' About to send message via SMTP with the following config: ' . print_r($debugConfig, true));
238             }
239             
240             Tinebase_Smtp::getInstance()->sendMessage($_mail, $transport);
241             
242             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
243                 . ' successful.');
244             
245             // append mail to sent folder
246             if ($_saveInSent) {
247                 $this->_saveInSent($transport, $_account, $this->_getAdditionalHeaders($_message));
248             }
249             
250             if ($_message !== null) {
251                 // add reply/forward flags if set
252                 if (! empty($_message->flags) 
253                     && ($_message->flags == Zend_Mail_Storage::FLAG_ANSWERED || $_message->flags == Zend_Mail_Storage::FLAG_PASSED)
254                     && $_message->original_id instanceof Felamimail_Model_Message
255                 ) {
256                     Felamimail_Controller_Message_Flags::getInstance()->addFlags($_message->original_id, array($_message->flags));
257                 }
258     
259                 // add email notes to contacts (only to/cc)
260                 if ($_message->note) {
261                     $this->_addEmailNote($_nonPrivateRecipients, $_message->subject, $_message->getPlainTextBody());
262                 }
263             }
264         } else {
265             Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Could not send message, no smtp config found.');
266         }
267     }
268     
269     /**
270      * add email notes to contacts with email addresses in $_recipients
271      *
272      * @param array $_recipients
273      * @param string $_subject
274      * 
275      * @todo add email home (when we have OR filters)
276      * @todo add link to message in sent folder?
277      */
278     protected function _addEmailNote($_recipients, $_subject, $_body)
279     {
280         $filter = new Addressbook_Model_ContactFilter(array(
281             array('field' => 'email', 'operator' => 'in', 'value' => $_recipients)
282             // OR: array('field' => 'email_home', 'operator' => 'in', 'value' => $_recipients)
283         ));
284         $contacts = Addressbook_Controller_Contact::getInstance()->search($filter);
285         
286         if (count($contacts)) {
287         
288             $translate = Tinebase_Translation::getTranslation($this->_applicationName);
289             
290             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Adding email notes to ' . count($contacts) . ' contacts.');
291             
292             $truncatedBody = (extension_loaded('mbstring')) ? mb_substr($_body, 0, 4096, 'UTF-8') : substr($_body, 0, 4096);
293             $noteText = $translate->_('Subject') . ':' . $_subject . "\n\n" . $translate->_('Body') . ': ' . $truncatedBody;
294             
295             try {
296                 foreach ($contacts as $contact) {
297                     $note = new Tinebase_Model_Note(array(
298                         'note_type_id'           => Tinebase_Notes::getInstance()->getNoteTypeByName('email')->getId(),
299                         'note'                   => $noteText,
300                         'record_id'              => $contact->getId(),
301                         'record_model'           => 'Addressbook_Model_Contact',
302                     ));
303                     
304                     Tinebase_Notes::getInstance()->addNote($note);
305                 }
306             } catch (Zend_Db_Statement_Exception $zdse) {
307                 Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' Saving note failed: ' . $noteText);
308                 Tinebase_Exception::log($zdse);
309             }
310         } else {
311             Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' Found no contacts to add notes to.');
312         }
313     }
314     
315     /**
316      * append mail to send folder
317      * 
318      * @param Felamimail_Transport $_transport
319      * @param Felamimail_Model_Account $_account
320      * @param array $_additionalHeaders
321      * @return void
322      */
323     protected function _saveInSent(Felamimail_Transport $_transport, Felamimail_Model_Account $_account, $_additionalHeaders = array())
324     {
325         try {
326             $mailAsString = $_transport->getRawMessage(NULL, $_additionalHeaders);
327             $sentFolder = Felamimail_Controller_Account::getInstance()->getSystemFolder($_account, Felamimail_Model_Folder::FOLDER_SENT);
328             
329             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
330                 ' About to save message in sent folder (' . $sentFolder->globalname . ') ...');
331             
332             Felamimail_Backend_ImapFactory::factory($_account)->appendMessage(
333                 $mailAsString,
334                 Felamimail_Model_Folder::encodeFolderName($sentFolder->globalname)
335             );
336             
337             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ 
338                 . ' Saved sent message in "' . $sentFolder->globalname . '".'
339             );
340         } catch (Zend_Mail_Protocol_Exception $zmpe) {
341             Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ 
342                 . ' Could not save sent message in "' . $sentFolder->globalname . '".'
343                 . ' Please check if a folder with this name exists.'
344                 . '(' . $zmpe->getMessage() . ')'
345             );
346         } catch (Zend_Mail_Storage_Exception $zmse) {
347             Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ 
348                 . ' Could not save sent message in "' . $sentFolder->globalname . '".'
349                 . ' Please check if a folder with this name exists.'
350                 . '(' . $zmse->getMessage() . ')'
351             );
352         }
353     }
354     
355     /**
356      * send Zend_Mail message via smtp
357      * 
358      * @param  mixed      $accountId
359      * @param  Zend_Mail  $mail
360      * @param  boolean    $saveInSent
361      * @param  Felamimail_Model_Message $originalMessage
362      * @return Zend_Mail
363      */
364     public function sendZendMail($accountId, Zend_Mail $mail, $saveInSent = false, $originalMessage = NULL)
365     {
366         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . 
367             ' Sending message with subject ' . $mail->getSubject() 
368         );
369         if ($originalMessage !== NULL) {
370             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . 
371                 ' Original Message subject: ' . $originalMessage->subject . ' / Flag to set: ' . var_export($originalMessage->flags, TRUE)
372             );
373             
374             // this is required for adding the reply/forward flag in _sendMailViaTransport()
375             $originalMessage->original_id = $originalMessage;
376         }
377         
378         // increase execution time (sending message with attachments can take a long time)
379         $oldMaxExcecutionTime = Tinebase_Core::setExecutionLifeTime(300); // 5 minutes
380         
381         // get account
382         $account = ($accountId instanceof Felamimail_Model_Account) ? $accountId : Felamimail_Controller_Account::getInstance()->get($accountId);
383         
384         $this->_setMailFrom($mail, $account);
385         $this->_setMailHeaders($mail, $account);
386         $this->_sendMailViaTransport($mail, $account, $originalMessage, $saveInSent);
387         
388         // reset max execution time to old value
389         Tinebase_Core::setExecutionLifeTime($oldMaxExcecutionTime);
390         
391         return $mail;
392     }
393     
394     /**
395      * set mail body
396      * 
397      * @param Tinebase_Mail $_mail
398      * @param Felamimail_Model_Message $_message
399      */
400     protected function _setMailBody(Tinebase_Mail $_mail, Felamimail_Model_Message $_message)
401     {
402         if (strpos($_message->body, '-----BEGIN PGP MESSAGE-----') === 0) {
403             $_mail->setBodyPGPMime($_message->body);
404             return;
405         }
406
407         if ($_message->content_type == Felamimail_Model_Message::CONTENT_TYPE_HTML) {
408             $_mail->setBodyHtml(Felamimail_Message::addHtmlMarkup($_message->body));
409             if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . $_mail->getBodyHtml(TRUE));
410         }
411         
412         $plainBodyText = $_message->getPlainTextBody();
413         $_mail->setBodyText($plainBodyText);
414         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . $_mail->getBodyText(TRUE));
415     }
416     
417     /**
418      * set from in mail to be sent
419      * 
420      * @param Tinebase_Mail $_mail
421      * @param Felamimail_Model_Account $_account
422      * @param Felamimail_Model_Message $_message
423      */
424     protected function _setMailFrom(Zend_Mail $_mail, Felamimail_Model_Account $_account, Felamimail_Model_Message $_message = NULL)
425     {
426         $_mail->clearFrom();
427         
428         $from = (isset($_account->from) && ! empty($_account->from)) 
429             ? $_account->from 
430             : Tinebase_Core::getUser()->accountFullName;
431         
432         $email = ($_message !== NULL && ! empty($_message->from_email)) ? $_message->from_email : $_account->email;
433         
434         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Set from for mail: ' . $email . ' / ' . $from);
435         
436         $_mail->setFrom($email, $from);
437     }
438     
439     /**
440      * set mail recipients
441      * 
442      * @param Tinebase_Mail $_mail
443      * @param Felamimail_Model_Message $_message
444      * @return array
445      */
446     protected function _setMailRecipients(Zend_Mail $_mail, Felamimail_Model_Message $_message)
447     {
448         $nonPrivateRecipients = array();
449         $punycodeConverter = $this->getPunycodeConverter();
450         $invalidEmailAddresses = array();
451         
452         foreach (array('to', 'cc', 'bcc') as $type) {
453             if (isset($_message->{$type})) {
454                 foreach((array) $_message->{$type} as $address) {
455
456                     $punyCodedAddress = $punycodeConverter->encode($address);
457
458                     if (! preg_match(Tinebase_Mail::EMAIL_ADDRESS_REGEXP, $punyCodedAddress)) {
459                         $invalidEmailAddresses[] = $address;
460                         continue;
461                     }
462
463                     if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::'
464                         . __LINE__ . ' Add ' . $type . ' address: ' . $punyCodedAddress);
465                     
466                     switch($type) {
467                         case 'to':
468                             $_mail->addTo($punyCodedAddress);
469                             $nonPrivateRecipients[] = $punyCodedAddress;
470                             break;
471                         case 'cc':
472                             $_mail->addCc($punyCodedAddress);
473                             $nonPrivateRecipients[] = $punyCodedAddress;
474                             break;
475                         case 'bcc':
476                             $_mail->addBcc($punyCodedAddress);
477                             break;
478                     }
479                 }
480             }
481         }
482
483         if (count($invalidEmailAddresses) > 0) {
484             $translation = Tinebase_Translation::getTranslation('Felamimail');
485             $messageText = '<' . implode(',', $invalidEmailAddresses) . '>: ' . $translation->_('Invalid address format');
486             $fe = new Felamimail_Exception($messageText);
487             throw $fe;
488         }
489         
490         return $nonPrivateRecipients;
491     }
492
493     protected function _getErrorException($messageText)
494     {
495         $translation = Tinebase_Translation::getTranslation('Felamimail');
496         $message = sprintf($translation->_('Error: %s'), $messageText);
497         $tesg = new Tinebase_Exception_SystemGeneric($message);
498         $tesg->setTitle($translation->_('Could not send message'));
499
500         return $tesg;
501     }
502     
503     /**
504      * set headers in mail to be sent
505      * 
506      * @param Tinebase_Mail $_mail
507      * @param Felamimail_Model_Account $_account
508      * @param Felamimail_Model_Message $_message
509      */
510     protected function _setMailHeaders(Zend_Mail $_mail, Felamimail_Model_Account $_account, Felamimail_Model_Message $_message = NULL)
511     {
512         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Setting mail headers');
513         
514         // add user agent
515         $_mail->addHeader('User-Agent', Tinebase_Core::getTineUserAgent('Email Client'));
516         
517         // set organization
518         if (isset($_account->organization) && ! empty($_account->organization)) {
519             $_mail->addHeader('Organization', $_account->organization);
520         }
521         
522         // set message-id (we could use Zend_Mail::createMessageId() here)
523         if ($_mail->getMessageId() === NULL) {
524             $domainPart = substr($_account->email, strpos($_account->email, '@'));
525             $uid = Tinebase_Record_Abstract::generateUID();
526             $_mail->setMessageId('<' . $uid . $domainPart . '>');
527         }
528         
529         if ($_message !== NULL) {
530             if ($_message->flags && $_message->flags == Zend_Mail_Storage::FLAG_ANSWERED && $_message->original_id instanceof Felamimail_Model_Message) {
531                 $this->_addReplyHeaders($_message);
532             }
533             
534             // set the header request response
535             if ($_message->reading_conf) {
536                 $_mail->addHeader('Disposition-Notification-To', $_message->from_email);
537             }
538             
539             // add other headers
540             if (! empty($_message->headers) && is_array($_message->headers)) {
541                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
542                     . ' Adding custom headers: ' . print_r($_message->headers, TRUE));
543                 foreach ($_message->headers as $key => $value) {
544                     $value = $this->_trimHeader($key, $value);
545                     $_mail->addHeader($key, $value);
546                 }
547             }
548         }
549     }
550     
551     /**
552      * trim message headers (Zend_Mail only supports < 998 chars)
553      * 
554      * @param string $value
555      * @return string
556      */
557     protected function _trimHeader($key, $value)
558     {
559         if (strlen($value) + strlen($key) > 998) {
560             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
561                 . ' Trimming header ' . $key);
562             
563             $value = substr(trim($value), 0, (995 - strlen($key)));
564
565             if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ 
566                 . $value);
567         }
568         
569         return $value;
570     }
571     
572     /**
573      * set In-Reply-To and References headers
574      * 
575      * @param Felamimail_Model_Message $message
576      * 
577      * @see http://www.faqs.org/rfcs/rfc2822.html / Section 3.6.4.
578      */
579     protected function _addReplyHeaders(Felamimail_Model_Message $message)
580     {
581         $originalHeaders = Felamimail_Controller_Message::getInstance()->getMessageHeaders($message->original_id);
582         if (! isset($originalHeaders['message-id'])) {
583             // no message-id -> skip this
584             return;
585         }
586
587         $messageHeaders = is_array($message->headers) ? $message->headers : array();
588         $messageHeaders['In-Reply-To'] = $originalHeaders['message-id'];
589         
590         $references = '';
591         if (isset($originalHeaders['references'])) {
592             $references = $originalHeaders['references'] . ' ';
593         } else if (isset($originalHeaders['in-reply-to'])) {
594             $references = $originalHeaders['in-reply-to'] . ' ';
595         }
596         $references .= $originalHeaders['message-id'];
597         $messageHeaders['References'] = $references;
598         
599         $message->headers = $messageHeaders;
600     }
601     
602     /**
603      * add attachments to mail
604      *
605      * @param Tinebase_Mail $_mail
606      * @param Felamimail_Model_Message $_message
607      * @throws Felamimail_Exception_IMAP
608      */
609     protected function _addAttachments(Tinebase_Mail $_mail, Felamimail_Model_Message $_message)
610     {
611         if (! isset($_message->attachments) || empty($_message->attachments)) {
612             return;
613         }
614
615         $maxAttachmentSize = $this->_getMaxAttachmentSize();
616         $totalSize = 0;
617
618         foreach ($_message->attachments as $attachment) {
619             if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
620                 . ' Adding attachment: ' . (is_object($attachment) ? print_r($attachment->toArray(), TRUE) : print_r($attachment, TRUE)));
621
622             if (isset($attachment['type'])
623                 && $attachment['type'] == Felamimail_Model_Message::CONTENT_TYPE_MESSAGE_RFC822
624                 && $_message->original_id instanceof Felamimail_Model_Message
625             ) {
626                 $part = $this->_getRfc822Attachment($attachment, $_message);
627
628             } else if ($attachment instanceof Tinebase_Model_TempFile || isset($attachment['tempFile'])) {
629                 $part = $this->_getTempFileAttachment($attachment);
630
631             } else {
632                 $part = $this->_getMessagePartAttachment($attachment);
633             }
634
635             if (! $part || empty($attachment['type'])) {
636                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
637                     . ' Skipping attachment ' . print_r($attachment, true));
638                 continue;
639             }
640             
641             $part->setTypeAndDispositionForAttachment($attachment['type'], $attachment['name']);
642
643             if (! empty($attachment['size'])) {
644                 $totalSize += $attachment['size'];
645             }
646             
647             if ($totalSize > $maxAttachmentSize) {
648                 if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
649                     . ' Current attachment size: ' . Tinebase_Helper::convertToMegabytes($totalSize) . ' MB / allowed size: '
650                     . Tinebase_Helper::convertToMegabytes($maxAttachmentSize) . ' MB');
651                 throw new Felamimail_Exception_IMAP('Maximum attachment size exceeded. Please remove one or more attachments.');
652             }
653             
654             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
655                 . ' Adding attachment ' . $part->type);
656             
657             $_mail->addAttachment($part);
658         }
659     }
660
661     /**
662      * get attachment of type CONTENT_TYPE_MESSAGE_RFC822
663      *
664      * @param $attachment
665      * @param $message
666      * @return Zend_Mime_Part
667      */
668     protected function _getRfc822Attachment(&$attachment, $message)
669     {
670         $part = $this->getMessagePart($message->original_id, ($message->original_part_id) ? $message->original_part_id : NULL);
671         $part->decodeContent();
672
673         // replace some chars from attachment name
674         $attachment['name'] = preg_replace("/[\s'\"]*/", "", $attachment['name']) . '.eml';
675
676         return $part;
677     }
678
679     /**
680      * get attachment defined by temp file
681      *
682      * @param $attachment
683      * @return null|Zend_Mime_Part
684      * @throws Tinebase_Exception_NotFound
685      */
686     protected function _getTempFileAttachment(&$attachment)
687     {
688         $tempFileBackend = Tinebase_TempFile::getInstance();
689         $tempFile = ($attachment instanceof Tinebase_Model_TempFile)
690             ? $attachment
691             : (((isset($attachment['tempFile']) || array_key_exists('tempFile', $attachment))) ? $tempFileBackend->get($attachment['tempFile']['id']) : NULL);
692
693         if ($tempFile === null) {
694             return null;
695         }
696
697         if (! $tempFile->path) {
698             Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' Could not find attachment.');
699             return null;
700         }
701
702         // get contents from uploaded file
703         $stream = fopen($tempFile->path, 'r');
704         $part = new Zend_Mime_Part($stream);
705
706         // RFC822 attachments are not encoded, set all others to ENCODING_BASE64
707         $part->encoding = ($tempFile->type == Felamimail_Model_Message::CONTENT_TYPE_MESSAGE_RFC822) ? null : Zend_Mime::ENCODING_BASE64;
708
709         $attachment['name'] = $tempFile->name;
710         $attachment['type'] = $tempFile->type;
711
712         if (! empty($tempFile->size)) {
713             $attachment['size'] = $tempFile->size;
714         }
715
716         return $part;
717     }
718
719     /**
720      * get attachment part defined by message id + part id
721      *
722      * @param $attachment
723      * @return null|Zend_Mime_Part
724      */
725     protected function _getMessagePartAttachment(&$attachment)
726     {
727         if (! isset($attachment['id']) || strpos($attachment['id'], '_') === false) {
728             Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' No valid message id/part id');
729             return null;
730         }
731
732         // might be an attachment defined by message id + part id -> fetch this and attach
733         list($messageId, $partId) = explode('_', $attachment['id']);
734         $part = $this->getMessagePart($messageId, $partId);
735         $part->decodeContent();
736
737         return $part;
738     }
739     
740     /**
741      * get max attachment size for outgoing mails
742      * 
743      * - currently it is set to memory_limit / 10
744      * - returns size in Bytes
745      * 
746      * @return integer
747      */
748     protected function _getMaxAttachmentSize()
749     {
750         $configuredMemoryLimit = ini_get('memory_limit');
751         
752         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
753             . ' memory_limit = ' . $configuredMemoryLimit);
754         
755         if ($configuredMemoryLimit === FALSE or $configuredMemoryLimit == -1) {
756             // set to a big default value
757             $configuredMemoryLimit = '512M';
758         }
759         
760         return Tinebase_Helper::convertToBytes($configuredMemoryLimit) / 10;
761     }
762 }