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