0012950: More attachment methods for mail
[tine20] / tests / tine20 / Felamimail / Frontend / JsonTest.php
1 <?php
2
3 use Sabre\DAV;
4
5 /**
6  * Tine 2.0 - http://www.tine20.org
7  *
8  * @package     Felamimail
9  * @license     http://www.gnu.org/licenses/agpl.html
10  * @copyright   Copyright (c) 2009-2017 Metaways Infosystems GmbH (http://www.metaways.de)
11  * @author      Philipp Schüle <p.schuele@metaways.de>
12  */
13
14 /**
15  * Test class for Tinebase_Group
16  */
17 class Felamimail_Frontend_JsonTest extends TestCase
18 {
19     use GetProtectedMethodTrait;
20
21     /**
22      * @var Felamimail_Frontend_Json
23      */
24     protected $_json = array();
25
26     /**
27      * message ids to delete
28      *
29      * @var array
30      */
31     protected $_messageIds = array();
32     
33     /**
34      * @var Felamimail_Model_Account
35      */
36     protected $_account = NULL;
37     
38     /**
39      * imap backend
40
41      * @var Felamimail_Backend_ImapProxy
42      */
43     protected $_imap = NULL;
44     
45     /**
46      * name of the folder to use for tests
47      * @var string
48      */
49     protected $_testFolderName = 'Junk';
50     
51     /**
52      * folders to delete in tearDown()
53      * 
54      * @var array
55      */
56     protected $_createdFolders = array();
57
58     /**
59      * are there messages to delete?
60      * 
61      * @var array
62      */
63     protected $_foldersToClear = array();
64
65     /**
66      * active sieve script name to be restored
67      *
68      * @var string
69      */
70     protected $_oldActiveSieveScriptName = NULL;
71
72     /**
73      * was sieve_vacation_active ?
74      *
75      * @var boolean
76      */
77     protected $_oldSieveVacationActiveState = FALSE;
78     
79     /**
80      * old sieve data
81      *
82      * @var Felamimail_Sieve_Backend_Sql
83      */
84     protected $_oldSieveData = NULL;
85
86     /**
87      * sieve script name to delete
88      *
89      * @var string
90      */
91     protected $_testSieveScriptName = NULL;
92
93     /**
94      * sieve vacation template file name
95      *
96      * @var string
97      */
98     protected $_sieveVacationTemplateFile = 'vacation_template.tpl';
99
100     /**
101      * test email domain
102      *
103      * @var string
104      */
105     protected $_mailDomain = 'tine20.org';
106
107     /**
108      * @var Felamimail_Model_Folder
109      */
110     protected $_folder = NULL;
111
112     /**
113      * paths in the vfs to delete
114      *
115      * @var array
116      */
117     protected $_pathsToDelete = array();
118
119     /**
120      *
121      * @var Tinebase_Frontend_Json
122      */
123     protected $_frontend = NULL;
124     
125     /**
126      * Sets up the fixture.
127      * This method is called before a test is executed.
128      *
129      * @access protected
130      */
131     protected function setUp()
132     {
133         Tinebase_TransactionManager::getInstance()->startTransaction(Tinebase_Core::getDb());
134         
135         // get (or create) test accout
136         $this->_account = Felamimail_Controller_Account::getInstance()->search()->getFirstRecord();
137         if ($this->_account === null) {
138             $this->markTestSkipped('no account found');
139         }
140         $this->_oldSieveVacationActiveState = $this->_account->sieve_vacation_active;
141         try {
142             $this->_oldSieveData = new Felamimail_Sieve_Backend_Sql($this->_account);
143         } catch (Tinebase_Exception_NotFound $tenf) {
144             // do nothing
145         }
146         
147         $this->_json = new Felamimail_Frontend_Json();
148         $this->_imap = Felamimail_Backend_ImapFactory::factory($this->_account);
149         
150         foreach (array($this->_testFolderName, $this->_account->sent_folder, $this->_account->trash_folder) as $folderToCreate) {
151             // create folder if it does not exist
152             $this->_getFolder($folderToCreate);
153         }
154         
155         $this->_mailDomain = TestServer::getPrimaryMailDomain();
156
157         $this->_frontend = new Tinebase_Frontend_Json();
158     }
159
160     /**
161      * Tears down the fixture
162      * This method is called after a test is executed.
163      *
164      * @access protected
165      */
166     protected function tearDown()
167     {
168         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
169             . ' Tearing down ...');
170         
171         if (count($this->_createdFolders) > 0) {
172             foreach ($this->_createdFolders as $folderName) {
173                 //echo "delete $folderName\n";
174                 try {
175                     $this->_imap->removeFolder(Felamimail_Model_Folder::encodeFolderName($folderName));
176                 } catch (Zend_Mail_Storage_Exception $zmse) {
177                     // already deleted
178                 }
179             }
180             Felamimail_Controller_Cache_Folder::getInstance()->clear($this->_account);
181         }
182         
183         if (! empty($this->_foldersToClear)) {
184             foreach ($this->_foldersToClear as $folderName) {
185                 // delete test messages from given folders on imap server (search by special header)
186                 $this->_imap->selectFolder($folderName);
187                 $result = $this->_imap->search(array(
188                     'HEADER X-Tine20TestMessage jsontest'
189                 ));
190                 //print_r($result);
191                 foreach ($result as $messageUid) {
192                     $this->_imap->removeMessage($messageUid);
193                 }
194                 
195                 // clear message cache
196                 $folder = Felamimail_Controller_Folder::getInstance()->getByBackendAndGlobalName($this->_account->getId(), $folderName);
197                 Felamimail_Controller_Cache_Message::getInstance()->clear($folder);
198             }
199         }
200         
201         // sieve cleanup
202         if ($this->_testSieveScriptName !== NULL) {
203             Felamimail_Controller_Sieve::getInstance()->setScriptName($this->_testSieveScriptName);
204             try {
205                 Felamimail_Controller_Sieve::getInstance()->deleteScript($this->_account->getId());
206             } catch (Zend_Mail_Protocol_Exception $zmpe) {
207                 // do not delete script if active
208             }
209             Felamimail_Controller_Account::getInstance()->setVacationActive($this->_account, $this->_oldSieveVacationActiveState);
210             
211             if ($this->_oldSieveData !== NULL) {
212                 $this->_oldSieveData->save();
213             }
214         }
215         if ($this->_oldActiveSieveScriptName !== NULL) {
216             Felamimail_Controller_Sieve::getInstance()->setScriptName($this->_oldActiveSieveScriptName);
217             Felamimail_Controller_Sieve::getInstance()->activateScript($this->_account->getId());
218         }
219         
220         // vfs cleanup
221         foreach ($this->_pathsToDelete as $path) {
222             $webdavRoot = new DAV\ObjectTree(new Tinebase_WebDav_Root());
223             //echo "delete $path";
224             $webdavRoot->delete($path);
225         }
226         
227         Tinebase_TransactionManager::getInstance()->rollBack();
228
229         // needed to clear cache of containers
230         Tinebase_Container::getInstance()->resetClassCache();
231     }
232
233     /************************ test functions *********************************/
234     
235     /*********************** folder tests ****************************/
236     
237     /**
238      * test search folders (check order of folders as well)
239      */
240     public function testSearchFolders()
241     {
242         $filter = $this->_getFolderFilter();
243         $result = $this->_json->searchFolders($filter);
244         
245         $this->assertGreaterThan(1, $result['totalcount']);
246         $expectedFolders = array('INBOX', $this->_testFolderName, $this->_account->trash_folder, $this->_account->sent_folder);
247         
248         $foundCount = 0;
249         foreach ($result['results'] as $index => $folder) {
250             if (in_array($folder['localname'], $expectedFolders)) {
251                 $foundCount++;
252             }
253         }
254         $this->assertEquals(count($expectedFolders), $foundCount);
255     }
256     
257     /**
258      * clear test folder
259      */
260     public function testClearFolder()
261     {
262         $folderName = $this->_testFolderName;
263         $folder = $this->_getFolder($this->_testFolderName);
264         $folder = Felamimail_Controller_Folder::getInstance()->emptyFolder($folder->getId());
265
266         $filter = $this->_getMessageFilter($folder->getId());
267         $result = $this->_json->searchMessages($filter, '');
268         
269         $this->assertEquals(0, $result['totalcount'], 'Found too many messages in folder ' . $this->_testFolderName);
270         $this->assertEquals(0, $folder->cache_totalcount);
271     }
272
273     /**
274      * try to create some folders
275      */
276     public function testCreateFolders()
277     {
278         $filter = $this->_getFolderFilter();
279         $result = $this->_json->searchFolders($filter);
280         
281         $foldernames = array('test' => 'test', 'Schlüssel' => 'Schlüssel', 'test//1' => 'test1', 'test\2' => 'test2');
282         
283         foreach ($foldernames as $foldername => $expected) {
284             $result = $this->_json->addFolder($foldername, $this->_testFolderName, $this->_account->getId());
285             $globalname = $this->_testFolderName . $this->_account->delimiter . $expected;
286             $this->_createdFolders[] = $globalname;
287             $this->assertEquals($expected, $result['localname']);
288             $this->assertEquals($globalname, $result['globalname']);
289             $this->assertEquals(Felamimail_Model_Folder::CACHE_STATUS_EMPTY, $result['cache_status']);
290         }
291     }
292     
293     /**
294      * test emtpy folder (with subfolder)
295      */
296     public function testEmptyFolderWithSubfolder()
297     {
298         $folderName = $this->_testFolderName;
299         $folder = $this->_getFolder($this->_testFolderName);
300         $this->testCreateFolders();
301         
302         $folderArray = $this->_json->emptyFolder($folder->getId());
303         $this->assertEquals(0, $folderArray['has_children']);
304         
305         $result = $this->_json->updateFolderCache($this->_account->getId(), $this->_testFolderName);
306         $this->assertEquals(0, count($result));
307     }
308     
309     /**
310      * testUpdateFolderCache
311      */
312     public function testUpdateFolderCache()
313     {
314         $result = $this->_json->updateFolderCache($this->_account->getId(), '');
315         
316         // create folders directly on imap server
317         $this->_imap->createFolder('test', $this->_testFolderName, $this->_account->delimiter);
318         $this->_imap->createFolder('testsub', $this->_testFolderName . $this->_account->delimiter . 'test', $this->_account->delimiter);
319         // if something goes wrong, we need to delete these folders in tearDown
320         $this->_createdFolders[] = $this->_testFolderName . $this->_account->delimiter . 'test' . $this->_account->delimiter . 'testsub';
321         $this->_createdFolders[] = $this->_testFolderName . $this->_account->delimiter . 'test';
322         
323         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
324             . ' Update cache and check if folder is found');
325         
326         $result = $this->_json->updateFolderCache($this->_account->getId(), $this->_testFolderName);
327         $testfolder = $result[0];
328         $this->assertGreaterThan(0, count($result));
329         $this->assertEquals($this->_testFolderName . $this->_account->delimiter . 'test', $testfolder['globalname']);
330         $this->assertEquals(TRUE, (bool)$testfolder['has_children'], 'should have children');
331         
332         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
333             . ' Delete subfolder directly on imap server');
334         
335         $this->_imap->removeFolder($this->_testFolderName . $this->_account->delimiter . 'test' . $this->_account->delimiter . 'testsub');
336         array_shift($this->_createdFolders);
337         
338         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
339             . ' Check if has_children got updated and folder is removed from cache');
340         
341         $this->_json->updateFolderCache($this->_account->getId(), '');
342         $testfolder = $this->_getFolder($this->_testFolderName . $this->_account->delimiter . 'test');
343         $this->assertEquals(FALSE, (bool)$testfolder['has_children'], 'should have no children');
344
345         return $testfolder;
346     }
347     
348     /**
349      * testUpdateFolderCacheOfNonexistantFolder
350      *
351      * @see 0009800: unselectable folder with subfolders disappears
352      */
353     public function testUpdateFolderCacheOfNonexistantFolder()
354     {
355         $testfolder = $this->testUpdateFolderCache();
356         
357         try {
358             $folderName = $this->_testFolderName . $this->_account->delimiter . 'test' . $this->_account->delimiter . 'testsub';
359             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
360                 . ' Trying to fetch deleted folder ' . $folderName);
361             
362             $testfoldersub = Felamimail_Controller_Folder::getInstance()->getByBackendAndGlobalName($this->_account->getId(), $folderName);
363             $this->fail('Tinebase_Exception_NotFound expected when looking for folder ' . $folderName);
364         } catch (Tinebase_Exception_NotFound $tenf) {
365         }
366         
367         $this->_imap->removeFolder($this->_testFolderName . $this->_account->delimiter . 'test');
368         array_shift($this->_createdFolders);
369         
370         // try to update message cache of nonexistant folder
371         $removedTestfolder = $this->_json->updateMessageCache($testfolder['id'], 1);
372         $this->assertEquals(0, $removedTestfolder['is_selectable'], 'Folder should not be selectable');
373         
374         // update cache and check if folder is deleted
375         $result = $this->_json->updateFolderCache($this->_account->getId(), $this->_testFolderName);
376         $this->assertEquals(0, count($result));
377     }
378     
379     /*********************** accounts tests **************************/
380     
381     /**
382      * test search for accounts and check default account from config
383      */
384     public function testSearchAccounts()
385     {
386         $system = $this->_getSystemAccount();
387         
388         $this->assertTrue(! empty($system), 'no accounts found');
389         if (TestServer::getInstance()->getConfig()->mailserver) {
390             $this->assertEquals(TestServer::getInstance()->getConfig()->mailserver, $system['host']);
391             $this->assertEquals(TestServer::getInstance()->getConfig()->mailserver, $system['sieve_hostname']);
392         }
393     }
394     
395     /**
396      * get system account
397      *
398      * @return array
399      */
400     protected function _getSystemAccount()
401     {
402         $results = $this->_json->searchAccounts(array());
403         
404         $this->assertGreaterThan(0, $results['totalcount']);
405         $system = array();
406         foreach ($results['results'] as $result) {
407             if ($result['name'] == Tinebase_Core::getUser()->accountLoginName . '@' . $this->_mailDomain) {
408                 $system = $result;
409             }
410         }
411         
412         return $system;
413     }
414     
415     /**
416      * test change / delete of account
417      */
418     public function testChangeDeleteAccount()
419     {
420         $system = $this->_getSystemAccount();
421         unset($system['id']);
422         $system['type'] = Felamimail_Model_Account::TYPE_USER;
423         $account = $this->_json->saveAccount($system);
424         
425         $accountRecord = new Felamimail_Model_Account($account, TRUE);
426         $accountRecord->resolveCredentials(FALSE);
427         if (TestServer::getInstance()->getConfig()->mailserver) {
428             $this->assertEquals(TestServer::getInstance()->getConfig()->mailserver, $account['host']);
429         }
430         
431         $this->_json->changeCredentials($account['id'], $accountRecord->user, 'neuespasswort');
432         $account = $this->_json->getAccount($account['id']);
433         
434         $accountRecord = new Felamimail_Model_Account($account, TRUE);
435         $accountRecord->resolveCredentials(FALSE);
436         $this->assertEquals('neuespasswort', $accountRecord->password);
437         
438         $this->_json->deleteAccounts($account['id']);
439     }
440     
441     /*********************** message tests ****************************/
442     
443     /**
444      * test update message cache
445      */
446     public function testUpdateMessageCache()
447     {
448         $message = $this->_sendMessage();
449         $inbox = $this->_getFolder('INBOX');
450         // update message cache and check result
451         $result = $this->_json->updateMessageCache($inbox['id'], 30);
452         
453         if ($result['cache_status'] == Felamimail_Model_Folder::CACHE_STATUS_COMPLETE) {
454             $this->assertEquals($result['imap_totalcount'], $result['cache_totalcount'], 'totalcounts should be equal');
455         } else if ($result['cache_status'] == Felamimail_Model_Folder::CACHE_STATUS_INCOMPLETE) {
456             $this->assertNotEquals(0, $result['cache_job_actions_est']);
457         }
458     }
459     
460     /**
461      * test folder status
462      */
463     public function testGetFolderStatus()
464     {
465         $filter = $this->_getFolderFilter();
466         $result = $this->_json->searchFolders($filter);
467         $this->assertGreaterThan(1, $result['totalcount']);
468         $expectedFolders = array('INBOX', $this->_testFolderName, $this->_account->trash_folder, $this->_account->sent_folder);
469         
470         foreach ($result['results'] as $folder) {
471             $this->_json->updateMessageCache($folder['id'], 30);
472         }
473         
474         $message = $this->_sendMessage();
475         
476         $status = $this->_json->getFolderStatus(array(array('field' => 'account_id', 'operator' => 'equals', 'value' => $this->_account->getId())));
477         $this->assertEquals(1, count($status));
478         $this->assertEquals($this->_account->sent_folder, $status[0]['localname']);
479     }
480
481     /**
482      * test folder status of deleted folder
483      *
484      * @see 0007134: getFolderStatus should ignore non-existent folders
485      */
486     public function testGetFolderStatusOfDeletedFolder()
487     {
488         $this->testCreateFolders();
489         // remove one of the created folders
490         $removedFolder = $this->_createdFolders[0];
491         $this->_imap->removeFolder(Felamimail_Model_Folder::encodeFolderName($removedFolder));
492         
493         $status = $this->_json->getFolderStatus(array(array('field' => 'account_id', 'operator' => 'equals', 'value' => $this->_account->getId())));
494         $this->assertGreaterThan(2, count($status), 'Expected more than 2 folders that need an update: ' . print_r($status, TRUE));
495         foreach ($status as $folder) {
496             if ($folder['globalname'] == $removedFolder) {
497                 $this->fail('removed folder should not appear in status array!');
498             }
499         }
500     }
501     
502     /**
503      * test send message
504      */
505     public function testSendMessage()
506     {
507         // set email to unittest@tine20.org
508         $contactFilter = new Addressbook_Model_ContactFilter(array(
509             array('field' => 'n_family', 'operator' => 'equals', 'value' => 'Clever')
510         ));
511         $contactIds = Addressbook_Controller_Contact::getInstance()->search($contactFilter, NULL, FALSE, TRUE);
512         $this->assertTrue(count($contactIds) > 0, 'sclever not found in addressbook');
513
514         $contact = Addressbook_Controller_Contact::getInstance()->get($contactIds[0]);
515         $originalEmail =  $contact->email;
516         $contact->email = $this->_account->email;
517
518         /* @var $contact Addressbook_Model_Contact */
519         $contact = Addressbook_Controller_Contact::getInstance()->update($contact, FALSE);
520
521         // send email
522         $messageToSend = $this->_getMessageData('unittestalias@' . $this->_mailDomain);
523         $messageToSend['note'] = 1;
524         $messageToSend['bcc']  = array(Tinebase_Core::getUser()->accountEmailAddress);
525
526         $this->_json->saveMessage($messageToSend);
527         $this->_foldersToClear = array('INBOX', $this->_account->sent_folder);
528
529         // check if message is in sent folder
530         $message = $this->_searchForMessageBySubject($messageToSend['subject'], $this->_account->sent_folder);
531         $this->assertEquals($message['from_email'], $messageToSend['from_email']);
532         $this->assertTrue(isset($message['to'][0]));
533         $this->assertEquals($message['to'][0],      $messageToSend['to'][0], 'recipient not found');
534         $this->assertEquals($message['bcc'][0],     $messageToSend['bcc'][0], 'bcc recipient not found');
535         $this->assertEquals($message['subject'],    $messageToSend['subject']);
536
537         $this->_checkEmailNote($contact, $messageToSend['subject']);
538
539         // reset sclevers original email address
540         $contact->email = $originalEmail;
541         Addressbook_Controller_Contact::getInstance()->update($contact, FALSE);
542     }
543
544     /**
545      * test send message
546      */
547     public function testSendMessageInvalidMail()
548     {
549         // send email
550         $messageToSend = $this->_getMessageData('unittestalias@' . $this->_mailDomain);
551         $messageToSend['note'] = 1;
552         $messageToSend['to'] = [
553             sprintf(
554                 '%s <    %s     >',
555                 Tinebase_Core::getUser()->accountFullName,
556                 Tinebase_Core::getUser()->accountEmailAddress
557             )
558         ];
559         $messageToSend['bcc']  = array(Tinebase_Core::getUser()->accountEmailAddress);
560
561         $this->_json->saveMessage($messageToSend);
562         $this->_foldersToClear = array('INBOX', $this->_account->sent_folder);
563     }
564
565     /**
566      * test mail sanitize
567      */
568     public function testSanitizeMail()
569     {
570         $expected = 'info@testest.de';
571         $obfuscatedMail = '  info@testest.de

';
572
573         $reflectionMethod = $this->getProtectedMethod(Felamimail_Model_Message::class, 'sanitizeMailAddress');
574         $result = $reflectionMethod->invokeArgs(new Felamimail_Model_Message(), [$obfuscatedMail]);
575
576         $this->assertEquals($expected, $result);
577     }
578
579     /**
580      * check email note
581      *
582      * @param Addressbook_Model_Contact $contact
583      * @param string $subject
584      */
585     protected function _checkEmailNote($contact, $subject)
586     {
587         // check if email note has been added to contact(s)
588         $contact = Addressbook_Controller_Contact::getInstance()->get($contact->getId());
589         $emailNoteType = Tinebase_Notes::getInstance()->getNoteTypeByName('email');
590         
591         // check / delete notes
592         $emailNotes = new Tinebase_Record_RecordSet('Tinebase_Model_Note');
593         foreach ($contact->notes as $note) {
594             if ($note->note_type_id == $emailNoteType->getId()) {
595                 $this->assertContains($subject, $note->note, 'did not find note subject');
596                 $this->assertEquals(Tinebase_Core::getUser()->getId(), $note->created_by);
597                 $this->assertContains('aaaaaä', $note->note);
598                 $emailNotes->addRecord($note);
599             }
600         }
601         $this->assertGreaterThan(0, $emailNotes->count(), 'no email notes found');
602         Tinebase_Notes::getInstance()->deleteNotes($emailNotes);
603     }
604
605     /**
606      * test send message to invalid recipient
607      */
608     public function testSendMessageToInvalidRecipient($invalidEmail = null, $toField = 'to', $expectedExceptionMessage = 'Recipient address rejected')
609     {
610         $this->markTestSkipped('FIXME: 0011802: Felamimail_Frontend_JsonTest::testSendMessageToInvalidRecipient fails');
611
612         $messageToSend = $this->_getMessageData($this->_account->email);
613         if ($invalidEmail === null) {
614             $invalidEmail = 'invaliduser@' . $this->_mailDomain;
615         }
616         if ($toField !== 'to') {
617             $messageToSend['to'] = array(Tinebase_Core::getUser()->accountEmailAddress);
618         }
619         $messageToSend[$toField] = array($invalidEmail);
620
621         $translation = Tinebase_Translation::getTranslation('Felamimail');
622
623         try {
624             $this->_json->saveMessage($messageToSend);
625             $this->fail('Tinebase_Exception_SystemGeneric expected');
626         } catch (Tinebase_Exception_SystemGeneric $tesg) {
627             $this->assertContains('>: ' . $translation->_($expectedExceptionMessage), $tesg->getMessage(),
628                 'exception message did not match: ' . $tesg->getMessage());
629         }
630     }
631
632     /**
633      * test send message to invalid recipients (invalid email addresses)
634      *
635      * @see 0012292: check and show invalid email addresses before sending mail
636      */
637     public function testSendMessageWithInvalidEmails()
638     {
639         $this->testSendMessageToInvalidRecipient('memyselfandi.de', 'to', 'Invalid address format');
640         $this->testSendMessageToInvalidRecipient('ich bins <mymail@ ' . $this->_mailDomain .'>', 'cc', 'Invalid address format');
641         $this->testSendMessageToInvalidRecipient('ich bins nicht <mymail\@' . $this->_mailDomain .'>', 'bcc', 'Invalid address format');
642         $this->testSendMessageToInvalidRecipient('my@mail@' . $this->_mailDomain, 'bcc', 'Invalid address format');
643     }
644
645     /**
646      * try to get a message from imap server (with complete body, attachments, etc)
647      *
648      * @see 0006300: add unique message-id header to new messages (for message-id check)
649      * @see 0012436: message-id is not valid because of double brackets
650      */
651     public function testGetMessage()
652     {
653         $message = $this->_sendMessage();
654         
655         // get complete message
656         $message = $this->_json->getMessage($message['id']);
657         
658         // check
659         $this->assertTrue(isset($message['headers']) && $message['headers']['message-id']);
660         $this->assertContains('@' . $this->_mailDomain, $message['headers']['message-id']);
661         $this->assertNotContains('<<', $message['headers']['message-id']);
662         $this->assertNotContains('>>', $message['headers']['message-id']);
663         $this->assertGreaterThan(0, preg_match('/aaaaaä/', $message['body']));
664         
665         // delete message on imap server and check if correct exception is thrown when trying to get it
666         $this->_imap->selectFolder('INBOX');
667         $this->_imap->removeMessage($message['messageuid']);
668         Tinebase_Core::getCache()->clean();
669         $this->setExpectedException('Felamimail_Exception_IMAPMessageNotFound');
670         $message = $this->_json->getMessage($message['id']);
671     }
672     
673     /**
674      * try to get a message as plain/text
675      */
676     public function testGetPlainTextMessage()
677     {
678         $accountBackend = new Felamimail_Backend_Account();
679         $message = $this->_sendMessage();
680         
681         // get complete message
682         $this->_account->display_format = Felamimail_Model_Account::DISPLAY_PLAIN;
683         $accountBackend->update($this->_account);
684         $message = $this->_json->getMessage($message['id']);
685         $this->_account->display_format = Felamimail_Model_Account::DISPLAY_HTML;
686         $accountBackend->update($this->_account);
687         
688         // check
689         $this->assertEquals("aaaaaä \n\r\n", $message['body']);
690     }
691     
692     /**
693      * try search for a message with path filter
694      */
695     public function testSearchMessageWithPathFilter()
696     {
697         $sentMessage = $this->_sendMessage();
698         $filter = array(array(
699             'field' => 'path', 'operator' => 'in', 'value' => '/' . $this->_account->getId()
700         ));
701         $result = $this->_json->searchMessages($filter, '');
702         $message = $this->_getMessageFromSearchResult($result, $sentMessage['subject']);
703         $this->assertTrue(! empty($message), 'Sent message not found with account path filter');
704
705         $inbox = $this->_getFolder('INBOX');
706         $filter = array(array(
707             'field' => 'path', 'operator' => 'in', 'value' => '/' . $this->_account->getId() . '/' . $inbox->getId()
708         ));
709         $result = $this->_json->searchMessages($filter, '');
710         $message = $this->_getMessageFromSearchResult($result, $sentMessage['subject']);
711         $this->assertTrue(! empty($message), 'Sent message not found with path filter');
712         foreach ($result['results'] as $mail) {
713             $this->assertEquals($inbox->getId(), $mail['folder_id'], 'message is in wrong folder: ' . print_r($mail, TRUE));
714         }
715     }
716     
717     /**
718      * try search for a message with all inboxes and flags filter
719      */
720     public function testSearchMessageWithAllInboxesFilter()
721     {
722         $sentMessage = $this->_sendMessage();
723         $filter = array(
724             array('field' => 'path',  'operator' => 'in',       'value' => Felamimail_Model_MessageFilter::PATH_ALLINBOXES),
725             array('field' => 'flags', 'operator' => 'notin',    'value' => Zend_Mail_Storage::FLAG_FLAGGED),
726         );
727         $result = $this->_json->searchMessages($filter, '');
728         $this->assertGreaterThan(0, $result['totalcount']);
729         $this->assertEquals($result['totalcount'], count($result['results']));
730         
731         $message = $this->_getMessageFromSearchResult($result, $sentMessage['subject']);
732         $this->assertTrue(! empty($message), 'Sent message not found with all inboxes filter');
733     }
734     
735     /**
736      * try search for a message with three cache filters to force a foreign relation join with at least 2 tables
737      */
738     public function testSearchMessageWithThreeCacheFilter()
739     {
740         $filter = array(
741             array('field' => 'flags',   'operator' => 'in',       'value' => Zend_Mail_Storage::FLAG_ANSWERED),
742             array('field' => 'to',      'operator' => 'contains', 'value' => 'testDOESNOTEXIST'),
743             array('field' => 'subject', 'operator' => 'contains', 'value' => 'testDOESNOTEXIST'),
744         );
745         $result = $this->_json->searchMessages($filter, '');
746         $this->assertEquals(0, $result['totalcount']);
747     }
748     
749     /**
750      * try search for a message with empty path filter
751      */
752     public function testSearchMessageEmptyPath()
753     {
754         $sentMessage = $this->_sendMessage();
755         
756         $filter = array(
757             array('field' => 'path',  'operator' => 'equals',   'value' => ''),
758         );
759         $result = $this->_json->searchMessages($filter, '');
760         
761         $this->assertEquals(0, $result['totalcount']);
762         $accountFilterFound = FALSE;
763         
764         foreach ($result['filter'] as $filter) {
765             if ($filter['field'] === 'account_id' && empty($filter['value'])) {
766                 $accountFilterFound = TRUE;
767                 break;
768             }
769         }
770         $this->assertTrue($accountFilterFound);
771     }
772     
773     /**
774      * test flags (add + clear + deleted)
775      */
776     public function testAddAndClearFlags()
777     {
778         $message = $this->_sendMessage();
779         $inboxBefore = $this->_getFolder('INBOX');
780         
781         $this->_json->addFlags($message['id'], Zend_Mail_Storage::FLAG_SEEN);
782         
783         // check if unread count got decreased
784         $inboxAfter = $this->_getFolder('INBOX');
785         $this->assertTrue($inboxBefore->cache_unreadcount - 1 == $inboxAfter->cache_unreadcount, 'wrong cache unreadcount');
786         
787         $message = $this->_json->getMessage($message['id']);
788         $this->assertTrue(in_array(Zend_Mail_Storage::FLAG_SEEN, $message['flags']), 'seen flag not set');
789         
790         // try with a filter
791         $filter = array(
792             array('field' => 'id', 'operator' => 'in', array($message['id']))
793         );
794         $this->_json->clearFlags($filter, Zend_Mail_Storage::FLAG_SEEN);
795         
796         $message = $this->_json->getMessage($message['id']);
797         $this->assertFalse(in_array(Zend_Mail_Storage::FLAG_SEEN, $message['flags']), 'seen flag should not be set');
798
799         $this->setExpectedException('Tinebase_Exception_NotFound');
800         $this->_json->addFlags(array($message['id']), Zend_Mail_Storage::FLAG_DELETED);
801         $this->_json->getMessage($message['id']);
802     }
803     
804     /**
805      * testMarkFolderRead
806      *
807      * @see 0009812: mark folder as read does not work with pgsql
808      */
809     public function testMarkFolderRead()
810     {
811         $inboxBefore = $this->_getFolder('INBOX');
812         $filter = array(array(
813             'field' => 'folder_id', 'operator' => 'equals', 'value' => $inboxBefore->getId()
814         ), array(
815             'field' => 'flags', 'operator' => 'notin', 'value' => array(Zend_Mail_Storage::FLAG_SEEN)
816         ));
817         $this->_json->addFlags($filter, Zend_Mail_Storage::FLAG_SEEN);
818         
819         $inboxAfter = $this->_getFolder('INBOX');
820         $this->assertEquals(0, $inboxAfter->cache_unreadcount);
821     }
822     
823     /**
824      * test delete from trash
825      */
826     public function testDeleteFromTrashWithFilter()
827     {
828         $message = $this->_sendMessage();
829         $this->_foldersToClear = array('INBOX', $this->_account->sent_folder, $this->_account->trash_folder);
830         
831         $trash = $this->_getFolder($this->_account->trash_folder);
832         $result = $this->_json->moveMessages(array(array(
833             'field' => 'id', 'operator' => 'in', 'value' => array($message['id'])
834         )), $trash->getId());
835
836         $messageInTrash = $this->_searchForMessageBySubject($message['subject'], $this->_account->trash_folder);
837         
838         // delete messages in trash with filter
839         $this->_json->addFlags(array(array(
840             'field' => 'folder_id', 'operator' => 'equals', 'value' => $trash->getId()
841         ), array(
842             'field' => 'id', 'operator' => 'in', 'value' => array($messageInTrash['id'])
843         )), Zend_Mail_Storage::FLAG_DELETED);
844         
845         $this->setExpectedException('Tinebase_Exception_NotFound');
846         $this->_json->getMessage($messageInTrash['id']);
847     }
848     
849     /**
850      * move message to trash with trash folder constant (Felamimail_Model_Folder::FOLDER_TRASH)
851      */
852     public function testMoveMessagesToTrash()
853     {
854         $message = $this->_sendMessage();
855         $this->_foldersToClear = array('INBOX', $this->_account->sent_folder, $this->_account->trash_folder);
856         
857         $result = $this->_json->moveMessages(array(array(
858             'field' => 'id', 'operator' => 'in', 'value' => array($message['id'])
859         )), Felamimail_Model_Folder::FOLDER_TRASH);
860
861         $messageInTrash = $this->_searchForMessageBySubject($message['subject'], $this->_account->trash_folder);
862     }
863     
864     /**
865      * test reply mail and check some headers
866      *
867      * @see 0006106: Add References header / https://forge.tine20.org/mantisbt/view.php?id=6106
868      */
869     public function testReplyMessage()
870     {
871         $message = $this->_sendMessage();
872         
873         $replyMessage = $this->_getReply($message);
874         $this->_json->saveMessage($replyMessage);
875         
876         $result = $this->_getMessages();
877         
878         $replyMessageFound = array();
879         $originalMessage = array();
880         foreach ($result['results'] as $mail) {
881             if ($mail['subject'] == $replyMessage['subject']) {
882                 $replyMessageFound = $mail;
883             }
884             if ($mail['subject'] == $message['subject']) {
885                 $originalMessage = $mail;
886             }
887         }
888
889         $this->assertTrue(isset($replyMessageFound['id']) && isset($originalMessage['id']), 'replied message not found');
890         $replyMessageFound = $this->_json->getMessage($replyMessageFound['id']);
891         $originalMessage = $this->_json->getMessage($originalMessage['id']);
892         
893         $this->assertTrue(! empty($replyMessageFound), 'replied message not found');
894         $this->assertTrue(! empty($originalMessage), 'original message not found');
895         
896         // check headers
897         $this->assertTrue(isset($replyMessageFound['headers']['in-reply-to']));
898         $this->assertEquals($originalMessage['headers']['message-id'], $replyMessageFound['headers']['in-reply-to']);
899         $this->assertTrue(isset($replyMessageFound['headers']['references']));
900         $this->assertEquals($originalMessage['headers']['message-id'], $replyMessageFound['headers']['references']);
901         
902         // check answered flag
903         $this->assertTrue(in_array(Zend_Mail_Storage::FLAG_ANSWERED, $originalMessage['flags'], 'could not find flag'));
904     }
905     
906     /**
907      * get reply message data
908      *
909      * @param array $_original
910      * @return array
911      */
912     protected function _getReply($_original)
913     {
914         $replyMessage               = $this->_getMessageData();
915         $replyMessage['subject']    = 'Re: ' . $_original['subject'];
916         $replyMessage['original_id']= $_original['id'];
917         $replyMessage['flags']      = Zend_Mail_Storage::FLAG_ANSWERED;
918         
919         return $replyMessage;
920     }
921
922     /**
923      * test reply mail in sent folder
924      */
925     public function testReplyMessageInSentFolder()
926     {
927         $messageInSent = $this->_sendMessage($this->_account->sent_folder);
928         $replyMessage = $this->_getReply($messageInSent);
929         $returned = $this->_json->saveMessage($replyMessage);
930         
931         $result = $this->_getMessages();
932         $sentMessage = $this->_getMessageFromSearchResult($result, $replyMessage['subject']);
933         $this->assertTrue(! empty($sentMessage));
934     }
935
936     /**
937      * test reply mail with long references header
938      *
939      * @see 0006644: "At least one mail header line is too long"
940      */
941     public function testReplyMessageWithLongHeader()
942     {
943         $messageInSent = $this->_sendMessage($this->_account->sent_folder, array(
944             'references' => '<c95d8187-2c71-437e-adb8-5e1dcdbdc507@email.test.org>
945    <2601bbfa-566e-4490-a3db-aad005733d32@email.test.org>
946    <20120530154350.1854610131@ganymed.de>
947    <7e393ce1-d193-44fc-bf5f-30c61a271fe6@email.test.org>
948    <4FC8B49C.8040704@funk.de>
949    <dba2ad5c-6726-4171-8710-984847c010a1@email.test.org>
950    <20120601123551.5E98610131@ganymed.de>
951    <f1cc3195-8641-46e3-8f20-f60f3e16b107@email.test.org>
952    <20120619093658.37E4210131@ganymed.de>
953    <CA+6Rn2PX2Q3tOk2tCQfCjcaC8zYS5XZX327OoyJfUb+w87vCLQ@mail.net.com>
954    <20120619130652.03DD310131@ganymed.de>
955    <37616c6a-4c47-4b54-9ca6-56875bc9205d@email.test.org>
956    <20120620074843.42E2010131@ganymed.de>
957    <CA+6Rn2MAb2x0qeSfcaW6F=0S7LEQL442Sx2ha9RtwMs4B0esBg@mail.net.com>
958    <20120620092902.88C8C10131@ganymed.de>
959    <c95d8187-2c71-437e-adb8-5e1dcdbdc507@email.test.org>
960    <2601bbfa-566e-4490-a3db-aad005733d32@email.test.org>
961    <20120530154350.1854610131@ganymed.de>
962    <7e393ce1-d193-44fc-bf5f-30c61a271fe6@email.test.org>
963    <4FC8B49C.8040704@funk.de>
964    <dba2ad5c-6726-4171-8710-984847c010a1@email.test.org>
965    <20120601123551.5E98610131@ganymed.de>
966    <f1cc3195-8641-46e3-8f20-f60f3e16b107@email.test.org>
967    <20120619093658.37E4210131@ganymed.de>
968    <CA+6Rn2PX2Q3tOk2tCQfCjcaC8zYS5XZX327OoyJfUb+w87vCLQ@mail.net.com>
969    <20120619130652.03DD310131@ganymed.de>
970    <37616c6a-4c47-4b54-9ca6-56875bc9205d@email.test.org>
971    <20120620074843.42E2010131@ganymed.de>
972    <CA+6Rn2MAb2x0qeSfcaW6F=0S7LEQL442Sx2ha9RtwMs4B0esBg@mail.net.com>
973    <20120620092902.88C8C10131@ganymed.de>'
974         ));
975         $replyMessage = $this->_getReply($messageInSent);
976         $returned = $this->_json->saveMessage($replyMessage);
977         
978         $result = $this->_getMessages();
979         $sentMessage = $this->_getMessageFromSearchResult($result, $replyMessage['subject']);
980         $this->assertTrue(! empty($sentMessage));
981     }
982     
983     /**
984      * test move
985      */
986     public function testMoveMessage()
987     {
988         $message = $this->_sendMessage();
989         $this->_foldersToClear = array('INBOX', $this->_account->sent_folder, $this->_testFolderName);
990         
991         $inbox = $this->_getFolder('INBOX');
992         $inboxBefore = $this->_json->updateMessageCache($inbox['id'], 30);
993         
994         // move
995         $testFolder = $this->_getFolder($this->_testFolderName);
996         $result = $this->_json->moveMessages(array(array(
997             'field' => 'id', 'operator' => 'in', 'value' => array($message['id'])
998         )), $testFolder->getId());
999
1000         // sleep for 2 secs because mailserver may be slower than expected
1001         sleep(2);
1002
1003         $inboxAfter = $this->_getFolder('INBOX');
1004         
1005         // check if count was decreased correctly
1006         $this->assertEquals($inboxBefore['cache_unreadcount'] - 1, $inboxAfter['cache_unreadcount']);
1007         $this->assertEquals($inboxBefore['cache_totalcount'] - 1, $inboxAfter['cache_totalcount']);
1008         
1009         $result = $this->_getMessages($this->_testFolderName);
1010         $movedMessage = array();
1011         foreach ($result['results'] as $mail) {
1012             if ($mail['subject'] == $message['subject']) {
1013                 $movedMessage = $mail;
1014             }
1015         }
1016         $this->assertTrue(! empty($movedMessage), 'moved message not found');
1017     }
1018     
1019     /**
1020      * forward message test
1021      *
1022      * @see 0007624: losing umlauts in attached filenames
1023      */
1024     public function testForwardMessageWithAttachment()
1025     {
1026         $testFolder = $this->_getFolder($this->_testFolderName);
1027         $message = fopen(dirname(__FILE__) . '/../files/multipart_related.eml', 'r');
1028         Felamimail_Controller_Message::getInstance()->appendMessage($testFolder, $message);
1029         
1030         $subject = 'Tine 2.0 bei Metaways - Verbessurngsvorschlag';
1031         $message = $this->_searchForMessageBySubject($subject, $this->_testFolderName);
1032         
1033         $fwdSubject = 'Fwd: ' . $subject;
1034         $forwardMessageData = array(
1035             'account_id'    => $this->_account->getId(),
1036             'subject'       => $fwdSubject,
1037             'to'            => array($this->_getEmailAddress()),
1038             'body'          => "aaaaaä <br>",
1039             'headers'       => array('X-Tine20TestMessage' => 'jsontest'),
1040             'original_id'   => $message['id'],
1041             'attachments'   => array(new Tinebase_Model_TempFile(array(
1042                 'type'  => Felamimail_Model_Message::CONTENT_TYPE_MESSAGE_RFC822,
1043                 'name'  => 'Verbessurüngsvorschlag',
1044             ), TRUE)),
1045             'flags'         => Zend_Mail_Storage::FLAG_PASSED,
1046         );
1047         
1048         $this->_foldersToClear[] = 'INBOX';
1049         $this->_json->saveMessage($forwardMessageData);
1050         $forwardMessage = $this->_searchForMessageBySubject($fwdSubject);
1051         
1052         // check attachment name
1053         $forwardMessageComplete = $this->_json->getMessage($forwardMessage['id']);
1054         $this->assertEquals(1, count($forwardMessageComplete['attachments']));
1055         $this->assertEquals('Verbessurüngsvorschlag.eml', $forwardMessageComplete['attachments'][0]['filename'], 'umlaut missing from attachment filename');
1056         
1057         $forwardMessage = $this->_json->getMessage($forwardMessage['id']);
1058         $this->assertTrue((isset($forwardMessage['structure']) || array_key_exists('structure', $forwardMessage)), 'structure should be set when fetching complete message: ' . print_r($forwardMessage, TRUE));
1059         $this->assertEquals(Felamimail_Model_Message::CONTENT_TYPE_MESSAGE_RFC822, $forwardMessage['structure']['parts'][2]['contentType']);
1060         
1061         $message = $this->_json->getMessage($message['id']);
1062         $this->assertTrue(in_array(Zend_Mail_Storage::FLAG_PASSED, $message['flags']), 'forwarded flag missing in flags: ' . print_r($message, TRUE));
1063     }
1064     
1065     /**
1066      * testSendMessageWithAttachmentWithoutExtension
1067      *
1068      * @see 0008328: email attachment without file extension is not sent properly
1069      */
1070     public function testSendMessageWithAttachmentWithoutExtension()
1071     {
1072         $subject = 'attachment test';
1073         $messageToSend = $this->_getMessageData('unittestalias@' . $this->_mailDomain, $subject);
1074         $tempfileName = 'jsontest' . Tinebase_Record_Abstract::generateUID(10);
1075         $tempFile = $this->_createTempFile($tempfileName);
1076         $messageToSend['attachments'] = array(
1077             array('tempFile' => array('id' => $tempFile->getId(), 'type' => $tempFile->type))
1078         );
1079         $this->_json->saveMessage($messageToSend);
1080         $forwardMessage = $this->_searchForMessageBySubject($subject);
1081         $this->_foldersToClear = array('INBOX', $this->_account->sent_folder);
1082         
1083         $fullMessage = $this->_json->getMessage($forwardMessage['id']);
1084         $this->assertTrue(count($fullMessage['attachments']) === 1);
1085         $attachment = $fullMessage['attachments'][0];
1086         $this->assertContains($tempfileName, $attachment['filename'], 'wrong attachment filename: ' . print_r($attachment, TRUE));
1087         $this->assertEquals(16, $attachment['size'], 'wrong attachment size: ' . print_r($attachment, TRUE));
1088     }
1089     
1090     /**
1091      * save message in folder (draft) test
1092      *
1093      * @see 0007178: BCC does not save the draft message
1094      */
1095     public function testSaveMessageInFolder()
1096     {
1097         $messageToSave = $this->_getMessageData();
1098         $messageToSave['bcc'] = array('bccaddress@email.org', 'bccaddress2@email.org');
1099         
1100         $draftsFolder = $this->_getFolder($this->_account->drafts_folder);
1101         $returned = $this->_json->saveMessageInFolder($this->_account->drafts_folder, $messageToSave);
1102         $this->_foldersToClear = array($this->_account->drafts_folder);
1103         
1104         // check if message is in drafts folder and recipients are present
1105         $message = $this->_searchForMessageBySubject($messageToSave['subject'], $this->_account->drafts_folder);
1106         $this->assertEquals($messageToSave['subject'],  $message['subject']);
1107         $this->assertEquals($messageToSave['to'][0],    $message['to'][0], 'recipient not found');
1108         $this->assertEquals(2, count($message['bcc']), 'bcc recipient not found: ' . print_r($message, TRUE));
1109         $this->assertEquals($messageToSave['bcc'][0],   $message['bcc'][0], '1st bcc recipient not found');
1110         $this->assertEquals($messageToSave['bcc'][1],   $message['bcc'][1], '2nd bcc recipient not found');
1111     }
1112     
1113     /**
1114      * testSendReadingConfirmation
1115      *
1116      * @see 0007736: ask user before sending reading confirmation
1117      * @see 0008402: Wrong recipient with read confirmation
1118      */
1119     public function testSendReadingConfirmation()
1120     {
1121         $messageToSave = $this->_getMessageData();
1122         $messageToSave['headers']['disposition-notification-to'] = '"' . Tinebase_Core::getUser()->accountFullName . '" <' . $this->_account->email . '>';
1123         $returned = $this->_json->saveMessageInFolder($this->_testFolderName, $messageToSave);
1124         $messageWithReadingConfirmationHeader = $this->_searchForMessageBySubject($messageToSave['subject'], $this->_testFolderName);
1125         $this->_messageIds[] = $messageWithReadingConfirmationHeader['id'];
1126         $this->_json->sendReadingConfirmation($messageWithReadingConfirmationHeader['id']);
1127         
1128         $translate = Tinebase_Translation::getTranslation('Felamimail');
1129         $subject = $translate->_('Reading Confirmation:') . ' '. $messageToSave['subject'];
1130         $message = $this->_searchForMessageBySubject($subject);
1131         $this->_messageIds[] = $message['id'];
1132         
1133         $complete = $this->_json->getMessage($message['id']);
1134         $this->assertContains($translate->_('Was read by:') . ' ' . $this->_account->from, $complete['body']);
1135     }
1136
1137     /**
1138      * save message in non-existant folder (templates) test
1139      *
1140      * @see 0008476: Drafts are not working
1141      */
1142     public function testSaveMessageInNonExistantTemplatesFolder()
1143     {
1144         $messageToSave = $this->_getMessageData();
1145         
1146         $templatesFolder = $this->_getFolder($this->_account->templates_folder, FALSE);
1147         if ($templatesFolder) {
1148             $this->_json->deleteFolder($templatesFolder['id'], $this->_account->getId());
1149         }
1150         $returned = $this->_json->saveMessageInFolder($this->_account->templates_folder, $messageToSave);
1151         $this->_foldersToClear = array($this->_account->templates_folder);
1152         
1153         // check if message is in templates folder
1154         $message = $this->_searchForMessageBySubject($messageToSave['subject'], $this->_account->templates_folder);
1155         $this->assertEquals($messageToSave['subject'],  $message['subject']);
1156         $this->assertEquals($messageToSave['to'][0],    $message['to'][0], 'recipient not found');
1157     }
1158     
1159     /**
1160      * testSaveMessageNoteWithInvalidChar
1161      *
1162      * @see 0008644: error when sending mail with note (wrong charset)
1163      */
1164     public function testSaveMessageNoteWithInvalidChar()
1165     {
1166         $subject = Tinebase_Core::filterInputForDatabase("\xF0\x9F\x98\x8A\xC2"); // :-) emoji &nbsp;
1167         $messageData = $this->_getMessageData('', $subject);
1168         $messageData['note'] = true;
1169         $messageData['body'] .= "&nbsp;";
1170         
1171         $this->_foldersToClear[] = 'INBOX';
1172         $this->_json->saveMessage($messageData);
1173         $message = $this->_searchForMessageBySubject($subject);
1174         
1175         $contact = Addressbook_Controller_Contact::getInstance()->getContactByUserId(Tinebase_Core::getUser()->getId());
1176         $this->_checkEmailNote($contact, $subject);
1177     }
1178     
1179     /**
1180      * testSaveMessageNoteWithInvalidChar
1181      *
1182      * @see 0008644: error when sending mail with note (wrong charset)
1183      */
1184     public function testSaveMessageWithInvalidChar()
1185     {
1186         $subject = "\xF0\x9F\x98\x8A"; // :-) emoji
1187         $messageData = $this->_getMessageData('', $subject);
1188         $this->_foldersToClear[] = 'INBOX';
1189         $this->_json->saveMessage($messageData);
1190         $this->_searchForMessageBySubject(Tinebase_Core::filterInputForDatabase($subject));
1191     }
1192
1193     /**
1194      * @see 0012160: save emails in filemanager
1195      *
1196      * @param string  $appName
1197      */
1198     public function testFileMessages($appName = 'Filemanager')
1199     {
1200         $user = Tinebase_Core::getUser();
1201         $personalFilemanagerContainer = $this->_getPersonalContainerNode($appName, $user);
1202         $message = $this->_sendMessage(
1203             'INBOX',
1204             /* $addtionalHeaders */ array(),
1205             /* $_emailFrom */ '',
1206             /*$_subject */ 'test\test' // is converted to 'test_test'
1207         );
1208         $path = '/' . Tinebase_Model_Container::TYPE_PERSONAL
1209             . '/' . $user->accountLoginName
1210             . '/' . $personalFilemanagerContainer->name;
1211         $filter = array(array(
1212             'field' => 'id', 'operator' => 'in', 'value' => array($message['id'])
1213         ));
1214         $result = $this->_json->fileMessages($filter, $appName, $path);
1215         $this->assertTrue(isset($result['totalcount']));
1216         $this->assertEquals(1, $result['totalcount'], 'message should be filed in ' . $appName . ': ' . print_r($result, true));
1217
1218         // check if message exists in $appName
1219         $filter = new Tinebase_Model_Tree_Node_Filter(array(array(
1220             'field'    => 'path',
1221             'operator' => 'equals',
1222             'value'    => $path
1223         ), array(
1224             'field'    => 'name',
1225             'operator' => 'contains',
1226             'value'    => 'test_test'
1227         )));
1228         $nodeController = Tinebase_Core::getApplicationInstance($appName . '_Model_Node');
1229         $emlNode = $nodeController->search($filter)->getFirstRecord();
1230         $this->assertTrue($emlNode !== null, 'could not find eml file node');
1231         $this->assertEquals(Tinebase_Model_Tree_FileObject::TYPE_FILE, $emlNode->type);
1232         $this->assertEquals('message/rfc822', $emlNode->contenttype);
1233         $this->assertTrue(preg_match('/[a-f0-9]{10}/', $emlNode->name) == 1, 'no message id hash in node name: ' . print_r($emlNode->toArray(), true));
1234
1235         $nodeWithDescription = $nodeController->get($emlNode['id']);
1236         $this->assertTrue(isset($nodeWithDescription->description), 'description missing from node: ' . print_r($nodeWithDescription->toArray(), true));
1237         $this->assertContains($message['received'], $nodeWithDescription->description);
1238         $this->assertContains('aaaaaä', $nodeWithDescription->description);
1239     }
1240
1241     protected function _getPersonalContainerNode($_appName, $_user = null)
1242     {
1243         $user = ($_user) ? $_user : Tinebase_Core::getUser();
1244         return Tinebase_FileSystem::getInstance()->getPersonalContainer(
1245             $user,
1246             $_appName,
1247             $user
1248         )->getFirstRecord();
1249     }
1250
1251     /**
1252      * @see 0012162: create new MailFiler application
1253      */
1254     public function testFileMessagesInMailFiler()
1255     {
1256         $this->testFileMessages('MailFiler');
1257         $personalFilemanagerContainer = $this->_getPersonalContainerNode('MailFiler');
1258         $path = '/' . Tinebase_Model_Container::TYPE_PERSONAL
1259             . '/' . Tinebase_Core::getUser()->accountLoginName
1260             . '/' . $personalFilemanagerContainer->name;
1261         $filter = array(array(
1262             'field'    => 'path',
1263             'operator' => 'equals',
1264             'value'    => $path
1265         ), array(
1266             'field'    => 'subject',
1267             'operator' => 'contains',
1268             'value'    => 'test'
1269         ));
1270         $mailFilerJson = new MailFiler_Frontend_Json();
1271         $emlNodes = $mailFilerJson->searchNodes($filter, array());
1272         $this->assertGreaterThan(0, $emlNodes['totalcount'], 'could not find eml file node with subject filter');
1273         $emlNode = $emlNodes['results'][0];
1274
1275         // check email fields
1276         $this->assertTrue(isset($emlNode['message']), 'message not found in node array: ' . print_r($emlNodes['results'], true));
1277         $this->assertEquals(array(Tinebase_Core::getUser()->accountEmailAddress), $emlNode['message']['to'], print_r($emlNode['message'], true));
1278         $this->assertTrue(isset($emlNode['message']['structure']) && is_array($emlNode['message']['structure']), 'structure not found or not an array: ' . print_r($emlNode['message'], true));
1279         $this->assertTrue(isset($emlNode['message']['body']) && is_string($emlNode['message']['body']), 'body not found or not a string: ' . print_r($emlNode['message'], true));
1280         $this->assertContains('aaaaaä', $emlNode['message']['body'], print_r($emlNode['message'], true));
1281     }
1282
1283     /**
1284      * @see 0012162: create new MailFiler application
1285      */
1286     public function testFileMessagesInMailFilerWithAttachment()
1287     {
1288         $emlNode = $this->_fileMessageInMailFiler();
1289         $this->assertTrue(isset($emlNode['message']['attachments']), 'attachments not found in message node: ' . print_r($emlNode, true));
1290         $this->assertEquals(1, count($emlNode['message']['attachments']), 'attachment not found in message node: ' . print_r($emlNode, true));
1291         $this->assertEquals('moz-screenshot-83.png', $emlNode['message']['attachments'][0]['filename'], print_r($emlNode['message']['attachments'], true));
1292     }
1293
1294     /**
1295      * @param string $messageFile
1296      * @return array
1297      */
1298     protected function _fileMessageInMailFiler($messageFile = 'multipart_related.eml', $subject = 'Tine 2.0 bei Metaways - Verbessurngsvorschlag')
1299     {
1300         $appName = 'MailFiler';
1301         $personalFilemanagerContainer = $this->_getPersonalContainerNode($appName);
1302         $testFolder = $this->_getFolder($this->_testFolderName);
1303         $message = fopen(dirname(__FILE__) . '/../files/' . $messageFile, 'r');
1304         Felamimail_Controller_Message::getInstance()->appendMessage($testFolder, $message);
1305
1306         $message = $this->_searchForMessageBySubject($subject, $this->_testFolderName);
1307         $path = '/' . Tinebase_Model_Container::TYPE_PERSONAL
1308             . '/' . Tinebase_Core::getUser()->accountLoginName
1309             . '/' . $personalFilemanagerContainer->name;
1310         $filter = array(array(
1311             'field' => 'id', 'operator' => 'in', 'value' => array($message['id'])
1312         ));
1313         $this->_json->fileMessages($filter, $appName, $path);
1314         $filter = array(array(
1315             'field'    => 'path',
1316             'operator' => 'equals',
1317             'value'    => $path
1318         ), array(
1319             'field'    => 'subject',
1320             'operator' => 'equals',
1321             'value'    => $message['subject']
1322         ));
1323         $mailFilerJson = new MailFiler_Frontend_Json();
1324         $emlNodes = $mailFilerJson->searchNodes($filter, array());
1325         $this->assertGreaterThan(0, $emlNodes['totalcount'], 'could not find eml file node with subject filter');
1326         $emlNode = $emlNodes['results'][0];
1327
1328         return $emlNode;
1329     }
1330
1331     /**
1332      * @see 0012162: create new MailFiler application
1333      */
1334     public function testFileMessagesInMailFilerWithSingleBodyPart()
1335     {
1336         $emlNode = $this->_fileMessageInMailFiler('tine20_alarm_notifictation.eml', 'Alarm for event "ss/ss" at Oct 12, 2016 4:00:00 PM');
1337         $this->assertContains('Event details', $emlNode['message']['body'], print_r($emlNode['message'], true));
1338         $this->assertContains('"ss/ss"', $emlNode['message']['subject'], print_r($emlNode['message'], true));
1339         $this->assertContains('"ss_ss"', $emlNode['name'], print_r($emlNode, true));
1340     }
1341
1342     /**
1343      * @see 0012162: create new MailFiler application
1344      */
1345     public function testFileMessageWithDelete()
1346     {
1347         $emlNode = $this->_fileMessageInMailFiler();
1348         $mailFilerJson = new MailFiler_Frontend_Json();
1349         $result = $mailFilerJson->deleteNodes(array($emlNode['path']));
1350         self::assertEquals('success', $result['status']);
1351     }
1352
1353     /**
1354      * @see 0012162: create new MailFiler application
1355      */
1356     public function testFileMessageWithMultipartAttachment()
1357     {
1358         $emlNode = $this->_fileMessageInMailFiler('multipart_attachments.eml', 'Testmail mit Anhang');
1359         $this->assertTrue(isset($emlNode['message']['attachments']), 'attachments not found in message node: ' . print_r($emlNode, true));
1360         $this->assertEquals(5, count($emlNode['message']['attachments']), 'attachment not found in message node: ' . print_r($emlNode, true));
1361         $this->assertEquals('TS Lagerstände.jpg', $emlNode['message']['attachments'][0]['filename'], print_r($emlNode['message']['attachments'], true));
1362         $this->assertContains('Siehe Dateien anbei', $emlNode['message']['body'], print_r($emlNode['message'], true));
1363     }
1364
1365     /**
1366      * testMessageWithInvalidICS
1367      *
1368      * @see 0008786: broken ics causes js error when showing details
1369      */
1370     public function testMessageWithInvalidICS()
1371     {
1372         $inbox = $this->_getFolder('INBOX');
1373         $mailAsString = file_get_contents(dirname(__FILE__) . '/../files/invalidimip.eml');
1374         Felamimail_Controller_Message::getInstance()->appendMessage($inbox, $mailAsString);
1375         
1376         $this->_foldersToClear = array('INBOX');
1377         $message = $this->_searchForMessageBySubject('test invalid imip');
1378         
1379         $fullMessage = $this->_json->getMessage($message['id']);
1380         $this->assertTrue(empty($fullMessage->preparedParts));
1381     }
1382
1383     /**
1384      * testSendMailveopeAPIMessage
1385      *
1386      * - envolpe amored message into PGP MIME structure
1387      */
1388     public function testSendMailveopeAPIMessage()
1389     {
1390         $subject = 'testSendMailveopeAPIMessage';
1391         $messageData = $this->_getMessageData('', $subject);
1392         $messageData['body'] = '-----BEGIN PGP MESSAGE-----
1393 Version: Mailvelope v1.3.3
1394 Comment: https://www.mailvelope.com
1395
1396 wcFMA/0LJF28pDbGAQ//YgtsmEZN+pgIJiBDb7iYwPEOchDRIEjGOx543KF6
1397 5YigW9p39pfcJgvGfT8x9cUIrYGxyw5idPSOEftYXyjjGaOYGaKpRSR4hI83
1398 OcJSlEHKq72xhg04mNpCjjJ8dLBstPcQ7tDtsA8Nfb4PwkUYB9IhIBnARg+n
1399 NvrN8mSA2UnY9ElFCvf30sar8EuM5swAjbk64C8TIypMy/Bg4T93zRdxwik6
1400 7BCcbOpm/2PTsiVYBOTcU4+XdG5eyTENXH58M6UTxTD4/g7Qi5PjN+PxyXqf
1401 v2Y1k9F49Y1egf2QJ2r4PX0EWS8SaynSHiIoBsp1xb07nLwZwCdMPG1QNPpF
1402 l2FqlS4dEuQTdkv0deMvd7gtiNynRTAVcJc1ZC6RuWJ+EH2jA49XWkn14eRC
1403 e5jMtPPudkhubnN9Je5lwatGKbJGyuXh+IaM0E0WQMZ3dm8+ST1l4WpVuGbw
1404 KozLUiTRJP9UoxWOcwpQOnzcSlc4rHmWdtF0y3usM9u9GPREqpNUWkEyEEuv
1405 XdZE7rKKj22dJHLCXxAQEh3m29Y2WVaq50YbtEZ+SwwbrHhxP4+FJEru+byh
1406 fiZ47sVW2KvYGJPvbFoSZHiSvMecxDg8BVwe+naZXww/Rwa/TlaX4bYyzpUG
1407 KeJUAzWEfFpJ0+yAvMGQEC7psIJ9NCx149C4ujiQmajSwhUB3XANcmCGB0wm
1408 JjcqC4AHvc7/t4MrQZm0F/W+nrMmNqbZk+gylVrPs9rFEqu7wbjwTmsFA3sS
1409 LkenvQIxBali6uzCR+nd09REqcYirG9cLti39DW048lhhG/ml+gAxxNEaSpG
1410 NbIoV/3w8n7sAIM1fjuHne8bX0gWG43TTjU8MwSMryG7tCOG5u+Cebh6TAoY
1411 NzbX2dpDhOYq5zXdCgKU4P3eh0csSs4UrqFT3TdAxIGrQJ7KrXvB6+N8gRZo
1412 FcUaR+zrRPJjPUZfi46ecP5SG/tM5ea1hqvkwEnEpqjLmCUxqB+rfxx46USX
1413 hMZd2ukUv6kEKv3EUDsRYu1SlDLhDLhWNx8RJae5XkMR+eUUMyNNVwbeMQbB
1414 VAcMcaPITTk84sH7XElr9eF6sCUN4V79OSBRPGY/aNGrcwcoDSD4Hwu+Lw9w
1415 Q+1n8EQ66gAkbJzCNd5GaYMZR9echkBaD/rdWDS3ktcrMehra+h44MTQONV9
1416 8W+RI+IT5jaIXtB4jePmGjsJjbC9aEhTRBRkUnPA7phgknc52dD74AY/6lzK
1417 yd4uZ6S3vhurJW0Vt4iBWJzhFNiSODh5PzteeNzCVAkGMsQvy1IHk0d3uzcE
1418 0tEuSh8fZOFGB4fvMx9Mk8oAU92wfj4J7AVpSo5oRdxMqAXfaYKqfr2Gn++q
1419 E5LClhVIBbFXclCoe0RYNz4wtxjeeYbP40Bq5g0JvPutD/dBMp8hz8Qt+yyG
1420 d8X4/KmQIXyFZ8aP17GMckE5GVVvY9y89eWnWuTUJdwM540hB/EJNeHHTE5y
1421 N2FSLGcmNkvE+3H7BczQ2ZI1SZDhof+umbUst0qoQW+hHmY3CSma48yGAVox
1422 52u2t7hosHCfpf631Ve/6fcICo8vJ2Qfufu2BGIMlSfx4WzUuaMQBynuxFSa
1423 IbVx8ZTO7dJRKrg72aFmWTf0uNla7vicAhpiLWobyNYcZbIjrAGDfg==
1424 =BaAn
1425 -----END PGP MESSAGE-----';
1426
1427         $this->_foldersToClear[] = 'INBOX';
1428         $this->_json->saveMessage($messageData);
1429
1430         $message = $this->_searchForMessageBySubject(Tinebase_Core::filterInputForDatabase($subject));
1431         $fullMessage = $this->_json->getMessage($message['id']);
1432
1433         $this->assertContains('multipart/encrypted', $fullMessage['headers']['content-type']);
1434         $this->assertContains('protocol="application/pgp-encrypted"', $fullMessage['headers']['content-type']);
1435         $this->assertCount(2, $fullMessage['structure']['parts']);
1436         $this->assertEquals('application/pgp-encrypted', $fullMessage['structure']['parts'][1]['contentType']);
1437         $this->assertEquals('application/octet-stream', $fullMessage['structure']['parts'][2]['contentType']);
1438
1439         return $fullMessage;
1440     }
1441
1442     /**
1443      * testMessagePGPMime
1444      *
1445      * - prepare amored part of PGP MIME structure
1446      */
1447     public function testMessagePGPMime()
1448     {
1449         $fullMessage = $this->testSendMailveopeAPIMessage();
1450
1451         $this->assertEquals('application/pgp-encrypted', $fullMessage['preparedParts'][0]['contentType']);
1452         $this->assertContains('-----BEGIN PGP MESSAGE-----', $fullMessage['preparedParts'][0]['preparedData']);
1453     }
1454
1455     public function testMessagePGPInline()
1456     {
1457         $inbox = $this->_getFolder('INBOX');
1458         $mailAsString = file_get_contents(dirname(__FILE__) . '/../files/multipart_alternative_pgp_inline.eml');
1459         Felamimail_Controller_Message::getInstance()->appendMessage($inbox, $mailAsString);
1460
1461         $this->_foldersToClear = array('INBOX');
1462         $message = $this->_searchForMessageBySubject('Re: mailvelope und tine20');
1463
1464         $fullMessage = $this->_json->getMessage($message['id']);
1465         $this->assertFalse(empty($fullMessage['preparedParts']));
1466     }
1467
1468     /*********************** sieve tests ****************************/
1469     
1470     /**
1471      * set and get vacation sieve script
1472      *
1473      * @see 0007768: Sieve - Vacation notify frequency not being set (Cyrus)
1474      */
1475     public function testGetSetVacation()
1476     {
1477         $vacationData = $this->_getVacationData();
1478         $this->_sieveTestHelper($vacationData);
1479         
1480         // check if script was activated
1481         $activeScriptName = Felamimail_Controller_Sieve::getInstance()->getActiveScriptName($this->_account->getId());
1482         $this->assertEquals($this->_testSieveScriptName, $activeScriptName);
1483         $updatedAccount = Felamimail_Controller_Account::getInstance()->get($this->_account->getId());
1484         $this->assertTrue((bool) $updatedAccount->sieve_vacation_active);
1485         
1486         $result = $this->_json->getVacation($this->_account->getId());
1487
1488         $this->assertEquals($this->_account->email, $result['addresses'][0]);
1489         
1490         $sieveBackend = Felamimail_Backend_SieveFactory::factory($this->_account->getId());
1491         if (preg_match('/dbmail/i', $sieveBackend->getImplementation())) {
1492             $translate = Tinebase_Translation::getTranslation('Felamimail');
1493             $vacationData['subject'] = sprintf($translate->_('Out of Office reply from %1$s'), Tinebase_Core::getUser()->accountFullName);
1494         }
1495         
1496         foreach (array('reason', 'enabled', 'subject', 'from', 'days') as $field) {
1497             $this->assertEquals($vacationData[$field], $result[$field], 'vacation data mismatch: ' . $field);
1498         }
1499     }
1500     
1501     /**
1502      * get vacation data
1503      *
1504      * @return array
1505      */
1506     protected function _getVacationData()
1507     {
1508         return array(
1509             'id'                    => $this->_account->getId(),
1510             'subject'               => 'unittest vacation subject',
1511             'from'                  => $this->_account->from . ' <' . $this->_account->email . '>',
1512             'days'                  => 3,
1513             'enabled'               => TRUE,
1514             'reason'                => 'unittest vacation message<br /><br />signature',
1515             'mime'                  => NULL,
1516         );
1517     }
1518     
1519     /**
1520      * test mime vacation sieve script
1521      */
1522     public function testMimeVacation()
1523     {
1524         $vacationData = $this->_getVacationData();
1525         $vacationData['reason'] = "\n<html><body><h1>unittest vacation&nbsp;message</h1></body></html>";
1526         
1527         $_sieveBackend = Felamimail_Backend_SieveFactory::factory($this->_account->getId());
1528         if (! in_array('mime', $_sieveBackend->capability())) {
1529             $vacationData['mime'] = 'text/html';
1530         }
1531         
1532         $this->_sieveTestHelper($vacationData, TRUE);
1533     }
1534     
1535     /**
1536      * test get/set of rules sieve script
1537      */
1538     public function testGetSetRules()
1539     {
1540         $ruleData = $this->_getRuleData();
1541         
1542         $this->_sieveTestHelper($ruleData);
1543         
1544         // check getRules
1545         $result = $this->_json->getRules($this->_account->getId());
1546         $this->assertEquals($result['totalcount'], count($ruleData));
1547         
1548         // check by sending mail
1549         $messageData = $this->_getMessageData('', 'viagra');
1550         $returned = $this->_json->saveMessage($messageData);
1551         $this->_foldersToClear = array('INBOX', $this->_testFolderName);
1552         // check if message is in test folder
1553         $message = $this->_searchForMessageBySubject($messageData['subject'], $this->_testFolderName);
1554     }
1555     
1556     /**
1557      * testRemoveRules
1558      *
1559      * @see 0006490: can not delete single filter rule
1560      */
1561     public function testRemoveRules()
1562     {
1563         $this->testGetSetRules();
1564         $this->_json->saveRules($this->_account->getId(), array());
1565         
1566         $result = $this->_json->getRules($this->_account->getId());
1567         $this->assertEquals(0, $result['totalcount'], 'found rules: ' . print_r($result, TRUE));
1568     }
1569     
1570     /**
1571      * get sieve rule data
1572      *
1573      * @return array
1574      */
1575     protected function _getRuleData()
1576     {
1577         return array(array(
1578             'id'            => 1,
1579             'action_type'   => Felamimail_Sieve_Rule_Action::FILEINTO,
1580             'action_argument' => $this->_testFolderName,
1581             'conjunction'  => 'allof',
1582             'conditions'    => array(array(
1583                 'test'          => Felamimail_Sieve_Rule_Condition::TEST_ADDRESS,
1584                 'comperator'    => Felamimail_Sieve_Rule_Condition::COMPERATOR_CONTAINS,
1585                 'header'        => 'From',
1586                 'key'           => '"abcd" <info@example.org>',
1587             )),
1588             'enabled'       => 1,
1589         ), array(
1590             'id'            => 2,
1591             'action_type'   => Felamimail_Sieve_Rule_Action::FILEINTO,
1592             'action_argument' => $this->_testFolderName,
1593             'conjunction'  => 'allof',
1594             'conditions'    => array(array(
1595                 'test'          => Felamimail_Sieve_Rule_Condition::TEST_ADDRESS,
1596                 'comperator'    => Felamimail_Sieve_Rule_Condition::COMPERATOR_CONTAINS,
1597                 'header'        => 'From',
1598                 'key'           => 'info@example.org',
1599             )),
1600             'enabled'       => 0,
1601         ), array(
1602             'id'            => 3,
1603             'action_type'   => Felamimail_Sieve_Rule_Action::FILEINTO,
1604             'action_argument' => $this->_testFolderName,
1605             'conjunction'  => 'allof',
1606             'conditions'    => array(array(
1607                 'test'          => Felamimail_Sieve_Rule_Condition::TEST_HEADER,
1608                 'comperator'    => Felamimail_Sieve_Rule_Condition::COMPERATOR_REGEX,
1609                 'header'        => 'subject',
1610                 'key'           => '[vV]iagra|cyalis',
1611             )),
1612             'enabled'       => 1,
1613         ));
1614     }
1615     
1616     /**
1617      * test to set a forward rule to this accounts email address
1618      * -> should throw exception to prevent mail cycling
1619      */
1620     public function testSetForwardRuleToSelf()
1621     {
1622         $ruleData = array(array(
1623             'id'            => 1,
1624             'action_type'   => Felamimail_Sieve_Rule_Action::REDIRECT,
1625             'action_argument' => $this->_account->email,
1626             'conjunction'     => 'allof',
1627             'conditions'    => array(array(
1628                 'test'          => Felamimail_Sieve_Rule_Condition::TEST_ADDRESS,
1629                 'comperator'    => Felamimail_Sieve_Rule_Condition::COMPERATOR_CONTAINS,
1630                 'header'        => 'From',
1631                 'key'           => 'info@example.org',
1632             )),
1633             'enabled'       => 1,
1634         ));
1635         
1636         try {
1637             $this->_sieveTestHelper($ruleData);
1638             $this->assertTrue(FALSE, 'it is not allowed to set own email address for redirect!');
1639         } catch (Felamimail_Exception_Sieve $e) {
1640             $this->assertTrue(TRUE);
1641         }
1642
1643         // this should work
1644         $ruleData[0]['enabled'] = 0;
1645         $this->_sieveTestHelper($ruleData);
1646     }
1647
1648     /**
1649      * @see 0006222: Keep a copy from mails forwarded to another emailaddress
1650      */
1651     public function testSetForwardRuleWithCopy()
1652     {
1653         $ruleData = array(array(
1654             'id'            => 1,
1655             'action_type'   => Felamimail_Sieve_Rule_Action::REDIRECT,
1656             'action_argument' => array(
1657                 'emails' => 'someaccount@example.org',
1658                 'copy'   => 1,
1659             ),
1660             'conjunction'     => 'allof',
1661             'conditions'    => array(array(
1662                 'test'          => Felamimail_Sieve_Rule_Condition::TEST_ADDRESS,
1663                 'comperator'    => Felamimail_Sieve_Rule_Condition::COMPERATOR_CONTAINS,
1664                 'header'        => 'From',
1665                 'key'           => 'info@example.org',
1666             )),
1667             'enabled'       => 1,
1668         ));
1669
1670         $this->_sieveTestHelper($ruleData);
1671     }
1672
1673     /**
1674      * @see 0006222: Keep a copy from mails forwarded to another emailaddress
1675      */
1676     public function testSetForwardRuleWithoutCopy()
1677     {
1678         $ruleData = array(array(
1679             'id'            => 1,
1680             'action_type'   => Felamimail_Sieve_Rule_Action::REDIRECT,
1681             'action_argument' => array(
1682                 'emails' => 'someaccount@example.org',
1683                 'copy'   => 0,
1684             ),
1685             'conjunction'     => 'allof',
1686             'conditions'    => array(array(
1687                 'test'          => Felamimail_Sieve_Rule_Condition::TEST_ADDRESS,
1688                 'comperator'    => Felamimail_Sieve_Rule_Condition::COMPERATOR_CONTAINS,
1689                 'header'        => 'From',
1690                 'key'           => 'info@example.org',
1691             )),
1692             'enabled'       => 1,
1693         ));
1694
1695         $this->_sieveTestHelper($ruleData);
1696     }
1697
1698     /**
1699      * testGetVacationTemplates
1700      *
1701      * @return array
1702      */
1703     public function testGetVacationTemplates()
1704     {
1705         $this->_addVacationTemplateFile();
1706         $result = $this->_json->getVacationMessageTemplates();
1707         
1708         $this->assertTrue($result['totalcount'] > 0, 'no templates found');
1709         $found = FALSE;
1710         foreach ($result['results'] as $template) {
1711             if ($template['name'] === $this->_sieveVacationTemplateFile) {
1712                 $found = TRUE;
1713                 break;
1714             }
1715         }
1716         
1717         $this->assertTrue($found, 'wrong templates: ' . print_r($result['results'], TRUE));
1718         
1719         return $template;
1720     }
1721     
1722     /**
1723      * add vacation template file to vfs
1724      */
1725     protected function _addVacationTemplateFile()
1726     {
1727         $webdavRoot = new DAV\ObjectTree(new Tinebase_WebDav_Root());
1728         $path = '/webdav/Felamimail/shared/Vacation Templates';
1729         $node = $webdavRoot->getNodeForPath($path);
1730         $this->_pathsToDelete[] = $path . '/' . $this->_sieveVacationTemplateFile;
1731         $node->createFile($this->_sieveVacationTemplateFile, fopen(dirname(__FILE__) . '/../files/' . $this->_sieveVacationTemplateFile, 'r'));
1732     }
1733     
1734     /**
1735      * testGetVacationMessage
1736      */
1737     public function testGetVacationMessage()
1738     {
1739         $result = $this->_getVacationMessageWithTemplate();
1740         $sclever = Tinebase_User::getInstance()->getFullUserByLoginName('sclever');
1741         $pwulf = Tinebase_User::getInstance()->getFullUserByLoginName('pwulf');
1742         $this->assertEquals("Ich bin vom 18.04.2012 bis zum 20.04.2012 im Urlaub. Bitte kontaktieren Sie<br /> Paul Wulf (" .
1743             $pwulf->accountEmailAddress . ") oder Susan Clever (" .
1744             $sclever->accountEmailAddress . ").<br /><br />I am on vacation until Apr 20, 2012. Please contact Paul Wulf<br />(" .
1745             $pwulf->accountEmailAddress . ") or Susan Clever (" .
1746             $sclever->accountEmailAddress . ") instead.<br /><br />" .
1747             Addressbook_Controller_Contact::getInstance()->getContactByUserId(Tinebase_Core::getUser()->getId())->n_fn, $result['message']);
1748     }
1749     
1750     /**
1751      * get vacation message with template
1752      *
1753      * @return array
1754      */
1755     protected function _getVacationMessageWithTemplate()
1756     {
1757         $template = $this->testGetVacationTemplates();
1758         $sclever = Tinebase_User::getInstance()->getFullUserByLoginName('sclever');
1759         $result = $this->_json->getVacationMessage(array(
1760             'start_date' => '2012-04-18',
1761             'end_date'   => '2012-04-20',
1762             'contact_ids' => array(
1763                 Tinebase_User::getInstance()->getFullUserByLoginName('pwulf')->contact_id,
1764                 $sclever->contact_id,
1765             ),
1766             'template_id' => $template['id'],
1767             'signature' => $this->_account->signature
1768         ));
1769         
1770         return $result;
1771     }
1772     
1773     /**
1774      * testGetVacationWithSignature
1775      *
1776      * @see 0006866: check signature linebreaks in vacation message from template
1777      */
1778     public function testGetVacationWithSignature()
1779     {
1780         $this->_sieveVacationTemplateFile = 'vacation_template_sig.tpl';
1781         
1782         // set signature with <br> + linebreaks
1783         $this->_account->signature = "llalala<br>\nxyz<br>\nblubb<br>";
1784         
1785         $result = $this->_getVacationMessageWithTemplate();
1786         $this->assertContains('-- <br />llalala<br />xyz<br />blubb<br />', $result['message'], 'wrong linebreaks or missing signature');
1787     }
1788     
1789     /**
1790     * testSetVacationWithStartAndEndDate
1791     *
1792     * @see 0006266: automatic deactivation of vacation message
1793     */
1794     public function testSetVacationWithStartAndEndDate()
1795     {
1796         $vacationData = $this->_getVacationData();
1797         $vacationData['start_date'] = '2012-04-18';
1798         $vacationData['end_date'] = '2012-04-20';
1799         $result = $this->_sieveTestHelper($vacationData);
1800         
1801         $this->assertContains($vacationData['start_date'], $result['start_date']);
1802         $this->assertContains($vacationData['end_date'], $result['end_date']);
1803     }
1804     
1805     /**
1806      * testSieveRulesOrder
1807      *
1808      * @see 0007240: order of sieve rules changes when vacation message is saved
1809      */
1810     public function testSieveRulesOrder()
1811     {
1812         $this->_setTestScriptname();
1813         
1814         // disable vacation first
1815         $this->_setDisabledVacation();
1816         
1817         $sieveBackend = Felamimail_Backend_SieveFactory::factory($this->_account->getId());
1818         
1819         $ruleData = $this->_getRuleData();
1820         $ruleData[0]['id'] = $ruleData[2]['id'];
1821         $ruleData[2]['id'] = 11;
1822         $resultSet = $this->_json->saveRules($this->_account->getId(), $ruleData);
1823         $sieveScriptRules = $sieveBackend->getScript($this->_testSieveScriptName);
1824         
1825         $this->_setDisabledVacation();
1826         $sieveScriptVacation = $sieveBackend->getScript($this->_testSieveScriptName);
1827         
1828         // compare sieve scripts
1829         $this->assertContains($sieveScriptRules, $sieveScriptVacation, 'rule order changed');
1830     }
1831     
1832     /**
1833      * use another name for test sieve script
1834      */
1835     protected function _setTestScriptname()
1836     {
1837         $this->_oldActiveSieveScriptName = Felamimail_Controller_Sieve::getInstance()->getActiveScriptName($this->_account->getId());
1838         $this->_testSieveScriptName = 'Felamimail_Unittest';
1839         Felamimail_Controller_Sieve::getInstance()->setScriptName($this->_testSieveScriptName);
1840     }
1841     
1842     /**
1843      * set disabled vacation message
1844      */
1845     protected function _setDisabledVacation()
1846     {
1847         $vacationData = $this->_getVacationData();
1848         $vacationData['enabled'] = FALSE;
1849         $resultSet = $this->_json->saveVacation($vacationData);
1850     }
1851     
1852     /**
1853      * get folder filter
1854      *
1855      * @return array
1856      */
1857     protected function _getFolderFilter()
1858     {
1859         return array(array(
1860             'field' => 'globalname', 'operator' => 'equals', 'value' => ''
1861         ));
1862     }
1863
1864     /**
1865      * get message filter
1866      *
1867      * @param string $_folderId
1868      * @return array
1869      */
1870     protected function _getMessageFilter($_folderId)
1871     {
1872         $result = array(array(
1873             'field' => 'folder_id', 'operator' => 'equals', 'value' => $_folderId
1874         ));
1875         
1876         return $result;
1877     }
1878     
1879     /**
1880      * get mailbox
1881      *
1882      * @param string $name
1883      * @param boolean $createFolder
1884      * @return Felamimail_Model_Folder|NULL
1885      */
1886     protected function _getFolder($name, $createFolder = TRUE)
1887     {
1888         Felamimail_Controller_Cache_Folder::getInstance()->update($this->_account->getId());
1889         try {
1890             $folder = Felamimail_Controller_Folder::getInstance()->getByBackendAndGlobalName($this->_account->getId(), $name);
1891         } catch (Tinebase_Exception_NotFound $tenf) {
1892             $folder = ($createFolder) ? Felamimail_Controller_Folder::getInstance()->create($this->_account, $name) : NULL;
1893         }
1894         
1895         return $folder;
1896     }
1897
1898     /**
1899      * get message data
1900      *
1901      * @return array
1902      */
1903     protected function _getMessageData($_emailFrom = '', $_subject = 'test')
1904     {
1905         return array(
1906             'account_id'    => $this->_account->getId(),
1907             'subject'       => $_subject,
1908             'to'            => [Tinebase_Core::getUser()->accountEmailAddress],
1909             'body'          => 'aaaaaä <br>',
1910             'headers'       => array('X-Tine20TestMessage' => 'jsontest'),
1911             'from_email'    => $_emailFrom,
1912             'content_type'  => Felamimail_Model_Message::CONTENT_TYPE_HTML,
1913         );
1914     }
1915
1916     /**
1917      * send message and return message array
1918      *
1919      * @param string $folderName
1920      * @param array $addtionalHeaders
1921      * @return array
1922      */
1923     protected function _sendMessage($folderName = 'INBOX', $addtionalHeaders = array(), $_emailFrom = '', $_subject = 'test')
1924     {
1925         $messageToSend = $this->_getMessageData($_emailFrom, $_subject);
1926         $messageToSend['headers'] = array_merge($messageToSend['headers'], $addtionalHeaders);
1927         $this->_json->saveMessage($messageToSend);
1928         $this->_foldersToClear = array('INBOX', $this->_account->sent_folder);
1929
1930         $i = 0;
1931         while ($i < 5) {
1932             $result = $this->_getMessages($folderName);
1933             $message = $this->_getMessageFromSearchResult($result, $messageToSend['subject']);
1934             if (! empty($message)) {
1935                 break;
1936             }
1937             // sleep for 1 sec because mailserver may be slower than expected
1938             sleep(1);
1939             $i++;
1940         }
1941
1942         $this->assertTrue(! empty($message), 'Sent message not found.');
1943         
1944         return $message;
1945     }
1946     
1947     /**
1948      * returns message array from result
1949      *
1950      * @param array $_result
1951      * @param string $_subject
1952      * @return array
1953      */
1954     protected function _getMessageFromSearchResult($_result, $_subject)
1955     {
1956         $message = array();
1957         foreach ($_result['results'] as $mail) {
1958             if ($mail['subject'] == $_subject) {
1959                 $message = $mail;
1960             }
1961         }
1962         
1963         return $message;
1964     }
1965     
1966     /**
1967      * get messages from folder
1968      *
1969      * @param string $_folderName
1970      * @return array
1971      */
1972     protected function _getMessages($_folderName = 'INBOX')
1973     {
1974         $folder = $this->_getFolder($_folderName);
1975         $filter = $this->_getMessageFilter($folder->getId());
1976         // update cache
1977         $folder = Felamimail_Controller_Cache_Message::getInstance()->updateCache($folder, 10, 1);
1978         $i = 0;
1979         while ($folder->cache_status != Felamimail_Model_Folder::CACHE_STATUS_COMPLETE && $i < 10) {
1980             $folder = Felamimail_Controller_Cache_Message::getInstance()->updateCache($folder, 10);
1981             $i++;
1982         }
1983         $result = $this->_json->searchMessages($filter, '');
1984
1985         return $result;
1986     }
1987     
1988     /**
1989      * search for message defined by subject in folder
1990      *
1991      * @param string $_subject
1992      * @param string $_folderName
1993      * @return string message data
1994      */
1995     protected function _searchForMessageBySubject($_subject, $_folderName = 'INBOX')
1996     {
1997         // give server some time to send and receive messages
1998         sleep(1);
1999
2000         $result = $this->_getMessages($_folderName);
2001         
2002         $message = array();
2003         foreach ($result['results'] as $mail) {
2004             if ($mail['subject'] == $_subject) {
2005                 $message = $mail;
2006             }
2007         }
2008         $this->assertGreaterThan(0, $result['totalcount'], 'folder is empty');
2009         $this->assertTrue(! empty($message), 'Message not found');
2010         
2011         return $message;
2012     }
2013     
2014     /**
2015      * sieve test helper
2016      *
2017      * @param array $_sieveData
2018      * @return array
2019      */
2020     protected function _sieveTestHelper($_sieveData, $_isMime = FALSE)
2021     {
2022         $this->_setTestScriptname();
2023         
2024         // check which save fn to use
2025         if ((isset($_sieveData['reason']) || array_key_exists('reason', $_sieveData))) {
2026             $resultSet = $this->_json->saveVacation($_sieveData);
2027             $this->assertEquals($this->_account->email, $resultSet['addresses'][0]);
2028             
2029             $_sieveBackend = Felamimail_Backend_SieveFactory::factory($this->_account->getId());
2030             
2031             if (preg_match('/dbmail/i', $_sieveBackend->getImplementation())) {
2032                 $translate = Tinebase_Translation::getTranslation('Felamimail');
2033                 $this->assertEquals(sprintf(
2034                     $translate->_('Out of Office reply from %1$s'), Tinebase_Core::getUser()->accountFullName),
2035                     $resultSet['subject']
2036                 );
2037             } else {
2038                 $this->assertEquals($_sieveData['subject'], $resultSet['subject']);
2039             }
2040             
2041             if ($_isMime) {
2042                 // TODO check why behaviour changed with php 7 (test was relaxed to hotfix this)
2043                 //$this->assertEquals(html_entity_decode('unittest vacation&nbsp;message', ENT_NOQUOTES, 'UTF-8'), $resultSet['reason']);
2044                 self::assertContains('unittest vacation', $resultSet['reason']);
2045             } else {
2046                 $this->assertEquals($_sieveData['reason'], $resultSet['reason']);
2047             }
2048             
2049         } else if ((isset($_sieveData[0]['action_type']) || array_key_exists('action_type', $_sieveData[0]))) {
2050             $resultSet = $this->_json->saveRules($this->_account->getId(), $_sieveData);
2051             $this->assertEquals($_sieveData, $resultSet);
2052         }
2053         
2054         return $resultSet;
2055     }
2056
2057     /**
2058      * search preferences by application felamimail
2059      *
2060      */
2061     public function testSearchFelamimailPreferences()
2062     {
2063         // search prefs
2064         $result = $this->_frontend->searchPreferencesForApplication('Felamimail', '');
2065         
2066         // check results
2067         $this->assertTrue(isset($result['results']));
2068         $this->assertGreaterThan(0, $result['totalcount']);
2069     }
2070     
2071     /**
2072      * testGetRegistryData
2073      *
2074      * @see 0010251: do not send unused config data to client
2075      */
2076     public function testGetRegistryData()
2077     {
2078         $regData = $this->_json->getRegistryData();
2079
2080         $this->assertFalse(isset($regData['defaults']));
2081         $supportedFlags = Felamimail_Config::getInstance()->featureEnabled(Felamimail_Config::FEATURE_TINE20_FLAG)
2082             ? 6
2083             : 5;
2084         $this->assertEquals($supportedFlags, $regData['supportedFlags']['totalcount']);
2085     }
2086
2087     /**
2088      * @see 0002284: add reply-to setting to email account
2089      */
2090     public function testReplyToSetting()
2091     {
2092         $this->_account->reply_to = 'noreply@tine20.org';
2093         $this->_json->saveAccount($this->_account->toArray());
2094
2095         $this->_foldersToClear[] = 'INBOX';
2096         $messageToSend = $this->_getMessageData();
2097         $this->_json->saveMessage($messageToSend);
2098         $message = $this->_searchForMessageBySubject($messageToSend['subject']);
2099
2100         $complete = $this->_json->getMessage($message['id']);
2101         $this->assertTrue(isset($complete['headers']['reply-to']), print_r($complete, true));
2102         $this->assertEquals('"Tine 2.0 Admin Account" <noreply@tine20.org>', $complete['headers']['reply-to']);
2103     }
2104
2105     /**
2106      * Its possible to choice the kind of attachment when adding it.
2107      *
2108      * type = tempfile: uploaded from harddisk, supposed to be a regular attachment
2109      *
2110      * @see 0012950: More attachment methods for mail
2111      */
2112     public function testAttachmentMethodAttachment()
2113     {
2114         $message = $this->_testAttachmentType('tempfile');
2115
2116         self::assertTrue(isset($message['attachments']), 'no attachment set: ' . print_r($message, true));
2117         self::assertEquals(1, count($message['attachments']), 'no attachment set: ' . print_r($message, true));
2118         self::assertEquals('foobar1.txt', $message['attachments'][0]['filename']);
2119         self::assertEquals(16, $message['attachments'][0]['size']);
2120     }
2121
2122     /**
2123      * Its possible to choice the kind of attachment when adding it.
2124      *
2125      * type = download_public: uploaded from harddisk, supposed to be a public download link
2126      *
2127      * @see 0012950: More attachment methods for mail
2128      */
2129     public function testAttachmentMethodPublicDownloadLinkUpload()
2130     {
2131         $message = $this->_testAttachmentType('download_public');
2132
2133         self::assertTrue(isset($message['attachments']), 'attachment set: ' . print_r($message, true));
2134         self::assertEquals(0, count($message['attachments']), 'attachment set: ' . print_r($message, true));
2135         self::assertContains('/download', $message['body'], 'no download link in body: ' . print_r($message, true));
2136     }
2137
2138     /**
2139      * Its possible to choice the kind of attachment when adding it.
2140      *
2141      * type = download_public_fm: chosen from fm, supposed to be a public download link
2142      *
2143      * @see 0012950: More attachment methods for mail
2144      */
2145     public function testAttachmentMethodPublicDownloadLinkFromFilemanager()
2146     {
2147         $message = $this->_testAttachmentType('download_public_fm');
2148
2149         self::assertTrue(isset($message['attachments']), 'attachment set: ' . print_r($message, true));
2150         self::assertEquals(0, count($message['attachments']), 'attachment set: ' . print_r($message, true));
2151         self::assertContains('/download', $message['body'], 'no download link in body: ' . print_r($message, true));
2152     }
2153
2154     /**
2155      * Its possible to choice the kind of attachment when adding it.
2156      *
2157      * type = download_protected: uploaded from harddisk, supposed to be a protected download link
2158      *
2159      * @see 0012950: More attachment methods for mail
2160      */
2161     public function testAttachmentMethodProtectedDownloadLink()
2162     {
2163         $message = $this->_testAttachmentType('download_protected');
2164
2165         self::assertTrue(isset($message['attachments']), 'attachment set: ' . print_r($message, true));
2166         self::assertEquals(0, count($message['attachments']), 'attachment set: ' . print_r($message, true));
2167         self::assertContains('/download', $message['body'], 'no download link in body: ' . print_r($message, true));
2168
2169         // download link id is at the end of message body
2170         $downloadLinkId = trim(substr($message['body'], -46, 40));
2171         $dl = Filemanager_Controller_DownloadLink::getInstance()->get($downloadLinkId);
2172         self::assertTrue(Filemanager_Controller_DownloadLink::getInstance()->validatePassword($dl, 'test'));
2173     }
2174
2175     /**
2176      * Its possible to choice the kind of attachment when adding it.
2177      *
2178      * type = filenode: chosen from fm, thats why type -> file, but the filemanager file is supposed to be used as a regular attachment
2179      *
2180      * @see 0012950: More attachment methods for mail
2181      */
2182     public function testAttachmentMethodFilemanagerNode()
2183     {
2184         $message = $this->_testAttachmentType('filenode');
2185
2186         self::assertTrue(isset($message['attachments']), 'no attachment set: ' . print_r($message, true));
2187         self::assertEquals(1, count($message['attachments']), 'no attachment set: ' . print_r($message, true));
2188         self::assertEquals('test.txt', $message['attachments'][0]['filename']);
2189         self::assertEquals(16, $message['attachments'][0]['size']);
2190     }
2191
2192     /**
2193      * @param $type
2194      * @return array
2195      *
2196      * @throws Filemanager_Exception_NodeExists
2197      * @throws Tinebase_Exception_InvalidArgument
2198      */
2199     protected function _testAttachmentType($type)
2200     {
2201         $this->_foldersToClear = array('INBOX', $this->_account->sent_folder);
2202         $tempfile = $this->_createTempFile('foobar1.txt');
2203
2204         if (in_array($type, array('tempfile', 'download_public', 'download_protected'))) {
2205             // attach uploaded tempfile
2206             $attachment = array(
2207                 'tempFile' => $tempfile->toArray(),
2208                 'name' => 'foobar1.txt',
2209                 'size' => $tempfile->size,
2210                 'type' => 'text/plain',
2211                 'id' => 'eeabe57fd3712a9fe27a34df07cb44cab9e9afb3',
2212                 'attachment_type' => $type,
2213             );
2214
2215         } elseif (in_array($type, array('filenode', 'download_public_fm', 'download_protected_fm'))) {
2216             // attach existing file from filemanager
2217             $nodeController = Filemanager_Controller_Node::getInstance();
2218             $testPath = '/' . Tinebase_FileSystem::FOLDER_TYPE_PERSONAL . '/' . Tinebase_Core::getUser()->accountLoginName . '/testcontainer';
2219             $result = $nodeController->createNodes($testPath, Tinebase_Model_Tree_FileObject::TYPE_FOLDER, array(), FALSE);
2220             $personalNode = $result[0];
2221             $filepath = $personalNode->path . '/test.txt';
2222
2223             // create empty file first (like the js frontend does)
2224             $nodeController->createNodes($filepath, Tinebase_Model_Tree_FileObject::TYPE_FILE, array(), FALSE);
2225             $fmFile = $nodeController->createNodes(
2226                 $filepath,
2227                 Tinebase_Model_Tree_FileObject::TYPE_FILE,
2228                 (array)$tempfile->getId(), TRUE
2229             )->getFirstRecord();
2230
2231             $attachment = [ // chosen from fm, thats why type -> file, but the filemanager file is supposed to be used as a regular attachment
2232                 'name' => $fmFile->name,
2233                 'path' => $fmFile->path,
2234                 'size' => $fmFile->size,
2235                 'type' => $fmFile->contenttype,
2236                 'id' => $fmFile->getId(),
2237                 'attachment_type' => $type,
2238             ];
2239
2240         } else {
2241             throw new Tinebase_Exception_InvalidArgument('invalid type given');
2242         }
2243
2244         if (in_array($type, array('download_protected_fm', 'download_protected'))) {
2245             $attachment['password'] = 'test';
2246         }
2247
2248         $messageToSend = [
2249             'note' => null,
2250             'content_type' => 'text/html',
2251             'account_id' => $this->_account->id,
2252             'to' => [
2253                 $this->_account->email
2254             ],
2255             'cc' => [],
2256             'bcc' => [],
2257             'subject' => 'attachment test [' . $type . ']',
2258             'body' => 'foobar',
2259
2260             'attachments' => [ $attachment ],
2261             'from_email' => 'vagrant@example.org',
2262             'customfields' => [],
2263             'headers' => array('X-Tine20TestMessage' => 'jsontest'),
2264         ];
2265
2266         $this->_json->saveMessage($messageToSend);
2267         $message = $this->_searchForMessageBySubject($messageToSend['subject']);
2268         $complete = $this->_json->getMessage($message['id']);
2269
2270         return $complete;
2271     }
2272
2273     /**
2274      * @param string $tempfileName
2275      * @return Tinebase_Model_TempFile
2276      */
2277     protected function _createTempFile($tempfileName = 'test.txt')
2278     {
2279         $tempfilePath = Tinebase_Core::getTempDir() . DIRECTORY_SEPARATOR . $tempfileName;
2280         file_put_contents($tempfilePath, 'some content');
2281         return Tinebase_TempFile::getInstance()->createTempFile($tempfilePath, $tempfileName);
2282     }
2283 }