Merge branch '2013.10' into 2014.11
[tine20] / tine20 / Felamimail / Frontend / ActiveSync.php
1 <?php
2 /**
3  * Tine 2.0
4  *
5  * @package     Felamimail
6  * @subpackage  Frontend
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @copyright   Copyright (c) 2008-2015 Metaways Infosystems GmbH (http://www.metaways.de)
9  * @author      Lars Kneschke <l.kneschke@metaways.de>Lars Kneschke <l.kneschke@metaways.de>
10  */
11
12 /**
13  * ActiveSync frontend class
14  *
15  * @package     Felamimail
16  * @subpackage  Frontend
17  */
18 class Felamimail_Frontend_ActiveSync extends ActiveSync_Frontend_Abstract implements Syncroton_Data_IDataEmail, Syncroton_Data_IDataSearch
19 {
20     protected $_mapping = array(
21         'body'              => 'body',
22         'cc'                => 'cc',
23         'dateReceived'      => 'received',
24         'from'              => 'from_email',
25         #'Sender'            => 'sender',
26         'subject'           => 'subject',
27         'to'                => 'to'
28     );
29     
30     protected $_debugEmail = false;
31     
32     /**
33      * available filters
34      * 
35      * @var array
36      */
37     protected $_filterArray = array(
38         Syncroton_Command_Sync::FILTER_1_DAY_BACK,
39         Syncroton_Command_Sync::FILTER_3_DAYS_BACK,
40         Syncroton_Command_Sync::FILTER_1_WEEK_BACK,
41         Syncroton_Command_Sync::FILTER_2_WEEKS_BACK,
42         Syncroton_Command_Sync::FILTER_1_MONTH_BACK,
43     );
44     
45     /**
46      * felamimail message controller
47      *
48      * @var Felamimail_Controller_Message
49      */
50     protected $_messageController;
51     
52     /**
53      * felamimail account
54      * 
55      * @var Felamimail_Model_Account
56      */
57     protected $_account;
58     
59     /**
60      * app name (required by abstract class)
61      * 
62      * @var string
63      */
64     protected $_applicationName     = 'Felamimail';
65     
66     /**
67      * model name (required by abstract class)
68      * 
69      * @var string
70      */
71     protected $_modelName           = 'Message';
72     
73     /**
74      * type of the default folder
75      *
76      * @var int
77      */
78     protected $_defaultFolderType   = Syncroton_Command_FolderSync::FOLDERTYPE_INBOX;
79     
80     /**
81      * type of user created folders
82      *
83      * @var int
84      */
85     protected $_folderType          = Syncroton_Command_FolderSync::FOLDERTYPE_MAIL_USER_CREATED;
86     
87     /**
88      * name of property which defines the filterid for different content classes
89      * 
90      * @var string
91      */
92     protected $_filterProperty = 'emailfilterId';
93     
94     /**
95      * field to sort search results by
96      * 
97      * @var string
98      */
99     protected $_sortField = 'received';
100     
101     /**
102      * @var Felamimail_Controller_Message
103      */
104     protected $_contentController;
105     
106     /**
107      * (non-PHPdoc)
108      * @see Syncroton_Data_IDataEmail::forwardEmail()
109      */
110     public function forwardEmail($source, $inputStream, $saveInSent, $replaceMime)
111     {
112         $account = $this->_getAccount();
113         
114         if (!$account) {
115             throw new Syncroton_Exception('no email account configured');
116         }
117         
118         if (empty(Tinebase_Core::getUser()->accountEmailAddress)) {
119             throw new Syncroton_Exception('no email address set for current user');
120         }
121         
122         if (! is_resource($inputStream)) {
123             $stream = fopen("php://temp", 'r+');
124             fwrite($stream, $inputStream);
125             $inputStream = $stream;
126             rewind($inputStream);
127         }
128         
129          if ($this->_debugEmail == true) {
130              $debugStream = fopen("php://temp", 'r+');
131              stream_copy_to_stream($inputStream, $debugStream);
132              rewind($debugStream);
133              if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
134                  __METHOD__ . '::' . __LINE__ . " email to send:" . stream_get_contents($debugStream));
135         
136              // replace original stream with debug stream, as php://input can't be rewinded
137              $inputStream = $debugStream;
138              rewind($inputStream);
139          }
140         
141         $incomingMessage = new Zend_Mail_Message(
142             array(
143                 'file' => $inputStream
144             )
145         );
146         
147         $messageId = is_array($source) ? $source['itemId'] : $source;
148         $fmailMessage = Felamimail_Controller_Message::getInstance()->get($messageId);
149         $fmailMessage->flags = Zend_Mail_Storage::FLAG_PASSED;
150         
151         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
152             __METHOD__ . '::' . __LINE__ . " source: " . $messageId . "saveInSent: " . $saveInSent);
153         
154         if ($replaceMime === FALSE) {
155             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
156                  __METHOD__ . '::' . __LINE__ . " Adding RFC822 attachment and appending body to forward message.");
157             
158             $rfc822 = Felamimail_Controller_Message::getInstance()->getMessagePart($fmailMessage);
159             $rfc822->type = Felamimail_Model_Message::CONTENT_TYPE_MESSAGE_RFC822;
160             $rfc822->filename = 'forwarded_email.eml';
161             $rfc822->encoding = Zend_Mime::ENCODING_7BIT;
162             $replyBody = Felamimail_Controller_Message::getInstance()->getMessageBody($fmailMessage, NULL, 'text/plain');
163         } else {
164             $rfc822 = NULL;
165             $replyBody = NULL;
166         }
167         
168         $mail = Tinebase_Mail::createFromZMM($incomingMessage, $replyBody);
169         if ($rfc822) {
170             $mail->addAttachment($rfc822);
171         }
172         
173         Felamimail_Controller_Message_Send::getInstance()->sendZendMail($account, $mail, $saveInSent, $fmailMessage);
174     }
175     
176     /**
177      * get all entries changed between to dates
178      *
179      * @param unknown_type $_field
180      * @param unknown_type $_startTimeStamp
181      * @param unknown_type $_endTimeStamp
182      * @return array
183      */
184     public function getChangedEntries($folderId, DateTime $_startTimeStamp, DateTime $_endTimeStamp = NULL, $filterType = NULL)
185     {
186         $filter = $this->_getContentFilter(0);
187         
188         $this->_addContainerFilter($filter, $folderId);
189
190         $startTimeStamp = ($_startTimeStamp instanceof DateTime) ? $_startTimeStamp->format(Tinebase_Record_Abstract::ISO8601LONG) : $_startTimeStamp;
191         $endTimeStamp = ($_endTimeStamp instanceof DateTime) ? $_endTimeStamp->format(Tinebase_Record_Abstract::ISO8601LONG) : $_endTimeStamp;
192         
193         // @todo filter also for create_timestamp??
194         $filter->addFilter(new Tinebase_Model_Filter_DateTime(
195             'timestamp',
196             'after',
197             $startTimeStamp
198         ));
199         
200         if($endTimeStamp !== NULL) {
201             $filter->addFilter(new Tinebase_Model_Filter_DateTime(
202                 'timestamp',
203                 'before',
204                 $endTimeStamp
205             ));
206         }
207         
208         $result = $this->_contentController->search($filter, NULL, false, true, 'sync');
209         
210         return $result;
211     }
212     
213     /**
214      * retrieve folders which were modified since last sync
215      * 
216      * @param  DateTime $startTimeStamp
217      * @param  DateTime $endTimeStamp
218      * @return array
219      * 
220      * @todo implement
221      * 
222      * @see 0007786: changed email folder names do not sync to device
223      */
224     public function getChangedFolders(DateTime $startTimeStamp, DateTime $endTimeStamp)
225     {
226         $syncrotonFolders = array();
227         
228         // @todo calculate changed folders
229         
230         return $syncrotonFolders;
231     }
232     
233     /**
234      * (non-PHPdoc)
235      * @see ActiveSync_Frontend_Abstract::getFileReference()
236      */
237     public function getFileReference($fileReference)
238     {
239         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(
240             __METHOD__ . '::' . __LINE__ . " fileReference " . $fileReference);
241         
242         list($messageId, $partId) = explode(ActiveSync_Frontend_Abstract::LONGID_DELIMITER, $fileReference, 2);
243         
244         $part = $this->_contentController->getMessagePart($messageId, $partId);
245         
246         $syncrotonFileReference = new Syncroton_Model_FileReference(array(
247             'contentType' => $part->type,
248             'data'        => $part->getDecodedStream()
249         ));
250         
251         return $syncrotonFileReference;
252     }
253     
254     /**
255      * (non-PHPdoc)
256      * @see Syncroton_Data_IDataEmail::replyEmail()
257      */
258     public function replyEmail($source, $inputStream, $saveInSent, $replaceMime)
259     {
260         $account = $this->_getAccount();
261         
262         if (!$account) {
263             if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) Tinebase_Core::getLogger()->warn(
264                 __METHOD__ . '::' . __LINE__ . " no email account configured");
265             
266             throw new Syncroton_Exception('no email account configured');
267         }
268         
269         if (empty(Tinebase_Core::getUser()->accountEmailAddress)) {
270             throw new Syncroton_Exception('no email address set for current user');
271         }
272         
273         if (! is_resource($inputStream)) {
274             $stream = fopen("php://temp", 'r+');
275             fwrite($stream, $inputStream);
276             $inputStream = $stream;
277             rewind($inputStream);
278         }
279         
280         if ($this->_debugEmail == true) {
281              $debugStream = fopen("php://temp", 'r+');
282              stream_copy_to_stream($inputStream, $debugStream);
283              rewind($debugStream);
284              if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
285                  __METHOD__ . '::' . __LINE__ . " email to send:" . stream_get_contents($debugStream));
286         
287              //replace original stream wirh debug stream, as php://input can't be rewinded
288              $inputStream = $debugStream;
289              rewind($inputStream);
290         }
291         
292         $incomingMessage = new Zend_Mail_Message(
293             array(
294                 'file' => $inputStream
295             )
296         );
297         
298         $messageId = is_array($source) ? $source['itemId'] : $source;
299         $fmailMessage = Felamimail_Controller_Message::getInstance()->get($messageId);
300         $fmailMessage->flags = Zend_Mail_Storage::FLAG_ANSWERED;
301         
302         if ($replaceMime === false) {
303             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
304                 __METHOD__ . '::' . __LINE__ . " attach source: " . $messageId . " saveInSent: " . $saveInSent);
305             
306             $replyBody = Felamimail_Controller_Message::getInstance()->getMessageBody($fmailMessage, null, 'text/plain');
307         } else {
308             $replyBody = null;
309         }
310         
311         $mail = Tinebase_Mail::createFromZMM($incomingMessage, $replyBody);
312         
313         Felamimail_Controller_Message_Send::getInstance()->sendZendMail($account, $mail, (bool)$saveInSent, $fmailMessage);
314     }
315     
316     /**
317      * send email
318      * 
319      * @param resource $inputStream
320      * @param boolean $saveInSent
321      * @throws Syncroton_Exception
322      */
323     public function sendEmail($inputStream, $saveInSent)
324     {
325         $account = $this->_getAccount();
326         
327         if (!$account) {
328             if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) Tinebase_Core::getLogger()->warn(
329                 __METHOD__ . '::' . __LINE__ . " no email account configured");
330             
331             throw new Syncroton_Exception('no email account configured');
332         }
333         
334         if (empty(Tinebase_Core::getUser()->accountEmailAddress)) {
335             throw new Syncroton_Exception('no email address set for current user');
336         }
337         
338         if (! is_resource($inputStream)) {
339             $stream = fopen("php://temp", 'r+');
340             fwrite($stream, $inputStream);
341             $inputStream = $stream;
342             rewind($inputStream);
343         }
344         
345         if ($this->_debugEmail == true) {
346              $debugStream = fopen("php://temp", 'r+');
347              stream_copy_to_stream($inputStream, $debugStream);
348              rewind($debugStream);
349              if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
350                  __METHOD__ . '::' . __LINE__ . " email to send:" . stream_get_contents($debugStream));
351         
352              //replace original stream wirh debug stream, as php://input can't be rewinded
353              $inputStream = $debugStream;
354              rewind($inputStream);
355         }
356         
357         $incomingMessage = new Zend_Mail_Message(
358             array(
359                 'file' => $inputStream
360             )
361         );
362         
363         $subject = $incomingMessage->headerExists('subject') ? $incomingMessage->getHeader('subject') : null;
364         
365         if (Tinebase_Mail::isiMIPMail($incomingMessage)) {
366             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
367                 . ' Do not send iMIP message with subject "' . $subject . '". The server should handle those.');
368         } else {
369             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
370                 __METHOD__ . '::' . __LINE__ . " Send Message with subject " . $subject . " (saveInSent: " . $saveInSent . ")");
371             
372             $mail = Tinebase_Mail::createFromZMM($incomingMessage);
373         
374             Felamimail_Controller_Message_Send::getInstance()->sendZendMail($account, $mail, (bool)$saveInSent);
375         }
376     }
377     
378     /**
379      * (non-PHPdoc)
380      * @see ActiveSync_Frontend_Abstract::toSyncrotonModel()
381      */
382     public function toSyncrotonModel($entry, array $options = array())
383     {
384         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
385             __METHOD__ . '::' . __LINE__ . " email data " . print_r($entry->toArray(), true));
386         
387         if (! $this->_isMemoryLeft($entry->size)) {
388             throw new Syncroton_Exception_MemoryExhausted('not enough memory left for: ' . $entry->getId() . ' Needed: ' . $entry->size);
389         }
390         
391         $syncrotonEmail = new Syncroton_Model_Email();
392         
393         foreach ($this->_mapping as $syncrotonProperty => $tine20Property) {
394             if (empty($entry->$tine20Property) && $entry->$tine20Property != '0' || count($entry->$tine20Property) === 0) {
395                 continue;
396             }
397         
398             switch($tine20Property) {
399                 case 'from_email':
400                     $syncrotonEmail->$syncrotonProperty = $this->_createEmailAddress($entry->from_name, $entry->from_email);
401                     
402                     break;
403                     
404                 case 'to':
405                 case 'cc':
406                     $syncrotonEmail->$syncrotonProperty = implode(', ', $entry->$tine20Property);
407                     
408                     break;
409                     
410                 default:
411                     $syncrotonEmail->$syncrotonProperty = $entry->$tine20Property;
412                     break;
413             }
414         }
415         
416         $syncrotonEmail->body = $this->_getSyncrotonBody($entry, $options);
417         if ($syncrotonEmail->body->type < 4) {
418             $syncrotonEmail->nativeBodyType = $syncrotonEmail->body->type;
419         }
420         
421         if ($syncrotonEmail->body->type == Syncroton_Command_Sync::BODY_TYPE_MIME) {
422             $syncrotonEmail->messageClass = 'IPM.Note.SMIME';
423         } else {
424             $syncrotonEmail->messageClass = 'IPM.Note';
425         }
426         
427         $syncrotonEmail->contentClass = 'urn:content-classes:message';
428         
429         // read flag
430         $syncrotonEmail->read = in_array(Zend_Mail_Storage::FLAG_SEEN, $entry->flags) ? 1 : 0;
431         
432         if (in_array(Zend_Mail_Storage::FLAG_ANSWERED, $entry->flags)) {
433             $syncrotonEmail->lastVerbExecuted = Syncroton_Model_Email::LASTVERB_REPLYTOSENDER;
434             $syncrotonEmail->lastVerbExecutionTime = new DateTime('now', new DateTimeZone('utc'));
435         #} elseif (in_array('\Forwarded', $entry->flags)) {
436         #    $syncrotonEmail->lastVerbExecuted = Syncroton_Model_Email::LASTVERB_FORWARD;
437         #    $syncrotonEmail->lastVerbExecutionTime = new DateTime('now', new DateTimeZone('utc'));
438         }
439         
440         $syncrotonEmail->flag = in_array('\Flagged', $entry->flags) ? 
441             new Syncroton_Model_EmailFlag(array(
442                 'status'       => Syncroton_Model_EmailFlag::STATUS_ACTIVE,
443                 'flagType'     => 'FollowUp',
444                 'reminderSet'  => 0,
445                 'startDate'    => Tinebase_DateTime::now(),
446                 'utcStartDate' => Tinebase_DateTime::now(),
447                 'dueDate'    => Tinebase_DateTime::now()->addWeek(1),
448                 'utcDueDate' => Tinebase_DateTime::now()->addWeek(1),
449             )) : 
450             new Syncroton_Model_EmailFlag(array(
451                 'status' => Syncroton_Model_EmailFlag::STATUS_CLEARED
452             ));
453         
454         // attachments?
455         if ($entry->has_attachment == true) {
456             $syncrotonAttachments = array();
457             
458             $attachments = $this->_contentController->getAttachments($entry);
459
460             if (count($attachments) > 0) {
461                 foreach ($attachments as $attachment) {
462                     $syncrotonAttachment = new Syncroton_Model_EmailAttachment(array(
463                         'displayName'       => trim($attachment['filename']),
464                         'fileReference'     => $entry->getId() . ActiveSync_Frontend_Abstract::LONGID_DELIMITER . $attachment['partId'],
465                         'method'            => 1,
466                         'estimatedDataSize' => $attachment['size']
467                     ));
468                     
469                     $syncrotonAttachments[] = $syncrotonAttachment;
470                 }
471             }
472             
473             $syncrotonEmail->attachments = $syncrotonAttachments;
474         }
475         
476         
477         #$syncrotonEmail->categories = array('Test');
478         $syncrotonEmail->conversationId    = $entry->getId();
479         $syncrotonEmail->conversationIndex = "\x00\x01\x02\x03\x04";
480         
481         return $syncrotonEmail;
482     }
483     
484     /**
485      * (non-PHPdoc)
486      * @see Syncroton_Data_IDataSearch::search()
487      */
488     public function search(Syncroton_Model_StoreRequest $store)
489     {
490         $storeResponse = new Syncroton_Model_StoreResponse();
491         
492         if (!isset($store->query['and']) || !isset($store->query['and']['freeText'])) {
493             $storeResponse->total = 0;
494             return $storeResponse;
495         }
496         
497         $filter = new $this->_contentFilterClass(array(array(
498             'field'     => 'query',
499             'operator'  => 'contains',
500             'value'     => $store->query['and']['freeText']
501         )));
502         
503         if (isset($store->query['and']['collections'])) {
504             // @todo search for multiple folders
505             $folderId = $store->query['and']['collections'][0];
506         } else {
507             $folderId = Felamimail_Controller_Folder::getInstance()->getByBackendAndGlobalName(
508                 Tinebase_Core::getPreference('Felamimail')->{Felamimail_Preference::DEFAULTACCOUNT},
509                 'INBOX'
510             )->getId();
511         }
512         $this->_addContainerFilter($filter, $folderId);
513         
514         if (isset($store->options['range'])) {
515             $pagination = new Tinebase_Model_Pagination(array(
516                 'start' => $store->options['range'][0],
517                 'limit' => $store->options['range'][1] - $store->options['range'][0]
518             ));
519         } else {
520             $pagination = null;
521         }
522         
523         $serverIds = $this->_contentController->search($filter, $pagination, false, true, 'sync');
524         $totalCount = $this->_contentController->searchCount($filter, 'sync');
525         
526         foreach ($serverIds as $serverId) {
527             $email = $this->getEntry(
528                 new Syncroton_Model_SyncCollection(array(
529                     'collectionId' => $folderId,
530                     'options'      => $store->options
531                 )), 
532                 $serverId
533             );
534     
535             $storeResponse->result[] = new Syncroton_Model_StoreResponseResult(array(
536                 'class'        => 'Email',
537                 'longId'       => $folderId . ActiveSync_Frontend_Abstract::LONGID_DELIMITER . $serverId,
538                 'collectionId' => $folderId,
539                 'properties'   => $email
540             ));
541         }
542         
543         $storeResponse->total = $totalCount;
544         if (count($storeResponse->result) > 0) {
545             $storeResponse->range = array($store->options['range'][0], $store->options['range'][1]);
546         }
547         
548         return $storeResponse;
549     }
550     
551     /**
552      * 
553      * @param Felamimail_Model_Message $entry
554      * @param array $options
555      * @return void|Syncroton_Model_EmailBody
556      */
557     protected function _getSyncrotonBody(Felamimail_Model_Message $entry, $options)
558     {
559         //var_dump($options);
560         
561         // get truncation
562         $truncateAt  = null;
563         $previewSize = null;
564         
565         if ($options['mimeSupport'] == Syncroton_Command_Sync::MIMESUPPORT_SEND_MIME) {
566             $airSyncBaseType = Syncroton_Command_Sync::BODY_TYPE_MIME;
567             
568             if (isset($options['bodyPreferences'][Syncroton_Command_Sync::BODY_TYPE_MIME]['truncationSize'])) {
569                 $truncateAt = $options['bodyPreferences'][Syncroton_Command_Sync::BODY_TYPE_MIME]['truncationSize'];
570             } elseif (isset($options['mimeTruncation']) && $options['mimeTruncation'] < Syncroton_Command_Sync::TRUNCATE_NOTHING) {
571                 switch($options['mimeTruncation']) {
572                     case Syncroton_Command_Sync::TRUNCATE_ALL:
573                         $truncateAt = 0;
574                         break;
575                     case Syncroton_Command_Sync::TRUNCATE_4096:
576                         $truncateAt = 4096;
577                         break;
578                     case Syncroton_Command_Sync::TRUNCATE_5120:
579                         $truncateAt = 5120;
580                         break;
581                     case Syncroton_Command_Sync::TRUNCATE_7168:
582                         $truncateAt = 7168;
583                         break;
584                     case Syncroton_Command_Sync::TRUNCATE_10240:
585                         $truncateAt = 10240;
586                         break;
587                     case Syncroton_Command_Sync::TRUNCATE_20480:
588                         $truncateAt = 20480;
589                         break;
590                     case Syncroton_Command_Sync::TRUNCATE_51200:
591                         $truncateAt = 51200;
592                         break;
593                     case Syncroton_Command_Sync::TRUNCATE_102400:
594                         $truncateAt = 102400;
595                         break;
596                 }
597             }
598             
599         } elseif (isset($options['bodyPreferences'][Syncroton_Command_Sync::BODY_TYPE_HTML])) {
600             $airSyncBaseType = Syncroton_Command_Sync::BODY_TYPE_HTML;
601             
602             if (isset($options['bodyPreferences'][Syncroton_Command_Sync::BODY_TYPE_HTML]['truncationSize'])) {
603                 $truncateAt = $options['bodyPreferences'][Syncroton_Command_Sync::BODY_TYPE_HTML]['truncationSize'];
604             }
605             if (isset($options['bodyPreferences'][Syncroton_Command_Sync::BODY_TYPE_HTML]['preview'])) {
606                 $previewSize = $options['bodyPreferences'][Syncroton_Command_Sync::BODY_TYPE_HTML]['preview'];
607             }
608             
609         } else {
610             $airSyncBaseType = Syncroton_Command_Sync::BODY_TYPE_PLAIN_TEXT;
611             
612             if (isset($options['bodyPreferences'][Syncroton_Command_Sync::BODY_TYPE_PLAIN_TEXT]['truncationSize'])) {
613                 $truncateAt = $options['bodyPreferences'][Syncroton_Command_Sync::BODY_TYPE_PLAIN_TEXT]['truncationSize'];
614             }
615             if (isset($options['bodyPreferences'][Syncroton_Command_Sync::BODY_TYPE_PLAIN_TEXT]['preview'])) {
616                 $previewSize = $options['bodyPreferences'][Syncroton_Command_Sync::BODY_TYPE_PLAIN_TEXT]['preview'];
617             }
618         }
619         
620         if ($airSyncBaseType == Syncroton_Command_Sync::BODY_TYPE_MIME) {
621             // getMessagePart will return Zend_Mime_Part
622             $messageBody = $this->_contentController->getMessagePart($entry);
623             $messageBody = stream_get_contents($messageBody->getRawStream());
624             
625         } else {
626             $messageBody = $this->_contentController->getMessageBody($entry, null, $airSyncBaseType == 2 ? Zend_Mime::TYPE_HTML : Zend_Mime::TYPE_TEXT, NULL, true);
627         }
628         
629         if($previewSize !== null) {
630             $preview = substr($this->_contentController->getMessageBody($entry, null, Zend_Mime::TYPE_TEXT, NULL, true), 0, $previewSize);
631             
632             // strip out any non utf-8 characters
633             $preview = @iconv('utf-8', 'utf-8//IGNORE', $preview);
634         }
635         
636         if($truncateAt !== null && strlen($messageBody) > $truncateAt) {
637             $messageBody  = substr($messageBody, 0, $truncateAt);
638             $isTruncacted = 1;
639         } else {
640             $isTruncacted = 0;
641         }
642         
643         // strip out any non utf-8 characters
644         $messageBody  = @iconv('utf-8', 'utf-8//IGNORE', $messageBody);
645         
646         $synrotonBody = new Syncroton_Model_EmailBody(array(
647             'type'              => $airSyncBaseType,
648             'estimatedDataSize' => $entry->size,
649         ));
650         
651         if (strlen($messageBody) > 0) {
652             $synrotonBody->data = $messageBody;
653         }
654         
655         if (isset($preview) && strlen($preview) > 0) {
656             $synrotonBody->preview = $preview;
657         }
658         
659         if ($isTruncacted === 1) {
660             $synrotonBody->truncated = 1;
661         } else {
662             $synrotonBody->truncated = 0;
663         }
664         
665         return $synrotonBody;
666     }
667     
668     /**
669      * (non-PHPdoc)
670      * @see ActiveSync_Frontend_Abstract::_inspectGetCountOfChanges()
671      */
672     protected function _inspectGetCountOfChanges(Syncroton_Backend_IContent $contentBackend, Syncroton_Model_IFolder $folder, Syncroton_Model_ISyncState $syncState)
673     {
674         Felamimail_Controller_Cache_Message::getInstance()->updateCache($folder->serverId, 10);
675     }
676     
677     /**
678      * delete entry
679      *
680      * @param  string  $_folderId
681      * @param  string  $_serverId
682      * @param  array   $_collectionData
683      */
684     public function deleteEntry($_folderId, $_serverId, $_collectionData)
685     {
686         Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " delete ColectionId: $_folderId Id: $_serverId");
687         
688         $folder  = Felamimail_Controller_Folder::getInstance()->get($_folderId);
689         $account = Felamimail_Controller_Account::getInstance()->get($folder->account_id);
690         
691         if ($_collectionData->deletesAsMoves === true && !empty($account->trash_folder)) {
692             // move message to trash folder
693             $trashFolder = Felamimail_Controller_Folder::getInstance()->getByBackendAndGlobalName($account, $account->trash_folder);
694             Felamimail_Controller_Message_Move::getInstance()->moveMessages($_serverId, $trashFolder);
695             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " moved entry $_serverId to trash folder");
696         } else {
697             // set delete flag
698             Felamimail_Controller_Message_Flags::getInstance()->addFlags($_serverId, Zend_Mail_Storage::FLAG_DELETED);
699             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " deleted entry " . $_serverId);
700         }
701     }
702     
703     /**
704      * (non-PHPdoc)
705      * @see ActiveSync_Frontend_Abstract::updateEntry()
706      */
707     public function updateEntry($folderId, $serverId, Syncroton_Model_IEntry $entry)
708     {
709         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(
710             __METHOD__ . '::' . __LINE__ . " CollectionId: $folderId Id: $serverId");
711         
712         try {
713             $message = $this->_contentController->get($serverId);
714         } catch (Tinebase_Exception_NotFound $tenf) {
715             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
716                 __METHOD__ . '::' . __LINE__ . ' ' . $tenf);
717             throw new Syncroton_Exception_NotFound($tenf->getMessage());
718         }
719         
720         if(isset($entry->read)) {
721             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
722                 __METHOD__ . '::' . __LINE__ . " CollectionId: $folderId Id: $serverId set read flag: $entry->read");
723             
724             if($entry->read == 1) {
725                 Felamimail_Controller_Message_Flags::getInstance()->addFlags($serverId, Zend_Mail_Storage::FLAG_SEEN);
726             } else {
727                 Felamimail_Controller_Message_Flags::getInstance()->clearFlags($serverId, Zend_Mail_Storage::FLAG_SEEN);
728             }
729         }
730         
731         if(isset($entry->flag)) {
732             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
733                 __METHOD__ . '::' . __LINE__ . " CollectionId: $folderId Id: $serverId set flagged flag: {$entry->flag->status}");
734             
735             if($entry->flag->status == Syncroton_Model_EmailFlag::STATUS_ACTIVE) {
736                 Felamimail_Controller_Message_Flags::getInstance()->addFlags($serverId, Zend_Mail_Storage::FLAG_FLAGGED);
737             } else {
738                 Felamimail_Controller_Message_Flags::getInstance()->clearFlags($serverId, Zend_Mail_Storage::FLAG_FLAGGED);
739             }
740         }
741         
742         $message->timestamp = $this->_syncTimeStamp;
743         $this->_contentController->update($message);
744         
745         return;
746     }
747     
748     /**
749      * (non-PHPdoc)
750      * @see Syncroton_Data_IData::updateFolder()
751      */
752     public function updateFolder(Syncroton_Model_IFolder $folder)
753     {
754         $fmailFolder = Felamimail_Controller_Folder::getInstance()->get($folder->serverId);
755         Felamimail_Controller_Folder::getInstance()->rename($fmailFolder->account_id, $folder->displayName, $fmailFolder->globalname);
756         return $folder;
757     }
758     
759     /**
760      * (non-PHPdoc)
761      * @see ActiveSync_Frontend_Abstract::toTineModel()
762      */
763     public function toTineModel(Syncroton_Model_IEntry $data, $entry = null)
764     {
765         // does nothing => you can't add emails via ActiveSync
766     }
767     
768     /**
769      * create rfc email address 
770      * 
771      * @param  string  $_realName
772      * @param  string  $_address
773      * @return string
774      */
775     protected function _createEmailAddress($_realName, $_address)
776     {
777         return !empty($_realName) ? sprintf('"%s" <%s>', str_replace('"', '\\"', $_realName), $_address) : $_address;
778     }
779     
780     /**
781      * return list of supported folders for this backend
782      *
783      * @return array
784      */
785     public function getAllFolders()
786     {
787         if (!Tinebase_Core::getUser()->hasRight('Felamimail', Tinebase_Acl_Rights::RUN)) {
788             // no folders
789             return array();
790         }
791         
792         $this->_updateFolderCache();
793         $result = $this->_getFolders();
794         
795         return $result;
796     }
797     
798     /**
799      * get felamimail account
800      * 
801      * @return Felamimail_Model_Account|NULL
802      */
803     protected function _getAccount()
804     {
805         if ($this->_account === NULL) {
806             $defaultAccountId = Tinebase_Core::getPreference('Felamimail')->{Felamimail_Preference::DEFAULTACCOUNT};
807             
808             if (empty($defaultAccountId)) {
809                 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(
810                     __METHOD__ . '::' . __LINE__ . " no default account set. Can't sync any folders.");
811                 return NULL;
812             }
813             
814             try {
815                 $this->_account = Felamimail_Controller_Account::getInstance()->get($defaultAccountId);
816             } catch (Tinebase_Exception_NotFound $ten) {
817                 return NULL;
818             }
819         }
820         
821         return $this->_account;
822     }
823     
824     /**
825      * update the folder cache
826      * 
827      * @throws Syncroton_Exception_Status_FolderSync
828      */
829     protected function _updateFolderCache()
830     {
831         $account = $this->_getAccount();
832         if (! $account) {
833             return;
834         }
835         
836         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) 
837             Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . " accountData " . print_r($account->toArray(), true));
838         
839         try {
840             Felamimail_Controller_Cache_Folder::getInstance()->update($account);
841             
842         } catch (Felamimail_Exception_IMAPServiceUnavailable $feisu) {
843             if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) Tinebase_Core::getLogger()->warn(
844                 __METHOD__ . '::' . __LINE__ . " Could not update folder cache: " . $feisu);
845             throw new Syncroton_Exception_Status_FolderSync(Syncroton_Exception_Status_FolderSync::FOLDER_SERVER_ERROR);
846             
847         } catch (Felamimail_Exception_IMAPInvalidCredentials $feiic) {
848             Tinebase_Exception::log($feiic, null, array(
849                 'accountname' => $account->name
850             ));
851         }
852     }
853     
854     /**
855      * get Syncroton_Model_Folder folders recursively by parentFolder
856      * 
857      * @param Felamimail_Model_Folder $parentFolder
858      * @param array $result
859      * @return array of Syncroton_Model_Folder
860      */
861     protected function _getFolders($parentFolder = NULL, &$result = array())
862     {
863         $globalname = ($parentFolder) ? $parentFolder->globalname : '';
864         $account = $this->_getAccount();
865         if (! $account) {
866             return array();
867         }
868         
869         $filter = new Felamimail_Model_FolderFilter(array(
870             array('field' => 'globalname', 'operator' => 'startswith',  'value' => $globalname),
871             array('field' => 'account_id', 'operator' => 'equals',      'value' => $account->getId()),
872         ));
873         
874         try {
875             $folders = Felamimail_Controller_Folder::getInstance()->search($filter);
876         } catch (Felamimail_Exception_IMAPInvalidCredentials $feiic) {
877             Tinebase_Exception::log($feiic, null, array(
878                 'accountname' => $account->name
879             ));
880             return array();
881         }
882         
883         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
884             . " Found " . count($folders) . ' subfolders of folder "' . $globalname . '"');
885         
886         foreach ($folders as $folder) {
887             $result[$folder->getId()] = new Syncroton_Model_Folder(array(
888                 'serverId'      => $folder->getId(),
889                 'parentId'      => ($parentFolder) ? $parentFolder->getId() : 0,
890                 'displayName'   => $folder->localname,
891                 'type'          => $this->_getFolderType($folder)
892             ));
893             
894             if ($folder->has_children) {
895                 $this->_getFolders($folder, $result);
896             }
897         }
898         
899         return $result;
900     }
901     
902     /**
903      * (non-PHPdoc)
904      * @see ActiveSync_Frontend_Abstract::moveItem()
905      */
906     public function moveItem($srcFolderId, $serverId, $dstFolderId)
907     {
908         $filter = new Felamimail_Model_MessageFilter(array(
909             array(
910                 'field'     => 'id',
911                 'operator'  => 'equals',
912                 'value'     => $serverId
913             )
914         ));
915         
916         Felamimail_Controller_Message_Move::getInstance()->moveMessages($filter, $dstFolderId);
917         
918         return $serverId;
919     }
920     
921     /**
922      * used by the mail backend only. Used to update the folder cache
923      * 
924      * @param  string  $_folderId
925      */
926     public function updateCache($_folderId)
927     {
928         try {
929             Felamimail_Controller_Cache_Message::getInstance()->updateCache($_folderId, 5);
930         } catch (Exception $e) {
931             if (Tinebase_Core::isLogLevel(Zend_Log::WARN))
932                 Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . " catched exception " . get_class($e));
933             if (Tinebase_Core::isLogLevel(Zend_Log::WARN))
934                 Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . " " . $e->getMessage());
935             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG))
936                 Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " " . $e->getTraceAsString());
937         }
938     }
939     
940     /**
941      * set activesync foldertype
942      * 
943      * @param Felamimail_Model_Folder $folder
944      */
945     protected function _getFolderType(Felamimail_Model_Folder $folder)
946     {
947         $personalNameSpaceSuffix = null;
948
949         $account = $this->_getAccount();
950         
951         // first lookup folder type by account settings ...
952         if ($account && !empty($account->ns_personal)) {
953             $personalNameSpaceSuffix = $account->ns_personal . $account->delimiter;
954         }
955         
956         switch (strtoupper($folder->localname)) {
957             case 'INBOX':
958                 if (($personalNameSpaceSuffix . 'INBOX' === $folder->globalname) ||
959                     (substr($personalNameSpaceSuffix, 0, 5) === 'INBOX' && $folder->globalname === 'INBOX') // Cyrus Prvate Namespace == 'INBOX.'
960                 ) {
961                     return Syncroton_Command_FolderSync::FOLDERTYPE_INBOX;
962                 }
963                 
964                 break;
965                 
966             case 'TRASH':
967                 // either use configured trash folder or detect by name
968                 if (($account && $account->trash_folder === $folder->globalname) ||
969                     ($personalNameSpaceSuffix . $folder->localname === $folder->globalname)
970                 ) {
971                     return Syncroton_Command_FolderSync::FOLDERTYPE_DELETEDITEMS;
972                 }
973                 
974                 break;
975                 
976             case 'SENT':
977                 // either use configured sent folder or detect by name
978                 if (($account && $account->sent_folder === $folder->globalname) ||
979                     ($personalNameSpaceSuffix . $folder->localname === $folder->globalname)
980                 ) {
981                     return Syncroton_Command_FolderSync::FOLDERTYPE_SENTMAIL;
982                 }
983                 
984                 break;
985                 
986             case 'DRAFTS':
987                 // either use configured drafts folder or detect by name
988                 if (($account && $account->drafts_folder === $folder->globalname) ||
989                     ($personalNameSpaceSuffix . $folder->localname === $folder->globalname)
990                 ) {
991                     return Syncroton_Command_FolderSync::FOLDERTYPE_DRAFTS;
992                 }
993                 
994                 break;
995         }
996         
997         return Syncroton_Command_FolderSync::FOLDERTYPE_MAIL_USER_CREATED;
998     }
999     
1000     /**
1001      * get folder identified by $_folderId
1002      *
1003      * @param string $_folderId
1004      * @return string
1005      */
1006     private function getFolder($_folderId)
1007     {
1008         $folders = $this->getSupportedFolders();
1009         
1010         if(!(isset($folders[$_folderId]) || array_key_exists($_folderId, $folders))) {
1011             throw new ActiveSync_Exception_FolderNotFound('folder not found. ' . $_folderId);
1012         }
1013         
1014         return $folders[$_folderId];
1015     }
1016     
1017     /**
1018      * (non-PHPdoc)
1019      * @see ActiveSync_Frontend_Abstract::_getContentFilter()
1020      */
1021     protected function _getContentFilter($_filterType)
1022     {
1023         $filter = parent::_getContentFilter($_filterType);
1024         
1025         if(in_array($_filterType, $this->_filterArray)) {
1026             $today = Tinebase_DateTime::now()->setTime(0,0,0);
1027                 
1028             switch($_filterType) {
1029                 case Syncroton_Command_Sync::FILTER_1_DAY_BACK:
1030                     $received = $today->subDay(1);
1031                     break;
1032                 case Syncroton_Command_Sync::FILTER_3_DAYS_BACK:
1033                     $received = $today->subDay(3);
1034                     break;
1035                 case Syncroton_Command_Sync::FILTER_1_WEEK_BACK:
1036                     $received = $today->subWeek(1);
1037                     break;
1038                 case Syncroton_Command_Sync::FILTER_2_WEEKS_BACK:
1039                     $received = $today->subWeek(2);
1040                     break;
1041                 case Syncroton_Command_Sync::FILTER_1_MONTH_BACK:
1042                     $received = $today->subMonth(1);
1043                     break;
1044             }
1045             
1046             // add period filter
1047             $filter->addFilter(new Tinebase_Model_Filter_DateTime('received', 'after', $received->get(Tinebase_Record_Abstract::ISO8601LONG)));
1048         }
1049         
1050         return $filter;
1051     }
1052     
1053     /**
1054      * (non-PHPdoc)
1055      * @see ActiveSync_Frontend_Abstract::_addContainerFilter()
1056      */
1057     protected function _addContainerFilter(Tinebase_Model_Filter_FilterGroup $_filter, $_containerId)
1058     {
1059         // custom filter gets added when created
1060         $_filter->createFilter(
1061             'account_id', 
1062             'equals', 
1063             Tinebase_Core::getPreference('Felamimail')->{Felamimail_Preference::DEFAULTACCOUNT}
1064         );
1065         
1066         $_filter->addFilter($_filter->createFilter(
1067             'folder_id', 
1068             'equals', 
1069             $_containerId
1070         ));
1071     }
1072
1073     /**
1074      * 
1075      * @return int     Syncroton_Command_Sync::FILTER...
1076      */
1077     public function getMaxFilterType()
1078     {
1079         return ActiveSync_Config::getInstance()->get(ActiveSync_Config::MAX_FILTER_TYPE_EMAIL);
1080     }
1081 }