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