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