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