0011282: Translate salutation in xml export
[tine20] / tests / tine20 / Addressbook / JsonTest.php
1 <?php
2 /**
3  * Tine 2.0 - http://www.tine20.org
4  *
5  * @package     Addressbook
6  * @license     http://www.gnu.org/licenses/agpl.html
7  * @copyright   Copyright (c) 2008-2014 Metaways Infosystems GmbH (http://www.metaways.de)
8  * @author      Lars Kneschke <l.kneschke@metaways.de>
9  *
10  * @todo        add testSetImage (NOTE: we can't test the upload yet, so we needd to simulate the upload)
11  */
12
13 /**
14  * Test helper
15  */
16 require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'TestHelper.php';
17
18 /**
19  * Test class for Addressbook_Frontend_Json
20  */
21 class Addressbook_JsonTest extends TestCase
22 {
23     /**
24      * set geodata for contacts
25      * 
26      * @var boolean
27      */
28     protected $_geodata = FALSE;
29     
30     /**
31      * instance of test class
32      *
33      * @var Addressbook_Frontend_Json
34      */
35     protected $_instance;
36
37     /**
38      * contacts that should be deleted later
39      *
40      * @var array
41      */
42     protected $_contactIdsToDelete = array();
43
44     /**
45      * customfields that should be deleted later
46      *
47      * @var array
48      */
49     protected $_customfieldIdsToDelete = array();
50
51     /**
52      * @var array test objects
53      */
54     protected $objects = array();
55
56     /**
57      * container to use for the tests
58      *
59      * @var Tinebase_Model_Container
60      */
61     protected $container;
62
63     /**
64      * sclever was made invisible! show her again in tearDown if this is TRUE!
65      * 
66      * @var boolean
67      */
68     protected $_makeSCleverVisibleAgain = FALSE;
69     
70     /**
71      * group ids to delete in tearDown
72      * 
73      * @var array
74      */
75     protected $_groupIdsToDelete = NULL;
76     
77     /**
78      * Runs the test methods of this class.
79      *
80      * @access public
81      * @static
82      */
83     public static function main()
84     {
85         $suite  = new PHPUnit_Framework_TestSuite('Tine 2.0 Addressbook Json Tests');
86         PHPUnit_TextUI_TestRunner::run($suite);
87     }
88
89     /**
90      * Sets up the fixture.
91      * This method is called before a test is executed.
92      *
93      * @access protected
94      */
95     protected function setUp()
96     {
97         $this->_geodata = Addressbook_Controller_Contact::getInstance()->setGeoDataForContacts(false);
98         
99         // always resolve customfields
100         Addressbook_Controller_Contact::getInstance()->resolveCustomfields(TRUE);
101         
102         $this->_instance = new Addressbook_Frontend_Json();
103         
104         $personalContainer = Tinebase_Container::getInstance()->getPersonalContainer(
105             Zend_Registry::get('currentAccount'),
106             'Addressbook',
107             Zend_Registry::get('currentAccount'),
108             Tinebase_Model_Grants::GRANT_EDIT
109         );
110
111         if ($personalContainer->count() === 0) {
112             $this->container = Tinebase_Container::getInstance()->addPersonalContainer(Zend_Registry::get('currentAccount')->accountId, 'Addressbook', 'PHPUNIT');
113         } else {
114             $this->container = $personalContainer[0];
115         }
116
117         // define filter
118         $this->objects['paging'] = array(
119             'start' => 0,
120             'limit' => 10,
121             'sort' => 'n_fileas',
122             'dir' => 'ASC',
123         );
124     }
125
126     /**
127      * Tears down the fixture
128      * This method is called after a test is executed.
129      *
130      * @access protected
131      */
132     protected function tearDown()
133     {
134         Addressbook_Controller_Contact::getInstance()->setGeoDataForContacts($this->_geodata);
135         
136         $this->_instance->deleteContacts($this->_contactIdsToDelete);
137
138         foreach ($this->_customfieldIdsToDelete as $cfd) {
139             Tinebase_CustomField::getInstance()->deleteCustomField($cfd);
140         }
141         
142         if ($this->_makeSCleverVisibleAgain) {
143             $sclever = Tinebase_User::getInstance()->getFullUserByLoginName('sclever');
144             $sclever->visibility = Tinebase_Model_User::VISIBILITY_DISPLAYED;
145             Tinebase_User::getInstance()->updateUser($sclever);
146         }
147         
148         if ($this->_groupIdsToDelete) {
149             Admin_Controller_Group::getInstance()->delete($this->_groupIdsToDelete);
150         }
151         
152         if (! empty($this->objects['createdTagIds'])) {
153             try {
154                 Tinebase_Tags::getInstance()->deleteTags($this->objects['createdTagIds'], TRUE);
155                 $this->objects['createdTagIds'] = array();
156             } catch (Tinebase_Exception_AccessDenied $e) {
157                 $this->objects['createdTagIds'] = array();
158             }
159         }
160     }
161
162     /**
163      * try to get all contacts
164      */
165     public function testGetAllContacts()
166     {
167         $paging = $this->objects['paging'];
168
169         $filter = array(
170             array('field' => 'containerType', 'operator' => 'equals',   'value' => 'all'),
171         );
172         $contacts = $this->_instance->searchContacts($filter, $paging);
173
174         $this->assertGreaterThan(0, $contacts['totalcount']);
175     }
176
177     /**
178      * test search contacts by list
179      */
180     public function testSearchContactsByList()
181     {
182         $paging = $this->objects['paging'];
183
184         $adminListId = Tinebase_Group::getInstance()->getDefaultAdminGroup()->list_id;
185         $filter = array(
186             array('field' => 'list', 'operator' => 'equals',   'value' => $adminListId),
187         );
188         $contacts = $this->_instance->searchContacts($filter, $paging);
189
190         $this->assertGreaterThan(0, $contacts['totalcount']);
191         // check if user in admin list
192         $found = FALSE;
193         foreach ($contacts['results'] as $contact) {
194             if ($contact['account_id'] == Tinebase_Core::getUser()->getId()) {
195                 $found = TRUE;
196                 break;
197             }
198         }
199         $this->assertTrue($found);
200     }
201     
202     /**
203      * testSearchContactsByListValueNull
204      */
205     public function testSearchContactsByListValueNull()
206     {
207         $filter = '[
208             {
209                 "condition": "OR",
210                 "filters": [
211                     {
212                         "condition": "AND",
213                         "filters": [
214                             {
215                                 "field": "list",
216                                 "operator": "equals",
217                                 "value": null,
218                                 "id": "ext-record-102"
219                             }
220                         ],
221                         "id": "ext-comp-1211",
222                         "label": "Kontakte"
223                     }
224                 ]';
225         $contacts = $this->_instance->searchContacts(Zend_Json::decode($filter), NULL);
226         $this->assertGreaterThan(0, $contacts['totalcount']);
227     }
228
229     /**
230     * testSearchContactsWithBadPaging
231     * 
232     * @see 0006214: LIMIT argument offset=-50 is not valid
233     */
234     public function testSearchContactsWithBadPaging()
235     {
236         $paging = $this->objects['paging'];
237         $paging['start'] = -50;
238         $filter = array(
239             array('field' => 'containerType', 'operator' => 'equals',   'value' => 'all'),
240         );
241     
242         $contacts = $this->_instance->searchContacts($filter, $paging);
243         $this->assertGreaterThan(0, $contacts['totalcount']);
244     }
245     
246     /**
247      * try to get contacts by missing container
248      *
249      */
250     public function testGetMissingContainerContacts()
251     {
252         $paging = $this->objects['paging'];
253
254         $filter = array(
255             array('field' => 'container_id', 'operator' => 'equals',   'value' => ''),
256         );
257         $contacts = $this->_instance->searchContacts($filter, $paging);
258
259         $this->assertGreaterThan(0, $contacts['totalcount']);
260     }
261
262     /**
263      * try to get other people contacts
264      *
265      */
266     public function testGetOtherPeopleContacts()
267     {
268         $paging = $this->objects['paging'];
269
270         $filter = array(
271             array('field' => 'containerType', 'operator' => 'equals',   'value' => 'otherUsers'),
272         );
273         $contacts = $this->_instance->searchContacts($filter, $paging);
274
275         $this->assertGreaterThanOrEqual(0, $contacts['totalcount'], 'getting other peoples contacts failed');
276     }
277
278     /**
279      * try to get contacts by owner
280      *
281      */
282     public function testGetContactsByTelephone()
283     {
284         $this->_addContact();
285
286         $paging = $this->objects['paging'];
287
288         $filter = array(
289             array('field' => 'telephone', 'operator' => 'contains', 'value' => '+49TELCELLPRIVATE')
290         );
291         $contacts = $this->_instance->searchContacts($filter, $paging);
292         $this->assertEquals(1, $contacts['totalcount']);
293     }
294
295     /**
296      * add a contact
297      *
298      * @param string $_orgName
299      * @param boolean $_forceCreation
300      * @param array $_tags
301      * @return array contact data
302      */
303     protected function _addContact($_orgName = NULL, $_forceCreation = FALSE, $_tags = NULL)
304     {
305         $newContactData = $this->_getContactData($_orgName);
306         if ($_tags !== NULL) {
307             $newContactData['tags'] = $_tags;
308         }
309         $newContact = $this->_instance->saveContact($newContactData, $_forceCreation);
310         $this->assertEquals($newContactData['n_family'], $newContact['n_family'], 'Adding contact failed');
311
312         $this->_contactIdsToDelete[] = $newContact['id'];
313
314         return $newContact;
315     }
316
317     /**
318      * get contact data
319      *
320      * @param string $_orgName
321      * @return array
322      */
323     protected function _getContactData($_orgName = NULL)
324     {
325         $note = array(
326             'note_type_id'      => 1,
327             'note'              => 'phpunit test note',
328         );
329
330         return array(
331             'n_given'           => 'ali',
332             'n_family'          => 'PHPUNIT',
333             'org_name'          => ($_orgName === NULL) ? Tinebase_Record_Abstract::generateUID() : $_orgName,
334             'container_id'      => $this->container->id,
335             'notes'             => array($note),
336             'tel_cell_private'  => '+49TELCELLPRIVATE',
337         );
338     }
339
340     /**
341      * this test is for Tinebase_Frontend_Json updateMultipleRecords with contact data in the addressbook app
342      */
343     public function testUpdateMultipleRecords()
344     {
345         $companies = array('Janes', 'Johns', 'Bobs');
346         $contacts = array();
347
348         $createdCustomField = $this->_createCustomfield();
349         $changes = array(
350             array('name' => 'url',                    'value' => "http://www.phpunit.de"),
351             array('name' => 'adr_one_region',         'value' => 'PHPUNIT_multipleUpdate'),
352             array('name' => 'customfield_' . $createdCustomField->name, 'value' => 'PHPUNIT_multipleUpdate' )
353         );
354
355         foreach($companies as $company) {
356             $contact = $this->_addContact($company);
357             $contactIds[] = $contact['id'];
358         }
359
360         $filter = array(array('field' => 'id','operator' => 'in', 'value' => $contactIds));
361         $json = new Tinebase_Frontend_Json();
362
363         $result = $json->updateMultipleRecords('Addressbook', 'Contact', $changes, $filter);
364
365         // look if all 3 contacts are updated
366         $this->assertEquals(3, $result['totalcount'],'Could not update the correct number of records');
367
368         // check if default field adr_one_region value was found
369         $sFilter = array(array('field' => 'adr_one_region','operator' => 'equals', 'value' => 'PHPUNIT_multipleUpdate'));
370         $searchResult = $this->_instance->searchContacts($sFilter,$this->objects['paging']);
371
372         // look if all 3 contacts are found again by default field, and check if default field got properly updated
373         $this->assertEquals(3, $searchResult['totalcount'],'Could not find the correct number of records by adr_one_region');
374
375         $record = array_pop($searchResult['results']);
376
377         // check if customfieldvalue was updated properly
378         $this->assertTrue(isset($record['customfields']), 'No customfields in record');
379         $this->assertEquals($record['customfields'][$createdCustomField->name],'PHPUNIT_multipleUpdate','Customfield was not updated as expected');
380
381         // check if other default field value was updated properly
382         $this->assertEquals($record['url'],'http://www.phpunit.de','DefaultField "url" was not updated as expected');
383         
384         // check 'changed' systemnote
385         $this->_checkChangedNote($record['id'], 'adr_one_region ( -> PHPUNIT_multipleUpdate) url ( -> http://www.phpunit.de) customfields ( -> {');
386         
387         // check invalid data
388         
389         $changes = array(
390             array('name' => 'n_family', 'value' => ''),
391             array('name' => 'n_given',  'value' => ''),
392             array('name' => 'org_name', 'value' => '')
393         );
394         $result = $json->updateMultipleRecords('Addressbook', 'Contact', $changes, $filter);
395         
396         $this->assertEquals($result['failcount'], 3, 'failcount does not show the correct number');
397         $this->assertEquals($result['totalcount'], 0, 'totalcount does not show the correct number');
398     }
399     
400     /**
401      * created customfield config
402      * 
403      * @return Tinebase_Model_CustomField_Config
404      */
405     protected function _createCustomfield($cfName = NULL)
406     {
407         $cfName = ($cfName !== NULL) ? $cfName : Tinebase_Record_Abstract::generateUID();
408         $cfc = Tinebase_CustomFieldTest::getCustomField(array(
409             'application_id' => Tinebase_Application::getInstance()->getApplicationByName('Addressbook')->getId(),
410             'model'          => 'Addressbook_Model_Contact',
411             'name'           => $cfName,
412         ));
413         
414         $createdCustomField = Tinebase_CustomField::getInstance()->addCustomField($cfc);
415         $this->_customfieldIdsToDelete[] = $createdCustomField->getId();
416         
417         return $createdCustomField;
418     }
419     
420     /**
421      * test customfield modlog
422      */
423     public function testCustomfieldModlog()
424     {
425         $cf = $this->_createCustomfield();
426         $contact = $this->_addContact();
427         $contact['customfields'][$cf->name] = 'changed value';
428         $result = $this->_instance->saveContact($contact);
429         
430         $this->assertEquals('changed value', $result['customfields'][$cf->name]);
431         $this->_checkChangedNote($result['id'], ' -> {"' . $cf->name . '":"changed value"})');
432     }
433     
434     /**
435      * check 'changed' system note and modlog after tag/customfield update
436      * 
437      * @param string $_recordId
438      * @param string|array $_expectedText
439      * @param integer $_changedNoteNumber
440      */
441     protected function _checkChangedNote($_recordId, $_expectedText = array(), $_changedNoteNumber = 3)
442     {
443         $tinebaseJson = new Tinebase_Frontend_Json();
444         $history = $tinebaseJson->searchNotes(array(array(
445             'field' => 'record_id',
446             'operator' => 'equals',
447             'value' => $_recordId
448         ), array(
449             'field' => "record_model",
450             'operator' => "equals",
451             'value' => 'Addressbook_Model_Contact'
452         )), array(
453             'sort' => array('note_type_id', 'creation_time')
454         ));
455         $this->assertEquals($_changedNoteNumber, $history['totalcount'], print_r($history, TRUE));
456         $changedNote = $history['results'][$_changedNoteNumber - 1];
457         foreach ((array) $_expectedText as $text) {
458             $this->assertContains($text, $changedNote['note'], print_r($changedNote, TRUE));
459         }
460     }
461
462     /**
463      * test tags modlog
464      * 
465      * @return array contact with tag
466      * 
467      * @see 0008546: When edit event, history show "code" ...
468      */
469     public function testTagsModlog()
470     {
471         $contact = $this->_addContact();
472         $tagName = Tinebase_Record_Abstract::generateUID();
473         $tag = array(
474             'type'          => Tinebase_Model_Tag::TYPE_PERSONAL,
475             'name'          => $tagName,
476             'description'    => 'testModlog',
477             'color'         => '#009B31',
478         );
479         $contact['tags'] = array($tag);
480         
481         $result = $this->_instance->saveContact($contact);
482         
483         $this->assertEquals($tagName, $result['tags'][0]['name']);
484         $this->_checkChangedNote($result['id'], array(
485             'tags',
486             '1 ' . Tinebase_Translation::getTranslation('Tinebase')->_('added'),
487         ));
488         
489         return $result;
490     }
491
492     /**
493     * test attach multiple tags modlog
494     */
495     public function testAttachMultipleTagsModlog()
496     {
497         $contact = $this->_addContact();
498         $filter = new Addressbook_Model_ContactFilter(array(array(
499             'field'    => 'id',
500             'operator' => 'equals',
501             'value'    =>  $contact['id']
502         )));
503         $sharedTagName = $this->_createAndAttachTag($filter);
504         $this->_checkChangedNote($contact['id'], array(',"name":"' . $sharedTagName . '","description":"testTagDescription"', 'tags ([] -> [{'));
505     }
506     
507     /**
508     * test detach multiple tags modlog
509     */
510     public function testDetachMultipleTagsModlog()
511     {
512         $contact = $this->testTagsModlog();
513         $contact['tags'] = array();
514         sleep(1); // make sure that the second change always gets last when fetching notes
515         $result = $this->_instance->saveContact($contact);
516         $this->_checkChangedNote($result['id'], array(
517             'tags',
518             '1 ' . Tinebase_Translation::getTranslation('Tinebase')->_('removed'),
519         ), 4);
520     }
521     
522     /**
523      * try to get contacts by owner
524      *
525      */
526     public function testGetContactsByAddressbookId()
527     {
528         $this->_addContact();
529
530         $paging = $this->objects['paging'];
531
532         $filter = array(
533             array('field' => 'containerType', 'operator' => 'equals',   'value' => 'singleContainer'),
534             array('field' => 'container', 'operator' => 'equals',   'value' => $this->container->id),
535         );
536         $contacts = $this->_instance->searchContacts($filter, $paging);
537
538         $this->assertGreaterThan(0, $contacts['totalcount']);
539     }
540
541     /**
542      * try to get contacts by owner / container_id
543      *
544      */
545     public function testGetContactsByOwner()
546     {
547         $this->_addContact();
548
549         $paging = $this->objects['paging'];
550
551         $filter = array(
552             array('field' => 'containerType', 'operator' => 'equals',   'value' => 'personal'),
553             array('field' => 'owner',  'operator' => 'equals',   'value' => Zend_Registry::get('currentAccount')->getId()),
554         );
555         $contacts = $this->_instance->searchContacts($filter, $paging);
556
557         $this->assertGreaterThan(0, $contacts['totalcount']);
558     }
559
560     /**
561      * test getting contact
562      *
563      */
564     public function testGetContact()
565     {
566         $contact = $this->_addContact();
567
568         $contact = $this->_instance->getContact($contact['id']);
569
570         $this->assertEquals('PHPUNIT', $contact['n_family'], 'getting contact failed');
571     }
572
573     /**
574      * test updating of a contact (including geodata)
575      */
576     public function testUpdateContactWithGeodata()
577     {
578         Addressbook_Controller_Contact::getInstance()->setGeoDataForContacts(true);
579         
580         $contact = $this->_addContact();
581
582         $contact['n_family'] = 'PHPUNIT UPDATE';
583         $contact['adr_one_locality'] = 'Hamburg';
584         $contact['adr_one_street'] = 'Pickhuben 2';
585         $updatedContact = $this->_instance->saveContact($contact);
586
587         $this->assertEquals($contact['id'], $updatedContact['id'], 'updated produced a new contact');
588         $this->assertEquals('PHPUNIT UPDATE', $updatedContact['n_family'], 'updating data failed');
589
590         if (Tinebase_Config::getInstance()->get(Tinebase_Config::MAPPANEL, TRUE)) {
591             // check geo data
592             $this->assertEquals(10, round($updatedContact['adr_one_lon']), 'wrong geodata (lon): ' . $updatedContact['adr_one_lon']);
593             $this->assertEquals(54, round($updatedContact['adr_one_lat']), 'wrong geodata (lat): ' . $updatedContact['adr_one_lat']);
594
595             // try another address
596             $updatedContact['adr_one_locality']    = 'Wien';
597             $updatedContact['adr_one_street']      = 'Blindengasse 52';
598             $updatedContact['adr_one_postalcode']  = '1095';
599             $updatedContact['adr_one_countryname'] = '';
600             $updatedContact = $this->_instance->saveContact($updatedContact);
601
602             // check geo data
603             $this->assertEquals(16,   round($updatedContact['adr_one_lon']), 'wrong geodata (lon): ' . $updatedContact['adr_one_lon']);
604             $this->assertEquals(48,   round($updatedContact['adr_one_lat']), 'wrong geodata (lat): ' . $updatedContact['adr_one_lat']);
605             $this->assertEquals('AT',           $updatedContact['adr_one_countryname'], 'wrong country');
606         }
607     }
608
609     /**
610      * test deleting contact
611      *
612      */
613     public function testDeleteContact()
614     {
615         $contact = $this->_addContact();
616
617         $this->_instance->deleteContacts($contact['id']);
618
619         $this->setExpectedException('Tinebase_Exception_NotFound');
620         $contact = $this->_instance->getContact($contact['id']);
621     }
622
623     /**
624      * get all salutations
625      */
626     public function testGetSalutations()
627     {
628         $salutations = Addressbook_Config::getInstance()->contactSalutation;
629         $this->assertGreaterThan(2, count($salutations->records));
630     }
631
632     /**
633      * test export data
634      */
635     public function testExport()
636     {
637         $filter = new Addressbook_Model_ContactFilter(array(array(
638             'field'    => 'n_fileas',
639             'operator' => 'equals',
640             'value'    =>  Tinebase_Core::getUser()->accountDisplayName
641         )));
642         $sharedTagName = $this->_createAndAttachTag($filter);
643         $personalTagName = $this->_createAndAttachTag($filter, Tinebase_Model_Tag::TYPE_PERSONAL);
644
645         // export first and create files array
646         $exporter = new Addressbook_Export_Csv($filter, Addressbook_Controller_Contact::getInstance());
647         $filename = $exporter->generate();
648         $export = file_get_contents($filename);
649         $this->assertContains($sharedTagName, $export, 'shared tag was not found in export:' . $export);
650         $this->assertContains($personalTagName, $export, 'personal tag was not found in export:' . $export);
651
652         // cleanup
653         unset($filename);
654         $sharedTagToDelete = Tinebase_Tags::getInstance()->getTagByName($sharedTagName);
655         $personalTagToDelete = Tinebase_Tags::getInstance()->getTagByName($personalTagName);
656         Tinebase_Tags::getInstance()->deleteTags(array($sharedTagToDelete->getId(), $personalTagToDelete->getId()));
657     }
658
659     /**
660      * tag attachment helper
661      * 
662      * @param Addressbook_Model_ContactFilter $_filter
663      * @param string $_tagType
664      * @return string created tag name
665      */
666     protected function _createAndAttachTag($_filter, $_tagType = Tinebase_Model_Tag::TYPE_SHARED)
667     {
668         $tag = $this->_getTag($_tagType);
669         Tinebase_Tags::getInstance()->attachTagToMultipleRecords($_filter, $tag);
670         
671         return $tag->name;
672     }
673     
674     /**
675      * testExportXlsWithCustomfield
676      * 
677      * @see 0006634: custom fields missing in XLS export
678      */
679     public function testExportXlsWithCustomfield()
680     {
681         $exportCf = $this->_createCustomfield('exportcf');
682         $filter = new Addressbook_Model_ContactFilter(array(array(
683             'field'    => 'n_fileas',
684             'operator' => 'equals',
685             'value'    =>  Tinebase_Core::getUser()->accountDisplayName
686         )));
687         
688         $ownContact = Addressbook_Controller_Contact::getInstance()->getContactByUserId(Tinebase_Core::getUser()->getId());
689         $ownContact->customfields = array('exportcf' => 'testcustomfieldvalue');
690         Addressbook_Controller_Contact::getInstance()->update($ownContact);
691         
692         $definition = dirname(__FILE__) . '/Export/definitions/adb_cf_xls_test.xml';
693         $exporter = new Addressbook_Export_Xls($filter, Addressbook_Controller_Contact::getInstance(), array('definitionFilename' => $definition));
694         $doc = $exporter->generate();
695         
696         $xlswriter = PHPExcel_IOFactory::createWriter($doc, 'CSV');
697         ob_start();
698         $xlswriter->save('php://output');
699         $out = ob_get_clean();
700         
701         $this->assertContains(Tinebase_Core::getUser()->accountDisplayName, $out, 'display name not found.');
702         $this->assertContains('exportcf', $out, 'customfield not found in headline.');
703         $this->assertContains('testcustomfieldvalue', $out, 'customfield value not found.');
704     }
705     
706     /**
707      * testExportXlsWithTranslation
708      *
709      */
710     public function testExportXlsWithTranslation()
711     {
712         $instance = new Tinebase_Frontend_Json();
713         $instance->setLocale('de', FALSE, FALSE);
714         
715         $filter = new Addressbook_Model_ContactFilter(array(array(
716                 'field'    => 'n_fileas',
717                 'operator' => 'equals',
718                 'value'    =>  Tinebase_Core::getUser()->accountDisplayName
719         )));
720         
721         $ownContact = Addressbook_Controller_Contact::getInstance()->getContactByUserId(Tinebase_Core::getUser()->getId());
722         $ownContact->salutation = 'MR';
723         Addressbook_Controller_Contact::getInstance()->update($ownContact);
724         
725         $definition = dirname(__FILE__) . '/Export/definitions/adb_translation_xls_test.xml';
726         $exporter = new Addressbook_Export_Xls($filter, Addressbook_Controller_Contact::getInstance(), array('definitionFilename' => $definition));
727         $doc = $exporter->generate();
728         
729         $xlswriter = PHPExcel_IOFactory::createWriter($doc, 'CSV');
730         ob_start();
731         $xlswriter->save('php://output');
732         $out = ob_get_clean();
733         
734         $this->assertContains(Tinebase_Core::getUser()->accountDisplayName, $out, 'display name not found.');
735         $this->assertContains('Herr', $out, 'no translated salutation found.');
736     }
737     
738     /**
739      * each tag should have an own column
740      */
741     public function testExportOdsWithTagMatrix()
742     {
743         $filter = new Tinebase_Model_TagFilter(array());
744         try {
745             $allTags = Tinebase_Tags::getInstance()->searchTags($filter);
746             if ($allTags->count()) {
747                 Tinebase_Tags::getInstance()->deleteTags($allTags->getId(), TRUE);
748             }
749         } catch (Tinebase_Exception_AccessDenied $e) {
750             $this->markTestSkipped('This fails each 2nd time.');
751         }
752         
753         $controller = Addressbook_Controller_Contact::getInstance();
754         
755         $t1 = $this->_getTag(Tinebase_Model_Tag::TYPE_SHARED, 'tag1');
756         $this->objects['createdTagIds'][] = $t1->getId();
757         
758         $c1 = new Addressbook_Model_Contact(array(
759             'n_given'           => 'ali',
760             'n_family'          => 'PHPUNIT',
761             'org_name'          => 'test',
762             'container_id'      => $this->container->id,
763             'tags' => array($t1)
764         ));
765         
766         $c1 = $controller->create($c1);
767         $this->_contactIdsToDelete[] = $c1->getId();
768         
769         $t2 = $this->_getTag(Tinebase_Model_Tag::TYPE_SHARED, 'tag2');
770         $this->objects['createdTagIds'][] = $t2->getId();
771         
772         // this tag should not occur, this is the addressbook application
773         $crmAppId = Tinebase_Application::getInstance()->getApplicationByName('Crm')->getId();
774         $t3 = $this->_getTag(Tinebase_Model_Tag::TYPE_SHARED, 'tag3', array($crmAppId));
775         $this->objects['createdTagIds'][] = $t3->getId();
776         
777         $c2 = new Addressbook_Model_Contact(array(
778             'n_given'           => 'alisabeth',
779             'n_family'          => 'PHPUNIT',
780             'org_name'          => 'test',
781             'container_id'      => $this->container->id,
782             'tags' => array($t2)
783         ));
784         
785         $c2 = $controller->create($c2);
786         $this->_contactIdsToDelete[] = $c2->getId();
787         
788         $this->assertNotEmpty($c1->tags);
789         $this->assertNotEmpty($c2->tags);
790         
791         $filter = new Addressbook_Model_ContactFilter(array(array(
792             'field'    => 'n_family',
793             'operator' => 'equals',
794             'value'    =>  'PHPUNIT'
795         )));
796         
797         $definition = dirname(__FILE__) . '/Export/definitions/adb_tagmatrix_ods.xml';
798         $exporter = new Addressbook_Export_Ods($filter, Addressbook_Controller_Contact::getInstance(), array('definitionFilename' => $definition));
799         $doc = $exporter->generate();
800         
801         $xml = $this->_getContentXML($doc);
802         
803         $ns = $xml->getNamespaces(true);
804         $spreadsheetXml = $xml->children($ns['office'])->{'body'}->{'spreadsheet'};
805         
806         $headerRowXml = $spreadsheetXml->children($ns['table'])->{'table'}->{'table-row'}->{1};
807         
808         // the tags should exist in the header row
809         $this->assertEquals('tag1', (string) $headerRowXml->children($ns['table'])->{'table-cell'}->{1}->children($ns['text'])->{0});
810         $this->assertEquals('tag2', (string) $headerRowXml->children($ns['table'])->{'table-cell'}->{2}->children($ns['text'])->{0});
811         
812         // if there is no more header column, tag3 is not shown
813         $this->assertEquals(3, (string) $headerRowXml->children($ns['table'])->{'table-cell'}->count());
814     }
815     
816     /**
817      * test import
818      * 
819      * @see 0006226: Data truncated for column 'adr_two_lon'
820      */
821     public function testImport()
822     {
823         $result = $this->_importHelper();
824         $this->assertEquals(2, $result['totalcount'], 'dryrun should detect 2 for import.' . print_r($result, TRUE));
825         $this->assertEquals(0, $result['failcount'], 'Import failed for one or more records.');
826         $this->assertEquals('Müller, Klaus', $result['results'][0]['n_fileas'], 'file as not found');
827
828         // import again without dryrun
829         $result = $this->_importHelper(array('dryrun' => 0));
830         $this->assertEquals(2, $result['totalcount'], 'Didn\'t import anything.');
831         $klaus = $result['results'][0];
832         $this->assertEquals('Import list (' . Tinebase_Translation::dateToStringInTzAndLocaleFormat(Tinebase_DateTime::now(), NULL, NULL, 'date') . ')', $klaus['tags'][0]['name']);
833
834         // import with duplicates
835         $result = $this->_importHelper(array('dryrun' => 0));
836         $this->assertEquals(0, $result['totalcount'], 'Do not import anything.');
837         $this->assertEquals(2, $result['duplicatecount'], 'Should find 2 dups.');
838         $this->assertEquals(1, count($result['exceptions'][0]['exception']['clientRecord']['tags']), '1 autotag expected');
839
840         // import again with clientRecords
841         $klaus['adr_one_locality'] = 'Hamburg';
842         // check that empty filter works correctly, db only accepts NULL for empty value here
843         $klaus['adr_two_lon'] = '';
844         $clientRecords = array(array(
845             'recordData'        => $klaus,
846             'resolveStrategy'   => 'mergeMine',
847             'index'             => 0,
848         ));
849         $result = $this->_importHelper(array('dryrun' => 0), $clientRecords);
850         $this->assertEquals(1, $result['totalcount'], 'Should merge Klaus: ' . print_r($result, TRUE));
851         $this->assertEquals(1, $result['duplicatecount'], 'Fritz is no duplicate.');
852         $this->assertEquals('Hamburg', $result['results'][0]['adr_one_locality'], 'locality should change');
853     }
854
855     /**
856     * test import with discard resolve strategy
857     */
858     public function testImportWithResolveStrategyDiscard()
859     {
860         $result = $this->_importHelper(array('dryrun' => 0));
861         $fritz = $result['results'][1];
862
863         $clientRecords = array(array(
864             'recordData'        => $fritz,
865             'resolveStrategy'   => 'discard',
866             'index'             => 1,
867         ));
868         $result = $this->_importHelper(array('dryrun' => 0), $clientRecords);
869         $this->assertEquals(0, $result['totalcount'], 'Should discard fritz');
870         $this->assertEquals(0, $result['failcount'], 'no failures expected');
871         $this->assertEquals(1, $result['duplicatecount'], 'klaus should still be a duplicate');
872     }
873
874     /**
875     * test import with mergeTheirs resolve strategy
876     * 
877     * @see 0007226: tag handling and other import problems
878     */
879     public function testImportWithResolveStrategyMergeTheirs()
880     {
881         $result = $this->_importHelper(array('dryrun' => 0));
882         $this->assertEquals(2, count($result['results']), 'no import results');
883         $fritz = $result['results'][1];
884         $fritz['tags'][] = array(
885             'name'        => 'new import tag'
886         );
887         $fritz['tel_work'] = '04040';
888
889         $clientRecords = array(array(
890             'recordData'        => $fritz,
891             'resolveStrategy'   => 'mergeTheirs',
892             'index'             => 1,
893         ));
894         $result = $this->_importHelper(array('dryrun' => 0), $clientRecords);
895         $this->assertEquals(1, $result['totalcount'], 'Should merge fritz');
896         $this->assertEquals(2, count($result['results'][0]['tags']), 'Should merge tags');
897         $this->assertEquals(0, $result['failcount'], 'no failures expected');
898         $this->assertEquals(1, $result['duplicatecount'], 'klaus should still be a duplicate');
899         
900         $fritz = $result['results'][0];
901         $fritz['tel_work'] = '04040';
902         // need to adjust user TZ
903         $lastModified = new Tinebase_DateTime($fritz['last_modified_time']);
904         $fritz['last_modified_time'] = $lastModified->setTimezone(Tinebase_Core::getUserTimezone())->toString();
905         $clientRecords = array(array(
906             'recordData'        => $fritz,
907             'resolveStrategy'   => 'mergeTheirs',
908             'index'             => 1,
909         ));
910         $result = $this->_importHelper(array('dryrun' => 0), $clientRecords);
911         $this->assertEquals(1, $result['totalcount'], 'Should merge fritz: ' . print_r($result, TRUE));
912     }
913
914     /**
915      * import helper
916      *
917      * @param array $additionalOptions
918      * @param array $clientRecords
919      * @param string $filename
920      * @return array
921      */
922     protected function _importHelper($additionalOptions = array('dryrun' => 1), $clientRecords = array(), $filename = NULL)
923     {
924         $definition = Tinebase_ImportExportDefinition::getInstance()->getByName('adb_tine_import_csv');
925         $definitionOptions = Tinebase_ImportExportDefinition::getOptionsAsZendConfigXml($definition);
926         
927         $tempFileBackend = new Tinebase_TempFile();
928         $importFile = ($filename) ? $filename : dirname(dirname(dirname(dirname(__FILE__)))) . '/tine20/' . $definitionOptions->example;
929         $tempFile = $tempFileBackend->createTempFile($importFile);
930         $options = array_merge($additionalOptions, array(
931             'container_id'  => $this->container->getId(),
932         ));
933         $result = $this->_instance->importContacts($tempFile->getId(), $definition->getId(), $options, $clientRecords);
934         if (isset($additionalOptions['dryrun']) && $additionalOptions['dryrun'] === 0) {
935             foreach ($result['results'] as $contact) {
936                 $this->_contactIdsToDelete[] = $contact['id'];
937             }
938         }
939
940         return $result;
941     }
942
943     /**
944      * testImportWithTags
945      */
946     public function testImportWithTags()
947     {
948         $options = array(
949             'dryrun'     => 0,
950             'autotags'   => array(array(
951                 'name'            => 'Importliste (19.10.2011)',
952                 'description'    => 'Kontakte der Importliste vom 19.10.2011 um 20.00 Uhr. Bearbeiter: UNITTEST',
953                 'contexts'        => array('Addressbook' => ''),
954                 'type'            => Tinebase_Model_Tag::TYPE_SHARED,
955             )),
956         );
957         $result = $this->_importHelper($options);
958         $fritz = $result['results'][1];
959         
960         //print_r($result);
961         $this->assertEquals(2, count($result['results']), 'should import 2');
962         $this->assertEquals(1, count($result['results'][0]['tags']), 'no tag added');
963         $this->assertEquals('Importliste (19.10.2011)', $result['results'][0]['tags'][0]['name']);
964         
965         $fritz['tags'] = array(array(
966             'name'    => 'supi',
967             'type'    => Tinebase_Model_Tag::TYPE_PERSONAL,
968         ));
969         $fritz = $this->_instance->saveContact($fritz);
970         //print_r($fritz);
971         
972         // once again for duplicates (check if client record has tag)
973         $result = $this->_importHelper($options);
974         //print_r($result);
975         $this->assertEquals(2, count($result['exceptions']), 'should have 2 duplicates');
976         $this->assertEquals(1, count($result['exceptions'][0]['exception']['clientRecord']['tags']), 'no tag added');
977         $this->assertEquals('Importliste (19.10.2011)', $result['exceptions'][0]['exception']['clientRecord']['tags'][0]['name']);
978         $fritzClient = $result['exceptions'][1]['exception']['duplicates'][0];
979         
980         // emulate client merge behaviour
981         $fritzClient['tags'][] = $result['exceptions'][1]['exception']['clientRecord']['tags'][0];
982         $fritzClient['adr_one_locality'] = '';
983         $clientRecords = array(array(
984             'recordData'        => $fritzClient,
985             'resolveStrategy'   => 'mergeMine',
986             'index'             => 1,
987         ));
988         
989         $result = $this->_importHelper(array('dryrun' => 0), $clientRecords);
990         $this->assertEquals(1, $result['totalcount'], 'Should merge fritz: ' . print_r($result['exceptions'], TRUE));
991         $this->assertEquals(2, count($result['results'][0]['tags']), 'Should merge tags');
992         $this->assertEquals(NULL, $result['results'][0]['adr_one_locality'], 'Should remove locality');
993     }
994
995     /**
996     * testImportWithExistingTag
997     */
998     public function testImportWithExistingTag()
999     {
1000         $tag = $this->_getTag(Tinebase_Model_Tag::TYPE_PERSONAL);
1001         $tag = Tinebase_Tags::getInstance()->create($tag);
1002         
1003         $options = array(
1004             'dryrun'     => 0,
1005             'autotags'   => array($tag->getId()),
1006         );
1007         $result = $this->_importHelper($options);
1008         
1009         $this->assertEquals(0, count($result['exceptions']));
1010         $this->assertEquals($tag->name, $result['results'][0]['tags'][0]['name']);
1011     }
1012     
1013     /**
1014     * testImportWithNewTag
1015     */
1016     public function testImportWithNewTag()
1017     {
1018         $tag = $this->_getTag(Tinebase_Model_Tag::TYPE_PERSONAL);
1019         
1020         $options = array(
1021             'dryrun'     => 0,
1022             'autotags'   => array($tag->toArray()),
1023         );
1024         $result = $this->_importHelper($options);
1025         
1026         $this->assertEquals(0, count($result['exceptions']));
1027         $this->assertEquals($tag->name, $result['results'][0]['tags'][0]['name']);
1028     }
1029     
1030     /**
1031      * testImportKeepExistingWithTag
1032      * 
1033      * @see 0006628: tag handling on duplicate resolve actions in import fails
1034      */
1035     public function testImportKeepExistingWithTag()
1036     {
1037         $klaus = $this->_tagImportHelper('discard');
1038         $this->assertEquals(2, count($klaus['tags']), 'klaus should have both tags: ' . print_r($klaus['tags'], TRUE));
1039     }
1040     
1041     /**
1042      * testImportMergeTheirsWithTag
1043      *
1044      */
1045     public function testImportMergeTheirsWithTag()
1046     {
1047         $result = $this->_importHelper(array('dryrun' => 0));
1048         $this->assertTrue(count($result['results']) > 0, 'no record were imported');
1049         $klaus = $result['results'][0];
1050         
1051         $klaus['tags'][] = $this->_getTag()->toArray();
1052         $klaus['adr_one_postalcode'] = '12345';
1053         
1054         $clientRecords = array(array(
1055             'recordData'        => $klaus,
1056             'resolveStrategy'   => 'mergeTheirs',
1057             'index'             => 0,
1058         ));
1059         
1060         $options = array(
1061             'dryrun'     => 0,
1062             'duplicateResolveStrategy' => 'mergeTheirs',
1063         );
1064         
1065         $result = $this->_importHelper($options, $clientRecords);
1066         $this->assertEquals(2, count($result['results'][0]['tags']), 'klaus should have both tags: ' . print_r($result['results'][0], TRUE));
1067         
1068         $klaus = $this->_instance->getContact($klaus['id']);
1069         $this->assertEquals(2, count($klaus['tags']), 'klaus should have both tags: ' . print_r($klaus, TRUE));
1070         $this->assertEquals('12345', $klaus['adr_one_postalcode']);
1071     }
1072     
1073     /**
1074      * helper for import with tags and keep/discard strategy
1075      * 
1076      * @param string $resolveStrategy
1077      * @return array
1078      */
1079     protected function _tagImportHelper($resolveStrategy)
1080     {
1081         $result = $this->_importHelper(array('dryrun' => 0));
1082         $klaus =  $result['results'][0];
1083         $currentTag = $klaus['tags'][0];
1084         $klausId = $klaus['id'];
1085         
1086         if ($resolveStrategy === 'keep') {
1087             unset($klaus['id']);
1088         }
1089         
1090         // keep existing record and discard mine + add new tag
1091         $clientRecords = array(array(
1092             'recordData'        => $klaus,
1093             'resolveStrategy'   => $resolveStrategy,
1094             'index'             => 0,
1095         ));
1096         $tag = $this->_getTag(Tinebase_Model_Tag::TYPE_PERSONAL);
1097         $options = array(
1098             'dryrun'     => 0,
1099             'autotags'   => array($tag->toArray()),
1100         );
1101         
1102         $result = $this->_importHelper($options, $clientRecords);
1103         
1104         $expectedTotalcount = ($resolveStrategy === 'keep') ? 1 : 0;
1105         $this->assertEquals($expectedTotalcount, $result['totalcount'], 'Should discard fritz');
1106         $this->assertEquals(1, $result['duplicatecount'], 'fritz should still be a duplicate');
1107         
1108         $klaus = $this->_instance->getContact($klausId);
1109         
1110         return $klaus;
1111     }
1112
1113     /**
1114      * testImportKeepBothWithTag
1115      * 
1116      * @see 0006628: tag handling on duplicate resolve actions in import fails
1117      */
1118     public function testImportKeepBothWithTag()
1119     {
1120         $klaus = $this->_tagImportHelper('keep');
1121         $this->assertEquals(1, count($klaus['tags']), 'klaus should have only one tag: ' . print_r($klaus['tags'], TRUE));
1122     }
1123     
1124     /**
1125      * testImportTagWithLongName
1126      * 
1127      * @see 0007276: import re-creates tags that have names with more than 40 chars
1128      * @see 0007356: increase tag name size to 256 chars
1129      */
1130     public function testImportTagWithLongName()
1131     {
1132         // import records with long tag name
1133         $result = $this->_importHelper(array('dryrun' => 0), array(), dirname(__FILE__) . '/Import/files/adb_tine_import_with_tag.csv');
1134         
1135         $this->assertEquals(2, $result['totalcount'], 'should import 2 records: ' . print_r($result, TRUE));
1136         $this->assertEquals(2, count($result['results'][0]['tags']), 'record should have 2 tags: ' . print_r($result['results'][0], TRUE));
1137         
1138         // check that tag is only created and added once + remove
1139         $tagName = '2202_Netzwerk_national_potentielle_Partner';
1140         $tags = Tinebase_Tags::getInstance()->searchTags(new Tinebase_Model_TagFilter(array(
1141             'name'  => $tagName,
1142             'grant' => Tinebase_Model_TagRight::VIEW_RIGHT,
1143             'type'  => Tinebase_Model_Tag::TYPE_SHARED
1144         )));
1145         $this->objects['createdTagIds'] = $tags->getArrayOfIds();
1146         $this->assertEquals(1, count($tags), 'tag not found');
1147         $this->assertEquals(2, $tags->getFirstRecord()->occurrence);
1148     }
1149     
1150     /**
1151      * test project relation filter
1152      */
1153     public function testProjectRelationFilter()
1154     {
1155         if (! Setup_Controller::getInstance()->isInstalled('Projects')) {
1156             $this->markTestSkipped('Projects not installed.');
1157         }
1158         
1159         $contact = $this->_instance->saveContact($this->_getContactData());
1160         $project = $this->_getProjectData($contact);
1161
1162         $projectJson = new Projects_Frontend_Json();
1163         $newProject = $projectJson->saveProject($project);
1164
1165         $this->_testProjectRelationFilter($contact, 'definedBy', $newProject);
1166         $this->_testProjectRelationFilter($contact, 'in', $newProject);
1167         $this->_testProjectRelationFilter($contact, 'equals', $newProject);
1168     }
1169
1170     /**
1171      * get Project (create and link project + contacts)
1172      *
1173      * @return array
1174      */
1175     protected function _getProjectData($_contact)
1176     {
1177         $project = array(
1178             'title'         => Tinebase_Record_Abstract::generateUID(),
1179             'description'   => 'blabla',
1180             'status'        => 'IN-PROCESS',
1181         );
1182
1183         $project['relations'] = array(
1184             array(
1185                 'own_model'              => 'Projects_Model_Project',
1186                 'own_backend'            => 'Sql',
1187                 'own_id'                 => 0,
1188                 'own_degree'             => Tinebase_Model_Relation::DEGREE_SIBLING,
1189                 'type'                   => 'COWORKER',
1190                 'related_backend'        => 'Sql',
1191                 'related_id'             => $_contact['id'],
1192                 'related_model'          => 'Addressbook_Model_Contact',
1193                 'remark'                 => NULL,
1194             ),
1195             array(
1196                 'own_model'              => 'Projects_Model_Project',
1197                 'own_backend'            => 'Sql',
1198                 'own_id'                 => 0,
1199                 'own_degree'             => Tinebase_Model_Relation::DEGREE_SIBLING,
1200                 'type'                   => 'RESPONSIBLE',
1201                 'related_backend'        => 'Sql',
1202                 'related_id'             => Tinebase_Core::getUser()->contact_id,
1203                 'related_model'          => 'Addressbook_Model_Contact',
1204                 'remark'                 => NULL,
1205             )
1206
1207         );
1208
1209         return $project;
1210     }
1211
1212     /**
1213      * helper for project relation filter test
1214      *
1215      * @param array $_contact
1216      * @param string
1217      * @param array $_project
1218      */
1219     protected function _testProjectRelationFilter($_contact, $_operator, $_project)
1220     {
1221         switch ($_operator) {
1222             case 'definedBy':
1223                 $closedStatus = Projects_Config::getInstance()->get(Projects_Config::PROJECT_STATUS)->records->filter('is_open', 0);
1224                 $filters = array(
1225                     array('field' => ":relation_type", "operator" => "equals", "value" => "COWORKER"),
1226                     array('field' => "status",         "operator" => "notin",  "value" => $closedStatus->getId()),
1227                     array('field' => 'id',             'operator' =>'in',      'value' => array($_project['id']))
1228                 );
1229                 break;
1230             case 'in':
1231                 $filters = array(array('field' => 'id', 'operator' => $_operator, 'value' => array($_project['id'])));
1232                 break;
1233             case 'equals':
1234                 $filters = array(array('field' => 'id', 'operator' => $_operator, 'value' => $_project['id']));
1235                 break;
1236         }
1237
1238         $filterId = Tinebase_Record_Abstract::generateUID();
1239         $filter = array(
1240             array(
1241                 'field'     => 'foreignRecord',
1242                 'operator'  => 'AND',
1243                 'id'        => $filterId,
1244                 'value' => array(
1245                     'linkType'      => 'relation',
1246                     'appName'       => 'Projects',
1247                     'modelName'     => 'Project',
1248                     'filters'       => $filters
1249                 )
1250             ),
1251             array('field' => 'id', 'operator' => 'in', 'value' => array($_contact['id'], Tinebase_Core::getUser()->contact_id)),
1252         );
1253         $result = $this->_instance->searchContacts($filter, array());
1254
1255         $this->assertEquals('relation', $result['filter'][0]['value']['linkType']);
1256         $this->assertTrue(isset($result['filter'][0]['id']), 'id expected');
1257         $this->assertEquals($filterId, $result['filter'][0]['id']);
1258
1259         if ($_operator === 'definedBy') {
1260             $this->assertEquals(':relation_type',        $result['filter'][0]['value']['filters'][0]['field']);
1261             $this->assertEquals(1, $result['totalcount'], 'Should find only the COWORKER!');
1262             $this->assertEquals($_contact['org_name'], $result['results'][0]['org_name']);
1263         } else {
1264             $this->assertEquals(2, $result['totalcount'], 'Should find both contacts!');
1265         }
1266     }
1267
1268     /**
1269      * testAttenderForeignIdFilter
1270      */
1271     public function testAttenderForeignIdFilter()
1272     {
1273         $contact = $this->_addContact();
1274         $event = $this->_getEvent($contact);
1275         Calendar_Controller_Event::getInstance()->create($event);
1276
1277         $filter = array(
1278             array(
1279                 'field' => 'foreignRecord',
1280                 'operator' => 'AND',
1281                 'value' => array(
1282                     'linkType'      => 'foreignId',
1283                     'appName'       => 'Calendar',
1284                     'filterName'    => 'ContactAttendeeFilter',
1285                     'modelName'     => 'Event',
1286                     'filters'       => array(
1287                         array('field' => "period",            "operator" => "within", "value" => array(
1288                             'from'  => '2009-01-01 00:00:00',
1289                             'until' => '2010-12-31 23:59:59',
1290                         )),
1291                         array('field' => 'attender_status',   "operator" => "in",  "value" => array('NEEDS-ACTION', 'ACCEPTED')),
1292                         array('field' => 'attender_role',     "operator" => "in",  "value" => array('REQ')),
1293                     )
1294                 )
1295             ),
1296             array('field' => 'id', 'operator' => 'in', 'value' => array(Tinebase_Core::getUser()->contact_id, $contact['id']))
1297         );
1298         $result = $this->_instance->searchContacts($filter, array());
1299         $this->assertEquals('foreignRecord', $result['filter'][0]['field']);
1300         $this->assertEquals('foreignId', $result['filter'][0]['value']['linkType']);
1301         $this->assertEquals('ContactAttendeeFilter', $result['filter'][0]['value']['filterName']);
1302         $this->assertEquals('Event', $result['filter'][0]['value']['modelName']);
1303
1304         $this->assertEquals(1, $result['totalcount']);
1305         $this->assertEquals(Tinebase_Core::getUser()->contact_id, $result['results'][0]['id']);
1306     }
1307
1308     /**
1309      * testOrganizerForeignIdFilter
1310      */
1311     public function testOrganizerForeignIdFilter()
1312     {
1313         $contact = $this->_addContact();
1314         $event = $this->_getEvent($contact);
1315         Calendar_Controller_Event::getInstance()->create($event);
1316
1317         $filter = array(
1318             $this->_getOrganizerForeignIdFilter(),
1319             array('field' => 'id', 'operator' => 'in', 'value' => array(Tinebase_Core::getUser()->contact_id, $contact['id']))
1320         );
1321         $result = $this->_instance->searchContacts($filter, array());
1322
1323         $this->assertEquals(1, $result['totalcount']);
1324         $this->assertEquals(Tinebase_Core::getUser()->contact_id, $result['results'][0]['id']);
1325     }
1326
1327     /**
1328      * return event organizuer filter
1329      *
1330      * @return array
1331      */
1332     protected function _getOrganizerForeignIdFilter()
1333     {
1334         return array(
1335             'field' => 'foreignRecord',
1336             'operator' => 'AND',
1337             'value' => array(
1338                 'linkType'      => 'foreignId',
1339                 'appName'       => 'Calendar',
1340                 'filterName'    => 'ContactOrganizerFilter',
1341                 'filters'       => array(
1342                     array('field' => "period",            "operator" => "within", "value" => array(
1343                         'from'  => '2009-01-01 00:00:00',
1344                         'until' => '2010-12-31 23:59:59',
1345                     )),
1346                     array('field' => 'organizer',   "operator" => "equals",  "value" => Tinebase_Core::getUser()->contact_id),
1347                 )
1348             )
1349         );
1350     }
1351
1352     /**
1353      * testOrganizerForeignIdFilterWithOrCondition
1354      */
1355     public function testOrganizerForeignIdFilterWithOrCondition()
1356     {
1357         $contact = $this->_addContact();
1358         $event = $this->_getEvent($contact);
1359         Calendar_Controller_Event::getInstance()->create($event);
1360
1361         $filter = array(array(
1362             'condition' => 'OR',
1363             'filters'   => array(
1364                 $this->_getOrganizerForeignIdFilter(),
1365                 array('field' => 'id', 'operator' => 'in', 'value' => array($contact['id']))
1366             )
1367         ));
1368         $result = $this->_instance->searchContacts($filter, array());
1369
1370         $this->assertEquals(2, $result['totalcount'], 'expected 2 contacts');
1371     }
1372
1373     /**
1374      * returns a simple event
1375      *
1376      * @param array $_contact
1377      * @return Calendar_Model_Event
1378      */
1379     protected function _getEvent($_contact)
1380     {
1381         $testCalendar = Tinebase_Container::getInstance()->addContainer(new Tinebase_Model_Container(array(
1382             'name'           => 'PHPUnit test calendar',
1383             'type'           => Tinebase_Model_Container::TYPE_PERSONAL,
1384             'backend'        => 'Sql',
1385             'application_id' => Tinebase_Application::getInstance()->getApplicationByName('Calendar')->getId()
1386         ), true));
1387
1388         return new Calendar_Model_Event(array(
1389             'summary'     => 'Wakeup',
1390             'dtstart'     => '2009-03-25 06:00:00',
1391             'dtend'       => '2009-03-25 06:15:00',
1392             'description' => 'Early to bed and early to rise, makes a men healthy, wealthy and wise',
1393             'attendee'    => new Tinebase_Record_RecordSet('Calendar_Model_Attender', array(
1394                 array (
1395                     'user_id'        => Tinebase_Core::getUser()->contact_id,
1396                     'user_type'      => Calendar_Model_Attender::USERTYPE_USER,
1397                     'role'           => Calendar_Model_Attender::ROLE_REQUIRED,
1398                     'status_authkey' => Tinebase_Record_Abstract::generateUID(),
1399                 ),
1400                 array (
1401                     'user_id'        => $_contact['id'],
1402                     'user_type'      => Calendar_Model_Attender::USERTYPE_USER,
1403                     'role'           => Calendar_Model_Attender::ROLE_OPTIONAL,
1404                     'status_authkey' => Tinebase_Record_Abstract::generateUID(),
1405                 ),
1406             )),
1407
1408             'container_id' => $testCalendar->getId(),
1409             'organizer'    => Tinebase_Core::getUser()->contact_id,
1410             'uid'          => Calendar_Model_Event::generateUID(),
1411
1412             Tinebase_Model_Grants::GRANT_READ    => true,
1413             Tinebase_Model_Grants::GRANT_EDIT    => true,
1414             Tinebase_Model_Grants::GRANT_DELETE  => true,
1415         ));
1416     }
1417
1418     /**
1419      * testDuplicateCheck
1420      */
1421     public function testDuplicateCheck($_duplicateCheck = TRUE)
1422     {
1423         $contact = $this->_addContact();
1424         try {
1425             $this->_addContact($contact['org_name'], $_duplicateCheck);
1426             $this->assertFalse($_duplicateCheck, 'duplicate detection failed');
1427         } catch (Tinebase_Exception_Duplicate $ted) {
1428             $this->assertTrue($_duplicateCheck, 'force creation failed');
1429             $exceptionData = $ted->toArray();
1430             $this->assertEquals(1, count($exceptionData['duplicates']), print_r($exceptionData['duplicates'], TRUE));
1431             $this->assertEquals($contact['n_given'], $exceptionData['duplicates'][0]['n_given']);
1432             $this->assertEquals($contact['org_name'], $exceptionData['duplicates'][0]['org_name']);
1433         }
1434     }
1435     
1436     /**
1437     * testDuplicateCheckWithTag
1438     */
1439     public function testDuplicateCheckWithTag()
1440     {
1441         $tagName = Tinebase_Record_Abstract::generateUID();
1442         $tag = array(
1443             'type'          => Tinebase_Model_Tag::TYPE_PERSONAL,
1444             'name'          => $tagName,
1445             'description'    => 'testModlog',
1446             'color'         => '#009B31',
1447         );
1448         $contact = $this->_addContact(NULL, FALSE, array($tag));
1449         
1450         unset($contact['id']);
1451         // replace tag array with single tag id (like the client does)
1452         $contact['tags'] = array($contact['tags'][0]['id']);
1453         try {
1454             $newContact = $this->_instance->saveContact($contact, TRUE);
1455             $this->assertTrue(FALSE, 'duplicate detection failed');
1456         } catch (Tinebase_Exception_Duplicate $ted) {
1457             $exceptionData = $ted->toArray();
1458             $this->assertEquals(1, count($exceptionData['clientRecord']['tags']), print_r($exceptionData['duplicates'], TRUE));
1459             $this->assertTrue(is_array($exceptionData['clientRecord']['tags'][0]), 'array of tag data expected: ' . print_r($exceptionData['clientRecord']['tags'], TRUE));
1460             $this->assertEquals(1, count($exceptionData['duplicates'][0]['tags']), print_r($exceptionData['duplicates'], TRUE));
1461             $this->assertTrue(is_array($exceptionData['duplicates'][0]['tags'][0]));
1462         }
1463     }
1464     
1465     /**
1466      * testDuplicateCheckWithEmail
1467      */
1468     public function testDuplicateCheckWithEmail()
1469     {
1470         $contact = $this->_getContactData();
1471         $contact['email'] = 'test@example.org';
1472         $contact = $this->_instance->saveContact($contact);
1473         $this->_contactIdsToDelete[] = $contact['id'];
1474         try {
1475             $contact2 = $this->_getContactData();
1476             $contact2['email'] = 'test@example.org';
1477             $contact2 = $this->_instance->saveContact($contact2);
1478             $this->_contactIdsToDelete[] = $contact2['id'];
1479             $this->assertTrue(FALSE, 'no duplicate exception');
1480         } catch (Tinebase_Exception_Duplicate $ted) {
1481             $exceptionData = $ted->toArray();
1482             $this->assertEquals(1, count($exceptionData['duplicates']));
1483             $this->assertEquals($contact['email'], $exceptionData['duplicates'][0]['email']);
1484         }
1485     }
1486
1487     /**
1488      * testForceCreation
1489      */
1490     public function testForceCreation()
1491     {
1492         $this->testDuplicateCheck(FALSE);
1493     }
1494
1495     /**
1496      * testImportDefinitionsInRegistry
1497      */
1498     public function testImportDefinitionsInRegistry()
1499     {
1500         $registryData = $this->_instance->getRegistryData();
1501
1502         $this->assertEquals('adb_tine_import_csv', $registryData['defaultImportDefinition']['name']);
1503         $this->assertTrue(is_array($registryData['importDefinitions']['results']));
1504
1505         $options = $registryData['defaultImportDefinition']['plugin_options'];
1506         $this->assertTrue(is_array($options));
1507         $this->assertEquals('Addressbook_Model_Contact', $options['model']);
1508         $this->assertTrue(is_array($options['autotags']));
1509         $this->assertEquals('Import list (###CURRENTDATE###)', $options['autotags'][0]['name']);
1510     }
1511
1512     /**
1513      * testSearchContactsWithTagIsNotFilter
1514      */
1515     public function testSearchContactsWithTagIsNotFilter()
1516     {
1517         $allContacts = $this->_instance->searchContacts(array(), array());
1518
1519         $filter = new Addressbook_Model_ContactFilter(array(array(
1520             'field'    => 'n_fileas',
1521             'operator' => 'equals',
1522             'value'    =>  Tinebase_Core::getUser()->accountDisplayName
1523         )));
1524         $sharedTagName = Tinebase_Record_Abstract::generateUID();
1525         $tag = new Tinebase_Model_Tag(array(
1526             'type'  => Tinebase_Model_Tag::TYPE_SHARED,
1527             'name'  => $sharedTagName,
1528             'description' => 'testImport',
1529             'color' => '#009B31',
1530         ));
1531         $tag = Tinebase_Tags::getInstance()->attachTagToMultipleRecords($filter, $tag);
1532
1533         $filter = array(array(
1534             'field'    => 'tag',
1535             'operator' => 'not',
1536             'value'    => $tag->getId()
1537         ));
1538         $allContactsWithoutTheTag = $this->_instance->searchContacts($filter, array());
1539
1540         $this->assertTrue(count($allContactsWithoutTheTag['totalcount']) > 0);
1541         $this->assertEquals($allContacts['totalcount']-1, $allContactsWithoutTheTag['totalcount']);
1542
1543         $sharedTagToDelete = Tinebase_Tags::getInstance()->getTagByName($sharedTagName);
1544     }
1545     
1546     /**
1547      * test search with tag filter with 'in' operator
1548      */
1549     public function testSearchContactsWithTagInFilter()
1550     {
1551         $filter = new Addressbook_Model_ContactFilter(array(array(
1552             'field'    => 'n_fileas',
1553             'operator' => 'equals',
1554             'value'    =>  Tinebase_Core::getUser()->accountDisplayName
1555         )));
1556         $sharedTagName = Tinebase_Record_Abstract::generateUID();
1557         $tag = new Tinebase_Model_Tag(array(
1558                     'type'  => Tinebase_Model_Tag::TYPE_SHARED,
1559                     'name'  => $sharedTagName,
1560                     'description' => 'testImport',
1561                     'color' => '#009B31',
1562         ));
1563         $tag = Tinebase_Tags::getInstance()->attachTagToMultipleRecords($filter, $tag);
1564         $filter = array(array(
1565             'field'    => 'tag',
1566             'operator' => 'in',
1567             'value'    => array($tag->getId())
1568         ));
1569         $allContactsWithTheTag = $this->_instance->searchContacts($filter, array());
1570         $this->assertEquals(1, $allContactsWithTheTag['totalcount']);
1571
1572         $filter = array(array(
1573             'field'    => 'tag',
1574             'operator' => 'in',
1575             'value'    => array()
1576         ));
1577         $emptyResult = $this->_instance->searchContacts($filter, array());
1578         $this->assertEquals(0, $emptyResult['totalcount']);
1579     }
1580     
1581     /**
1582     * testParseAddressData
1583     */
1584     public function testParseAddressData()
1585     {
1586         $addressString = "Dipl.-Inf. (FH) Philipp Schüle
1587 Core Developer
1588 Metaways Infosystems GmbH
1589 Pickhuben 2, D-20457 Hamburg
1590
1591 E-Mail: p.schuele@metaways.de
1592 Web: http://www.metaways.de
1593 Tel: +49 (0)40 343244-232
1594 Fax: +49 (0)40 343244-222";
1595         
1596         $result = $this->_instance->parseAddressData($addressString);
1597         
1598         $this->assertTrue((isset($result['contact']) || array_key_exists('contact', $result)));
1599         $this->assertTrue(is_array($result['contact']));
1600         $this->assertTrue((isset($result['unrecognizedTokens']) || array_key_exists('unrecognizedTokens', $result)));
1601         $this->assertTrue(count($result['unrecognizedTokens']) > 10 && count($result['unrecognizedTokens']) < 13,
1602             'unrecognizedTokens number mismatch: ' . count('unrecognizedTokens'));
1603         $this->assertEquals('p.schuele@metaways.de', $result['contact']['email']);
1604         $this->assertEquals('Pickhuben 2', $result['contact']['adr_one_street']);
1605         $this->assertEquals('Hamburg', $result['contact']['adr_one_locality']);
1606         $this->assertEquals('Metaways Infosystems GmbH', $result['contact']['org_name']);
1607         $this->assertEquals('+49 (0)40 343244-232', $result['contact']['tel_work']);
1608         $this->assertEquals('http://www.metaways.de', $result['contact']['url']);
1609     }
1610
1611     /**
1612     * testParseAnotherAddressData
1613     * 
1614     * @see http://forge.tine20.org/mantisbt/view.php?id=5800
1615     */
1616     public function testParseAnotherAddressData()
1617     {
1618         // NOTE: on some systems the /u modifier fails
1619         if (! preg_match('/\w/u', 'ä')) {
1620             $this->markTestSkipped('preg_match has no unicode support');
1621         }
1622         
1623         $addressString = "Straßenname 25 · 23569 Lübeck
1624 Steuernummer 33/111/32212";
1625         
1626         $result = $this->_instance->parseAddressData($addressString);
1627         $this->assertEquals('Straßenname 25', $result['contact']['adr_one_street'], 'wrong street: ' . print_r($result, TRUE));
1628         $this->assertEquals('Lübeck', $result['contact']['adr_one_locality'], 'wrong street: ' . print_r($result, TRUE));
1629     }
1630     
1631     /**
1632      * testContactDisabledFilter
1633      */
1634     public function testContactDisabledFilter()
1635     {
1636         $this->_makeSCleverVisibleAgain = TRUE;
1637         
1638         // hide sclever from adb
1639         $sclever = Tinebase_User::getInstance()->getFullUserByLoginName('sclever');
1640         $sclever->visibility = Tinebase_Model_User::VISIBILITY_HIDDEN;
1641         Tinebase_User::getInstance()->updateUser($sclever);
1642         
1643         // search for her with ContactDisabledFilter
1644         $filter = array(array('field' => 'n_given',      'operator' => 'equals',   'value' => 'Susan'));
1645         $result = $this->_instance->searchContacts($filter, array());
1646         $this->assertEquals(0, $result['totalcount'], 'found contacts: ' . print_r($result, true));
1647         
1648         $filter[] = array('field' => 'showDisabled', 'operator' => 'equals',   'value' => TRUE);
1649         $result = $this->_instance->searchContacts($filter, array());
1650         $this->assertEquals(1, $result['totalcount']);
1651     }
1652     
1653     /**
1654      * test search hidden list -> should not appear
1655      * 
1656      * @see 0006934: setting a group that is hidden from adb as attendee filter throws exception
1657      */
1658     public function testSearchHiddenList()
1659     {
1660         $hiddenGroup = new Tinebase_Model_Group(array(
1661             'name'          => 'hiddengroup',
1662             'description'   => 'hidden group',
1663             'visibility'    => Tinebase_Model_Group::VISIBILITY_HIDDEN
1664         ));
1665         
1666         try {
1667             $hiddenGroup = Admin_Controller_Group::getInstance()->create($hiddenGroup);
1668         } catch (Exception $e) {
1669             // group already exists
1670             $hiddenGroup = Tinebase_Group::getInstance()->getGroupByName($hiddenGroup->name);
1671         }
1672         
1673         $this->_groupIdsToDelete = array($hiddenGroup->getId());
1674         
1675         $filter = array(array(
1676             'field'    => 'name',
1677             'operator' => 'equals',
1678             'value'    => 'hiddengroup'
1679         ));
1680         
1681         $result = $this->_instance->searchLists($filter, array());
1682         $this->assertEquals(0, $result['totalcount'], 'should not find hidden list: ' . print_r($result, TRUE));
1683     }
1684 }