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

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