dc9bd11e798ae36d73622e473031e4bf2e85606c
[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                 'sort' => $this->_sortField,
519                 'dir' => 'DESC',
520             ));
521         } else {
522             $pagination = new Tinebase_Model_Pagination(array(
523                 'sort' => $this->_sortField,
524                 'dir' => 'DESC',
525             ));
526         }
527         
528         $serverIds = $this->_contentController->search($filter, $pagination, false, true, 'sync');
529         $totalCount = $this->_contentController->searchCount($filter, 'sync');
530         
531         foreach ($serverIds as $serverId) {
532             $email = $this->getEntry(
533                 new Syncroton_Model_SyncCollection(array(
534                     'collectionId' => $folderId,
535                     'options'      => $store->options
536                 )), 
537                 $serverId
538             );
539     
540             $storeResponse->result[] = new Syncroton_Model_StoreResponseResult(array(
541                 'class'        => 'Email',
542                 'longId'       => $folderId . ActiveSync_Frontend_Abstract::LONGID_DELIMITER . $serverId,
543                 'collectionId' => $folderId,
544                 'properties'   => $email
545             ));
546         }
547         
548         $storeResponse->total = $totalCount;
549         if (count($storeResponse->result) > 0) {
550             $storeResponse->range = array($store->options['range'][0], $store->options['range'][1]);
551         }
552         
553         return $storeResponse;
554     }
555     
556     /**
557      * 
558      * @param Felamimail_Model_Message $entry
559      * @param array $options
560      * @return void|Syncroton_Model_EmailBody
561      */
562     protected function _getSyncrotonBody(Felamimail_Model_Message $entry, $options)
563     {
564         //var_dump($options);
565         
566         // get truncation
567         $truncateAt  = null;
568         $previewSize = null;
569         
570         if ($options['mimeSupport'] == Syncroton_Command_Sync::MIMESUPPORT_SEND_MIME) {
571             $airSyncBaseType = Syncroton_Command_Sync::BODY_TYPE_MIME;
572             
573             if (isset($options['bodyPreferences'][Syncroton_Command_Sync::BODY_TYPE_MIME]['truncationSize'])) {
574                 $truncateAt = $options['bodyPreferences'][Syncroton_Command_Sync::BODY_TYPE_MIME]['truncationSize'];
575             } elseif (isset($options['mimeTruncation']) && $options['mimeTruncation'] < Syncroton_Command_Sync::TRUNCATE_NOTHING) {
576                 switch($options['mimeTruncation']) {
577                     case Syncroton_Command_Sync::TRUNCATE_ALL:
578                         $truncateAt = 0;
579                         break;
580                     case Syncroton_Command_Sync::TRUNCATE_4096:
581                         $truncateAt = 4096;
582                         break;
583                     case Syncroton_Command_Sync::TRUNCATE_5120:
584                         $truncateAt = 5120;
585                         break;
586                     case Syncroton_Command_Sync::TRUNCATE_7168:
587                         $truncateAt = 7168;
588                         break;
589                     case Syncroton_Command_Sync::TRUNCATE_10240:
590                         $truncateAt = 10240;
591                         break;
592                     case Syncroton_Command_Sync::TRUNCATE_20480:
593                         $truncateAt = 20480;
594                         break;
595                     case Syncroton_Command_Sync::TRUNCATE_51200:
596                         $truncateAt = 51200;
597                         break;
598                     case Syncroton_Command_Sync::TRUNCATE_102400:
599                         $truncateAt = 102400;
600                         break;
601                 }
602             }
603             
604         } elseif (isset($options['bodyPreferences'][Syncroton_Command_Sync::BODY_TYPE_HTML])) {
605             $airSyncBaseType = Syncroton_Command_Sync::BODY_TYPE_HTML;
606             
607             if (isset($options['bodyPreferences'][Syncroton_Command_Sync::BODY_TYPE_HTML]['truncationSize'])) {
608                 $truncateAt = $options['bodyPreferences'][Syncroton_Command_Sync::BODY_TYPE_HTML]['truncationSize'];
609             }
610             if (isset($options['bodyPreferences'][Syncroton_Command_Sync::BODY_TYPE_HTML]['preview'])) {
611                 $previewSize = $options['bodyPreferences'][Syncroton_Command_Sync::BODY_TYPE_HTML]['preview'];
612             }
613             
614         } else {
615             $airSyncBaseType = Syncroton_Command_Sync::BODY_TYPE_PLAIN_TEXT;
616             
617             if (isset($options['bodyPreferences'][Syncroton_Command_Sync::BODY_TYPE_PLAIN_TEXT]['truncationSize'])) {
618                 $truncateAt = $options['bodyPreferences'][Syncroton_Command_Sync::BODY_TYPE_PLAIN_TEXT]['truncationSize'];
619             }
620             if (isset($options['bodyPreferences'][Syncroton_Command_Sync::BODY_TYPE_PLAIN_TEXT]['preview'])) {
621                 $previewSize = $options['bodyPreferences'][Syncroton_Command_Sync::BODY_TYPE_PLAIN_TEXT]['preview'];
622             }
623         }
624         
625         if ($airSyncBaseType == Syncroton_Command_Sync::BODY_TYPE_MIME) {
626             // getMessagePart will return Zend_Mime_Part
627             $messageBody = $this->_contentController->getMessagePart($entry);
628             $messageBody = stream_get_contents($messageBody->getRawStream());
629             
630         } else {
631             $messageBody = $this->_contentController->getMessageBody($entry, null, $airSyncBaseType == 2 ? Zend_Mime::TYPE_HTML : Zend_Mime::TYPE_TEXT, NULL, true);
632         }
633         
634         if($previewSize !== null) {
635             $preview = substr($this->_contentController->getMessageBody($entry, null, Zend_Mime::TYPE_TEXT, NULL, true), 0, $previewSize);
636             
637             // strip out any non utf-8 characters
638             $preview = @iconv('utf-8', 'utf-8//IGNORE', $preview);
639         }
640         
641         if($truncateAt !== null && strlen($messageBody) > $truncateAt) {
642             $messageBody  = substr($messageBody, 0, $truncateAt);
643             $isTruncacted = 1;
644         } else {
645             $isTruncacted = 0;
646         }
647         
648         // strip out any non utf-8 characters
649         $messageBody  = @iconv('utf-8', 'utf-8//IGNORE', $messageBody);
650         
651         $synrotonBody = new Syncroton_Model_EmailBody(array(
652             'type'              => $airSyncBaseType,
653             'estimatedDataSize' => $entry->size,
654         ));
655         
656         if (strlen($messageBody) > 0) {
657             $synrotonBody->data = $messageBody;
658         }
659         
660         if (isset($preview) && strlen($preview) > 0) {
661             $synrotonBody->preview = $preview;
662         }
663         
664         if ($isTruncacted === 1) {
665             $synrotonBody->truncated = 1;
666         } else {
667             $synrotonBody->truncated = 0;
668         }
669         
670         return $synrotonBody;
671     }
672     
673     /**
674      * (non-PHPdoc)
675      * @see ActiveSync_Frontend_Abstract::_inspectGetCountOfChanges()
676      */
677     protected function _inspectGetCountOfChanges(Syncroton_Backend_IContent $contentBackend, Syncroton_Model_IFolder $folder, Syncroton_Model_ISyncState $syncState)
678     {
679         Felamimail_Controller_Cache_Message::getInstance()->updateCache($folder->serverId, 10);
680     }
681     
682     /**
683      * delete entry
684      *
685      * @param  string  $_folderId
686      * @param  string  $_serverId
687      * @param  array   $_collectionData
688      */
689     public function deleteEntry($_folderId, $_serverId, $_collectionData)
690     {
691         Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " delete ColectionId: $_folderId Id: $_serverId");
692         
693         $folder  = Felamimail_Controller_Folder::getInstance()->get($_folderId);
694         $account = Felamimail_Controller_Account::getInstance()->get($folder->account_id);
695         
696         if ($_collectionData->deletesAsMoves === true && !empty($account->trash_folder)) {
697             // move message to trash folder
698             $trashFolder = Felamimail_Controller_Folder::getInstance()->getByBackendAndGlobalName($account, $account->trash_folder);
699             Felamimail_Controller_Message_Move::getInstance()->moveMessages($_serverId, $trashFolder);
700             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " moved entry $_serverId to trash folder");
701         } else {
702             // set delete flag
703             Felamimail_Controller_Message_Flags::getInstance()->addFlags($_serverId, Zend_Mail_Storage::FLAG_DELETED);
704             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " deleted entry " . $_serverId);
705         }
706     }
707     
708     /**
709      * (non-PHPdoc)
710      * @see ActiveSync_Frontend_Abstract::updateEntry()
711      */
712     public function updateEntry($folderId, $serverId, Syncroton_Model_IEntry $entry)
713     {
714         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(
715             __METHOD__ . '::' . __LINE__ . " CollectionId: $folderId Id: $serverId");
716         
717         try {
718             $message = $this->_contentController->get($serverId);
719         } catch (Tinebase_Exception_NotFound $tenf) {
720             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
721                 __METHOD__ . '::' . __LINE__ . ' ' . $tenf);
722             throw new Syncroton_Exception_NotFound($tenf->getMessage());
723         }
724         
725         if(isset($entry->read)) {
726             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
727                 __METHOD__ . '::' . __LINE__ . " CollectionId: $folderId Id: $serverId set read flag: $entry->read");
728             
729             if($entry->read == 1) {
730                 Felamimail_Controller_Message_Flags::getInstance()->addFlags($serverId, Zend_Mail_Storage::FLAG_SEEN);
731             } else {
732                 Felamimail_Controller_Message_Flags::getInstance()->clearFlags($serverId, Zend_Mail_Storage::FLAG_SEEN);
733             }
734         }
735         
736         if(isset($entry->flag)) {
737             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
738                 __METHOD__ . '::' . __LINE__ . " CollectionId: $folderId Id: $serverId set flagged flag: {$entry->flag->status}");
739             
740             if($entry->flag->status == Syncroton_Model_EmailFlag::STATUS_ACTIVE) {
741                 Felamimail_Controller_Message_Flags::getInstance()->addFlags($serverId, Zend_Mail_Storage::FLAG_FLAGGED);
742             } else {
743                 Felamimail_Controller_Message_Flags::getInstance()->clearFlags($serverId, Zend_Mail_Storage::FLAG_FLAGGED);
744             }
745         }
746         
747         $message->timestamp = $this->_syncTimeStamp;
748         $this->_contentController->update($message);
749         
750         return;
751     }
752     
753     /**
754      * (non-PHPdoc)
755      * @see Syncroton_Data_IData::updateFolder()
756      */
757     public function updateFolder(Syncroton_Model_IFolder $folder)
758     {
759         $fmailFolder = Felamimail_Controller_Folder::getInstance()->get($folder->serverId);
760         Felamimail_Controller_Folder::getInstance()->rename($fmailFolder->account_id, $folder->displayName, $fmailFolder->globalname);
761         return $folder;
762     }
763     
764     /**
765      * (non-PHPdoc)
766      * @see ActiveSync_Frontend_Abstract::toTineModel()
767      */
768     public function toTineModel(Syncroton_Model_IEntry $data, $entry = null)
769     {
770         // does nothing => you can't add emails via ActiveSync
771     }
772     
773     /**
774      * create rfc email address 
775      * 
776      * @param  string  $_realName
777      * @param  string  $_address
778      * @return string
779      */
780     protected function _createEmailAddress($_realName, $_address)
781     {
782         return !empty($_realName) ? sprintf('"%s" <%s>', str_replace('"', '\\"', $_realName), $_address) : $_address;
783     }
784     
785     /**
786      * return list of supported folders for this backend
787      *
788      * @return array
789      */
790     public function getAllFolders()
791     {
792         if (!Tinebase_Core::getUser()->hasRight('Felamimail', Tinebase_Acl_Rights::RUN)) {
793             // no folders
794             return array();
795         }
796         
797         $this->_updateFolderCache();
798         $result = $this->_getFolders();
799         
800         return $result;
801     }
802     
803     /**
804      * get felamimail account
805      * 
806      * @return Felamimail_Model_Account|NULL
807      */
808     protected function _getAccount()
809     {
810         if ($this->_account === NULL) {
811             $defaultAccountId = Tinebase_Core::getPreference('Felamimail')->{Felamimail_Preference::DEFAULTACCOUNT};
812             
813             if (empty($defaultAccountId)) {
814                 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(
815                     __METHOD__ . '::' . __LINE__ . " no default account set. Can't sync any folders.");
816                 return NULL;
817             }
818             
819             try {
820                 $this->_account = Felamimail_Controller_Account::getInstance()->get($defaultAccountId);
821             } catch (Tinebase_Exception_NotFound $ten) {
822                 return NULL;
823             }
824         }
825         
826         return $this->_account;
827     }
828     
829     /**
830      * update the folder cache
831      * 
832      * @throws Syncroton_Exception_Status_FolderSync
833      */
834     protected function _updateFolderCache()
835     {
836         $account = $this->_getAccount();
837         if (! $account) {
838             return;
839         }
840         
841         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) 
842             Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . " accountData " . print_r($account->toArray(), true));
843         
844         try {
845             Felamimail_Controller_Cache_Folder::getInstance()->update($account);
846             
847         } catch (Felamimail_Exception_IMAPServiceUnavailable $feisu) {
848             if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) Tinebase_Core::getLogger()->warn(
849                 __METHOD__ . '::' . __LINE__ . " Could not update folder cache: " . $feisu);
850             throw new Syncroton_Exception_Status_FolderSync(Syncroton_Exception_Status_FolderSync::FOLDER_SERVER_ERROR);
851             
852         } catch (Felamimail_Exception_IMAPInvalidCredentials $feiic) {
853             Tinebase_Exception::log($feiic, null, array(
854                 'accountname' => $account->name
855             ));
856         }
857     }
858     
859     /**
860      * get Syncroton_Model_Folder folders recursively by parentFolder
861      * 
862      * @param Felamimail_Model_Folder $parentFolder
863      * @param array $result
864      * @return array of Syncroton_Model_Folder
865      */
866     protected function _getFolders($parentFolder = NULL, &$result = array())
867     {
868         $globalname = ($parentFolder) ? $parentFolder->globalname : '';
869         $account = $this->_getAccount();
870         if (! $account) {
871             return array();
872         }
873         
874         $filter = new Felamimail_Model_FolderFilter(array(
875             array('field' => 'globalname', 'operator' => 'startswith',  'value' => $globalname),
876             array('field' => 'account_id', 'operator' => 'equals',      'value' => $account->getId()),
877         ));
878         
879         try {
880             $folders = Felamimail_Controller_Folder::getInstance()->search($filter);
881         } catch (Felamimail_Exception_IMAPInvalidCredentials $feiic) {
882             Tinebase_Exception::log($feiic, null, array(
883                 'accountname' => $account->name
884             ));
885             return array();
886         }
887         
888         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
889             . " Found " . count($folders) . ' subfolders of folder "' . $globalname . '"');
890         
891         foreach ($folders as $folder) {
892             $result[$folder->getId()] = new Syncroton_Model_Folder(array(
893                 'serverId'      => $folder->getId(),
894                 'parentId'      => ($parentFolder) ? $parentFolder->getId() : 0,
895                 'displayName'   => $folder->localname,
896                 'type'          => $this->_getFolderType($folder)
897             ));
898             
899             if ($folder->has_children) {
900                 $this->_getFolders($folder, $result);
901             }
902         }
903         
904         return $result;
905     }
906     
907     /**
908      * (non-PHPdoc)
909      * @see ActiveSync_Frontend_Abstract::moveItem()
910      */
911     public function moveItem($srcFolderId, $serverId, $dstFolderId)
912     {
913         $filter = new Felamimail_Model_MessageFilter(array(
914             array(
915                 'field'     => 'id',
916                 'operator'  => 'equals',
917                 'value'     => $serverId
918             )
919         ));
920         
921         Felamimail_Controller_Message_Move::getInstance()->moveMessages($filter, $dstFolderId);
922         
923         return $serverId;
924     }
925     
926     /**
927      * used by the mail backend only. Used to update the folder cache
928      * 
929      * @param  string  $_folderId
930      */
931     public function updateCache($_folderId)
932     {
933         try {
934             Felamimail_Controller_Cache_Message::getInstance()->updateCache($_folderId, 5);
935         } catch (Exception $e) {
936             if (Tinebase_Core::isLogLevel(Zend_Log::WARN))
937                 Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . " catched exception " . get_class($e));
938             if (Tinebase_Core::isLogLevel(Zend_Log::WARN))
939                 Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . " " . $e->getMessage());
940             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG))
941                 Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " " . $e->getTraceAsString());
942         }
943     }
944     
945     /**
946      * set activesync foldertype
947      * 
948      * @param Felamimail_Model_Folder $folder
949      */
950     protected function _getFolderType(Felamimail_Model_Folder $folder)
951     {
952         $personalNameSpaceSuffix = null;
953
954         $account = $this->_getAccount();
955         
956         // first lookup folder type by account settings ...
957         if ($account && !empty($account->ns_personal)) {
958             $personalNameSpaceSuffix = $account->ns_personal . $account->delimiter;
959         }
960         
961         switch (strtoupper($folder->localname)) {
962             case 'INBOX':
963                 if (($personalNameSpaceSuffix . 'INBOX' === $folder->globalname) ||
964                     (substr($personalNameSpaceSuffix, 0, 5) === 'INBOX' && $folder->globalname === 'INBOX') // Cyrus Prvate Namespace == 'INBOX.'
965                 ) {
966                     return Syncroton_Command_FolderSync::FOLDERTYPE_INBOX;
967                 }
968                 
969                 break;
970                 
971             case 'TRASH':
972                 // either use configured trash folder or detect by name
973                 if (($account && $account->trash_folder === $folder->globalname) ||
974                     ($personalNameSpaceSuffix . $folder->localname === $folder->globalname)
975                 ) {
976                     return Syncroton_Command_FolderSync::FOLDERTYPE_DELETEDITEMS;
977                 }
978                 
979                 break;
980                 
981             case 'SENT':
982                 // either use configured sent folder or detect by name
983                 if (($account && $account->sent_folder === $folder->globalname) ||
984                     ($personalNameSpaceSuffix . $folder->localname === $folder->globalname)
985                 ) {
986                     return Syncroton_Command_FolderSync::FOLDERTYPE_SENTMAIL;
987                 }
988                 
989                 break;
990                 
991             case 'DRAFTS':
992                 // either use configured drafts folder or detect by name
993                 if (($account && $account->drafts_folder === $folder->globalname) ||
994                     ($personalNameSpaceSuffix . $folder->localname === $folder->globalname)
995                 ) {
996                     return Syncroton_Command_FolderSync::FOLDERTYPE_DRAFTS;
997                 }
998                 
999                 break;
1000         }
1001         
1002         return Syncroton_Command_FolderSync::FOLDERTYPE_MAIL_USER_CREATED;
1003     }
1004     
1005     /**
1006      * get folder identified by $_folderId
1007      *
1008      * @param string $_folderId
1009      * @return string
1010      */
1011     private function getFolder($_folderId)
1012     {
1013         $folders = $this->getSupportedFolders();
1014         
1015         if(!(isset($folders[$_folderId]) || array_key_exists($_folderId, $folders))) {
1016             throw new ActiveSync_Exception_FolderNotFound('folder not found. ' . $_folderId);
1017         }
1018         
1019         return $folders[$_folderId];
1020     }
1021     
1022     /**
1023      * (non-PHPdoc)
1024      * @see ActiveSync_Frontend_Abstract::_getContentFilter()
1025      */
1026     protected function _getContentFilter($_filterType)
1027     {
1028         $filter = parent::_getContentFilter($_filterType);
1029         
1030         if(in_array($_filterType, $this->_filterArray)) {
1031             $today = Tinebase_DateTime::now()->setTime(0,0,0);
1032                 
1033             switch($_filterType) {
1034                 case Syncroton_Command_Sync::FILTER_1_DAY_BACK:
1035                     $received = $today->subDay(1);
1036                     break;
1037                 case Syncroton_Command_Sync::FILTER_3_DAYS_BACK:
1038                     $received = $today->subDay(3);
1039                     break;
1040                 case Syncroton_Command_Sync::FILTER_1_WEEK_BACK:
1041                     $received = $today->subWeek(1);
1042                     break;
1043                 case Syncroton_Command_Sync::FILTER_2_WEEKS_BACK:
1044                     $received = $today->subWeek(2);
1045                     break;
1046                 case Syncroton_Command_Sync::FILTER_1_MONTH_BACK:
1047                     $received = $today->subMonth(1);
1048                     break;
1049             }
1050             
1051             // add period filter
1052             $filter->addFilter(new Tinebase_Model_Filter_DateTime('received', 'after', $received->get(Tinebase_Record_Abstract::ISO8601LONG)));
1053         }
1054         
1055         return $filter;
1056     }
1057     
1058     /**
1059      * (non-PHPdoc)
1060      * @see ActiveSync_Frontend_Abstract::_addContainerFilter()
1061      */
1062     protected function _addContainerFilter(Tinebase_Model_Filter_FilterGroup $_filter, $_containerId)
1063     {
1064         // custom filter gets added when created
1065         $_filter->createFilter(
1066             'account_id', 
1067             'equals', 
1068             Tinebase_Core::getPreference('Felamimail')->{Felamimail_Preference::DEFAULTACCOUNT}
1069         );
1070         
1071         $_filter->addFilter($_filter->createFilter(
1072             'folder_id', 
1073             'equals', 
1074             $_containerId
1075         ));
1076     }
1077
1078     /**
1079      * 
1080      * @return int     Syncroton_Command_Sync::FILTER...
1081      */
1082     public function getMaxFilterType()
1083     {
1084         return ActiveSync_Config::getInstance()->get(ActiveSync_Config::MAX_FILTER_TYPE_EMAIL);
1085     }
1086 }