Merge branch '2015.11' into 2015.11-develop
[tine20] / tests / tine20 / Calendar / JsonTests.php
1 <?php
2 /**
3  * Tine 2.0 - http://www.tine20.org
4  * 
5  * @package     Calendar
6  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
7  * @copyright   Copyright (c) 2009-2014 Metaways Infosystems GmbH (http://www.metaways.de)
8  * @author      Cornelius Weiss <c.weiss@metaways.de>
9  */
10
11 /**
12  * Test class for Json Frontend
13  * 
14  * @package     Calendar
15  */
16 class Calendar_JsonTests extends Calendar_TestCase
17 {
18     /**
19      * Calendar Json Object
20      *
21      * @var Calendar_Frontend_Json
22      */
23     protected $_uit = null;
24     
25     /**
26      * (non-PHPdoc)
27      * @see Calendar/Calendar_TestCase::setUp()
28      */
29     public function setUp()
30     {
31         parent::setUp();
32         
33         Calendar_Controller_Event::getInstance()->doContainerACLChecks(true);
34         
35         $this->_uit = new Calendar_Frontend_Json();
36     }
37     
38     /**
39      * testGetRegistryData
40      */
41     public function testGetRegistryData()
42     {
43         // enforce fresh instance of calendar preferences
44         Tinebase_Core::set(Tinebase_Core::PREFERENCES, array());
45         
46         $registryData = $this->_uit->getRegistryData();
47         
48         $this->assertTrue(is_array($registryData['defaultContainer']['account_grants']));
49         $this->assertTrue(is_array($registryData['defaultContainer']['ownerContact']));
50     }
51
52     /**
53      * test shared calendar as default
54      * @see 0011986: Default Calender in Preferences restet to personal one after logout/login
55      */
56     public function testGetRegistryDataWithSharedDefault()
57     {
58         $fe = new Tinebase_Frontend_Json_Container();
59         $container = $fe->addContainer('Calendar', 'testdeletecontacts', Tinebase_Model_Container::TYPE_SHARED, '');
60
61         Tinebase_Core::set(Tinebase_Core::PREFERENCES, array());
62         Tinebase_Core::getPreference('Calendar')
63             ->setValue(Calendar_Preference::DEFAULTCALENDAR, $container['id']);
64
65         $registryData = $this->_uit->getRegistryData();
66
67         $this->assertTrue(is_array($registryData['defaultContainer']['account_grants']));
68         $this->assertFalse(isset($registryData['defaultContainer']['ownerContact']));
69
70         Tinebase_Core::getPreference('Calendar')
71             ->deleteUserPref(Calendar_Preference::DEFAULTCALENDAR);
72     }
73
74     /**
75      * testCreateEvent
76      * 
77      * @param $now should the current date be used
78      */
79     public function testCreateEvent($now = FALSE)
80     {
81         $scleverDisplayContainerId = Tinebase_Core::getPreference('Calendar')->getValueForUser(Calendar_Preference::DEFAULTCALENDAR, $this->_getPersona('sclever')->getId());
82         $contentSeqBefore = Tinebase_Container::getInstance()->getContentSequence($scleverDisplayContainerId);
83         
84         $eventData = $this->_getEvent($now)->toArray();
85         
86         $tag = Tinebase_Tags::getInstance()->createTag(new Tinebase_Model_Tag(array(
87             'name' => 'phpunit-' . substr(Tinebase_Record_Abstract::generateUID(), 0, 10),
88             'type' => Tinebase_Model_Tag::TYPE_PERSONAL
89         )));
90         $eventData['tags'] = array($tag->toArray());
91         
92         $note = new Tinebase_Model_Note(array(
93             'note'         => 'very important note!',
94             'note_type_id' => Tinebase_Notes::getInstance()->getNoteTypes()->getFirstRecord()->getId(),
95         ));
96         $eventData['notes'] = array($note->toArray());
97         $eventData['etag'] = Tinebase_Record_Abstract::generateUID();
98         
99         $persistentEventData = $this->_uit->saveEvent($eventData);
100         $loadedEventData = $this->_uit->getEvent($persistentEventData['id']);
101         
102         $this->_assertJsonEvent($eventData, $loadedEventData, 'failed to create/load event');
103         $this->assertEquals($eventData['etag'], $loadedEventData['etag']);
104         
105         $contentSeqAfter = Tinebase_Container::getInstance()->getContentSequence($scleverDisplayContainerId);
106         $this->assertEquals($contentSeqBefore + 1, $contentSeqAfter,
107             'content sequence of display container should be increased by 1:' . $contentSeqAfter);
108         $this->assertEquals($contentSeqAfter, Tinebase_Container::getInstance()->get($scleverDisplayContainerId)->content_seq);
109         
110         return $loadedEventData;
111     }
112     
113     public function testStripWindowsLinebreaks()
114     {
115         $e = $this->_getEvent(TRUE);
116         $e->description = 'Hello my friend,' . chr(13) . chr(10) .'bla bla bla.'  . chr(13) . chr(10) .'good bye.';
117         $persistentEventData = $this->_uit->saveEvent($e->toArray());
118         $loadedEventData = $this->_uit->getEvent($persistentEventData['id']);
119         $this->assertEquals($loadedEventData['description'], 'Hello my friend,' . chr(10) . 'bla bla bla.' . chr(10) . 'good bye.');
120     }
121
122     /**
123     * testCreateEventWithNonExistantAttender
124     */
125     public function testCreateEventWithNonExistantAttender()
126     {
127         $testEmail = 'unittestnotexists@example.org';
128         $eventData = $this->_getEvent(TRUE)->toArray();
129         $eventData['attendee'][] = $this->_getUserTypeAttender($testEmail);
130         
131         $persistentEventData = $this->_uit->saveEvent($eventData);
132         $found = FALSE;
133         foreach ($persistentEventData['attendee'] as $attender) {
134             if ($attender['user_id']['email'] === $testEmail) {
135                 $this->assertEquals($testEmail, $attender['user_id']['n_fn']);
136                 $found = TRUE;
137             }
138         }
139         $this->assertTrue($found);
140     }
141     
142     /**
143      * get single attendee array
144      * 
145      * @param string $email
146      * @return array
147      */
148     protected function _getUserTypeAttender($email = 'unittestnotexists@example.org')
149     {
150         return array(
151             'user_id'        => $email,
152             'user_type'      => Calendar_Model_Attender::USERTYPE_USER,
153             'role'           => Calendar_Model_Attender::ROLE_REQUIRED,
154         );
155     }
156     
157     /**
158      * test create event with alarm
159      *
160      * @todo add testUpdateEventWithAlarm
161      */
162     public function testCreateEventWithAlarm()
163     {
164         $eventData = $this->_getEventWithAlarm(TRUE)->toArray();
165         $persistentEventData = $this->_uit->saveEvent($eventData);
166         $loadedEventData = $this->_uit->getEvent($persistentEventData['id']);
167         
168         //print_r($loadedEventData);
169         
170         // check if alarms are created / returned
171         $this->assertGreaterThan(0, count($loadedEventData['alarms']));
172         $this->assertEquals('Calendar_Model_Event', $loadedEventData['alarms'][0]['model']);
173         $this->assertEquals(Tinebase_Model_Alarm::STATUS_PENDING, $loadedEventData['alarms'][0]['sent_status']);
174         $this->assertTrue((isset($loadedEventData['alarms'][0]['minutes_before']) || array_key_exists('minutes_before', $loadedEventData['alarms'][0])), 'minutes_before is missing');
175         
176         $scheduler = Tinebase_Core::getScheduler();
177         $scheduler->addTask('Tinebase_Alarm', $this->createTask());
178         $scheduler->run();
179         
180         // check alarm status
181         $loadedEventData = $this->_uit->getEvent($persistentEventData['id']);
182         $this->assertEquals(Tinebase_Model_Alarm::STATUS_SUCCESS, $loadedEventData['alarms'][0]['sent_status']);
183     }
184     
185     /**
186      * createTask
187      */
188     public function createTask()
189     {
190         $request = new Zend_Controller_Request_Http();
191         $request->setControllerName('Tinebase_Alarm');
192         $request->setActionName('sendPendingAlarms');
193         $request->setParam('eventName', 'Tinebase_Event_Async_Minutely');
194         
195         $task = new Tinebase_Scheduler_Task();
196         $task->setMonths("Jan-Dec");
197         $task->setWeekdays("Sun-Sat");
198         $task->setDays("1-31");
199         $task->setHours("0-23");
200         $task->setMinutes("0/1");
201         $task->setRequest($request);
202         return $task;
203     }
204     
205     /**
206      * testUpdateEvent
207      */
208     public function testUpdateEvent()
209     {
210         $event = new Calendar_Model_Event($this->testCreateEvent(), true);
211         $event->dtstart->addHour(5);
212         $event->dtend->addHour(5);
213         $event->description = 'are you kidding?';
214         
215         $eventData = $event->toArray();
216         foreach ($eventData['attendee'] as $key => $attenderData) {
217             if ($eventData['attendee'][$key]['user_id'] != $this->_getTestUserContact()->getId()) {
218                 unset($eventData['attendee'][$key]);
219             }
220         }
221         
222         $updatedEventData = $this->_uit->saveEvent($eventData);
223         
224         $this->_assertJsonEvent($eventData, $updatedEventData, 'failed to update event');
225         
226         return $updatedEventData;
227     }
228
229     /**
230      * testDeleteEvent
231      */
232     public function testDeleteEvent() {
233         $eventData = $this->testCreateEvent();
234         
235         $this->_uit->deleteEvents(array($eventData['id']));
236         
237         $this->setExpectedException('Tinebase_Exception_NotFound');
238         $this->_uit->getEvent($eventData['id']);
239     }
240     
241     /**
242      * testSearchEvents
243      */
244     public function testSearchEvents()
245     {
246         $eventData = $this->testCreateEvent(TRUE); 
247         
248         $filter = $this->_getEventFilterArray();
249         $searchResultData = $this->_uit->searchEvents($filter, array());
250         
251         $this->assertTrue(! empty($searchResultData['results']));
252         $resultEventData = $searchResultData['results'][0];
253         
254         $this->_assertJsonEvent($eventData, $resultEventData, 'failed to search event');
255         return $searchResultData;
256     }
257     
258     /**
259      * get filter array with container and period filter
260      * 
261      * @param string|int $containerId
262      * @return multitype:multitype:string Ambigous <number, multitype:>  multitype:string multitype:string
263      */
264     protected function _getEventFilterArray($containerId = NULL)
265     {
266         $containerId = ($containerId) ? $containerId : $this->_getTestCalendar()->getId();
267         return array(
268             array('field' => 'container_id', 'operator' => 'equals', 'value' => $containerId),
269             array('field' => 'period', 'operator' => 'within', 'value' =>
270                 array("from" => '2009-03-20 06:15:00', "until" => Tinebase_DateTime::now()->addDay(1)->toString())
271             )
272         );
273     }
274     
275     /**
276      * testSearchEvents with period filter
277      * 
278      * @todo add an event that is in result set of Calendar_Controller_Event::search() 
279      *       but should be removed in Calendar_Frontend_Json::_multipleRecordsToJson()
280      */
281     public function testSearchEventsWithPeriodFilter()
282     {
283         $eventData = $this->testCreateRecurEvent();
284         
285         $filter = array(
286             array('field' => 'period', 'operator' => 'within', 'value' => array(
287                 'from'  => '2009-03-25 00:00:00',
288                 'until' => '2009-03-25 23:59:59',
289             )),
290             array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_getTestCalendar()->getId()),
291         );
292         
293         $searchResultData = $this->_uit->searchEvents($filter, array());
294         
295         $this->assertTrue(isset($searchResultData['results'][0]), 'event not found in result: ' . print_r($searchResultData['results'], true));
296         $resultEventData = $searchResultData['results'][0];
297         
298         $this->_assertJsonEvent($eventData, $resultEventData, 'failed to search event');
299     }
300     
301     /**
302      * #7688: Internal Server Error on calendar search
303      * 
304      * add period filter if none is given
305      * 
306      * https://forge.tine20.org/mantisbt/view.php?id=7688
307      */
308     public function testSearchEventsWithOutPeriodFilter()
309     {
310         $eventData = $this->testCreateRecurEvent();
311         $filter = array(array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_getTestCalendar()->getId()));
312         
313         $searchResultData = $this->_uit->searchEvents($filter, array());
314         $returnedFilter = $searchResultData['filter'];
315         $this->assertEquals(2, count($returnedFilter), 'Two filters shoud have been returned!');
316         $this->assertTrue($returnedFilter[1]['field'] == 'period' || $returnedFilter[0]['field'] == 'period', 'One returned filter shoud be a period filter');
317     }
318     
319     /**
320      * add period filter if none is given / configure from+until
321      * 
322      * @see 0009688: allow to configure default period filter in json frontend
323      */
324     public function testSearchEventsWithOutPeriodFilterConfiguredFromAndUntil()
325     {
326         Calendar_Config::getInstance()->set(Calendar_Config::MAX_JSON_DEFAULT_FILTER_PERIOD_FROM, 12);
327         
328         $filter = array(array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_getTestCalendar()->getId()));
329         $searchResultData = $this->_uit->searchEvents($filter, array());
330         
331         $now = Tinebase_DateTime::now()->setTime(0,0,0);
332         foreach ($searchResultData['filter'] as $filter) {
333             if ($filter['field'] === 'period') {
334                 $this->assertEquals($now->getClone()->subYear(1)->toString(), $filter['value']['from']);
335                 $this->assertEquals($now->getClone()->addMonth(1)->toString(), $filter['value']['until']);
336             }
337         }
338     }
339     
340     /**
341      * testSearchEvents with organizer = me filter
342      * 
343      * @see #6716: default favorite "me" is not resolved properly
344      */
345     public function testSearchEventsWithOrganizerMeFilter()
346     {
347         $eventData = $this->testCreateEvent(TRUE);
348         
349         $filter = $this->_getEventFilterArray();
350         $filter[] = array('field' => 'organizer', 'operator' => 'equals', 'value' => Addressbook_Model_Contact::CURRENTCONTACT);
351         
352         $searchResultData = $this->_uit->searchEvents($filter, array());
353         $this->assertTrue(! empty($searchResultData['results']));
354         $resultEventData = $searchResultData['results'][0];
355         $this->_assertJsonEvent($eventData, $resultEventData, 'failed to search event');
356         
357         // check organizer filter resolving
358         $organizerfilter = $searchResultData['filter'][2];
359         $this->assertTrue(is_array($organizerfilter['value']), 'organizer should be resolved: ' . print_r($organizerfilter, TRUE));
360         $this->assertEquals(Tinebase_Core::getUser()->contact_id, $organizerfilter['value']['id']);
361     }
362
363     /**
364      * testSearchEventsWithSharedContainerFilter
365      *
366      * @see 0011968: shared calendars filter leads to sql error with pgsql
367      */
368     public function testSearchEventsWithSharedContainerFilter()
369     {
370         $filter = $this->_getEventFilterArray();
371         $pathFilterValue = array("path" => "/shared");
372         $filter[0]['value'] = $pathFilterValue;
373         $searchResultData = $this->_uit->searchEvents($filter, array());
374
375         $this->assertEquals($pathFilterValue, $searchResultData['filter'][0]['value'], print_r($searchResultData['filter'], true));
376     }
377
378     /**
379      * search event with alarm
380      */
381     public function testSearchEventsWithAlarm()
382     {
383         $eventData = $this->_getEventWithAlarm(TRUE)->toArray();
384         $persistentEventData = $this->_uit->saveEvent($eventData);
385         
386         $searchResultData = $this->_uit->searchEvents($this->_getEventFilterArray(), array());
387         $this->assertTrue(! empty($searchResultData['results']));
388         $resultEventData = $searchResultData['results'][0];
389         
390         $this->_assertJsonEvent($persistentEventData, $resultEventData, 'failed to search event with alarm');
391     }
392     
393     /**
394      * testSetAttenderStatus
395      */
396     public function testSetAttenderStatus()
397     {
398         $eventData = $this->testCreateEvent();
399         $numAttendee = count($eventData['attendee']);
400         $eventData['attendee'][$numAttendee] = array(
401             'user_id' => $this->_getPersonasContacts('pwulf')->getId(),
402         );
403         
404         $updatedEventData = $this->_uit->saveEvent($eventData);
405         $pwulf = $this->_findAttender($updatedEventData['attendee'], 'pwulf');
406         
407         // he he, we don't have his authkey, cause json class sorts it out due to rights restrictions.
408         $attendeeBackend = new Calendar_Backend_Sql_Attendee();
409         $pwulf['status_authkey'] = $attendeeBackend->get($pwulf['id'])->status_authkey;
410         
411         $updatedEventData['container_id'] = $updatedEventData['container_id']['id'];
412         
413         $pwulf['status'] = Calendar_Model_Attender::STATUS_ACCEPTED;
414         $this->_uit->setAttenderStatus($updatedEventData, $pwulf, $pwulf['status_authkey']);
415         
416         $loadedEventData = $this->_uit->getEvent($eventData['id']);
417         $loadedPwulf = $this->_findAttender($loadedEventData['attendee'], 'pwulf');
418         $this->assertEquals(Calendar_Model_Attender::STATUS_ACCEPTED, $loadedPwulf['status']);
419     }
420     
421     /**
422      * testCreateRecurEvent
423      */
424     public function testCreateRecurEvent()
425     {
426         $eventData = $this->testCreateEvent();
427         $eventData['rrule'] = array(
428             'freq'     => 'WEEKLY',
429             'interval' => 1,
430             'byday'    => 'WE'
431         );
432         
433         $updatedEventData = $this->_uit->saveEvent($eventData);
434         $this->assertTrue(is_array($updatedEventData['rrule']));
435
436         return $updatedEventData;
437     }
438
439     /**
440      * testCreateRecurEventYearly
441      * 
442      * @see 0010610: yearly event is not shown in week view
443      */
444     public function testCreateRecurEventYearly()
445     {
446         $eventData = $this->_getEvent()->toArray();
447         $eventData['is_all_day_event'] = true;
448         $eventData['dtstart'] = '2015-01-04 00:00:00';
449         $eventData['dtend'] = '2015-01-04 23:59:59';
450         $eventData['rrule'] = array(
451             'freq'       => 'YEARLY',
452             'interval'   => 1,
453             'bymonthday' => 4,
454             'bymonth'    => 1,
455         );
456         
457         $updatedEventData = $this->_uit->saveEvent($eventData);
458         $this->assertTrue(is_array($updatedEventData['rrule']));
459         
460         $filter = array(
461             array('field' => 'container_id', 'operator' => 'equals', 'value' => $eventData['container_id']),
462             array('field' => 'period', 'operator' => 'within', 'value' =>
463                 array("from" => '2014-12-29 00:00:00', "until" => '2015-01-05 00:00:00')
464             )
465         );
466         $searchResultData = $this->_uit->searchEvents($filter, array());
467         $this->assertEquals(1, $searchResultData['totalcount'], 'event not found');
468     }
469     
470     /**
471      * testCreateRecurEventWithRruleUntil
472      * 
473      * @see 0008906: rrule_until is saved in usertime
474      */
475     public function testCreateRecurEventWithRruleUntil()
476     {
477         $eventData = $this->testCreateRecurEvent();
478         $localMidnight = Tinebase_DateTime::now()->setTime(23,59,59)->toString();
479         $eventData['rrule']['until'] = $localMidnight;
480         //$eventData['rrule']['freq']  = 'WEEKLY';
481         
482         $updatedEventData = $this->_uit->saveEvent($eventData);
483         $this->assertGreaterThanOrEqual($localMidnight, $updatedEventData['rrule']['until']);
484         
485         // check db record
486         $calbackend = new Calendar_Backend_Sql();
487         $db = $calbackend->getAdapter();
488         $select = $db->select();
489         $select->from(array($calbackend->getTableName() => $calbackend->getTablePrefix() . $calbackend->getTableName()), array('rrule_until', 'rrule'))->limit(1);
490         $select->where($db->quoteIdentifier($calbackend->getTableName() . '.id') . ' = ?', $updatedEventData['id']);
491         
492         $stmt = $db->query($select);
493         $queryResult = $stmt->fetch();
494         
495 //         echo Tinebase_Core::getUserTimezone();
496 //         echo date_default_timezone_get();
497         
498         $midnightInUTC = new Tinebase_DateTime($queryResult['rrule_until']);
499         $this->assertEquals(Tinebase_DateTime::now()->setTime(23,59,59)->toString(), $midnightInUTC->setTimezone(Tinebase_Core::getUserTimezone(), TRUE)->toString());
500     }
501
502     /**
503      * testCreateRecurEventWithConstrains
504      */
505     public function testCreateRecurEventWithConstrains()
506     {
507         $conflictEventData = $this->testCreateEvent();
508
509         $eventData = $this->testCreateEvent();
510         $eventData['rrule'] = array(
511             'freq'       => 'WEEKLY',
512             'interval'   => 1,
513             'byday'      => 'WE',
514         );
515         $eventData['rrule_constraints'] = array(
516             array('field' => 'container_id', 'operator' => 'in', 'value' => array($eventData['container_id'])),
517         );
518
519         $updatedEventData = $this->_uit->saveEvent($eventData);
520
521         $this->assertTrue(is_array($updatedEventData['rrule_constraints']));
522         $this->assertEquals('personal',$updatedEventData['rrule_constraints'][0]['value'][0]['type'], 'filter is not resolved');
523         $this->assertEquals(1, count($updatedEventData['exdate']));
524         $this->assertEquals('2009-03-25 06:00:00', $updatedEventData['exdate'][0]);
525
526         return $updatedEventData;
527     }
528
529     /**
530     * testSearchRecuringIncludes
531     */
532     public function testSearchRecuringIncludes()
533     {
534         $recurEvent = $this->testCreateRecurEvent();
535     
536         $from = $recurEvent['dtstart'];
537         $until = new Tinebase_DateTime($from);
538         $until->addWeek(5)->addHour(10);
539         $until = $until->get(Tinebase_Record_Abstract::ISO8601LONG);
540     
541         $filter = array(
542         array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_getTestCalendar()->getId()),
543         array('field' => 'period',       'operator' => 'within', 'value' => array('from' => $from, 'until' => $until)),
544         );
545     
546         $searchResultData = $this->_uit->searchEvents($filter, array());
547     
548         $this->assertEquals(6, $searchResultData['totalcount']);
549         
550         // test appending tags to recurring instances
551         $this->assertTrue(isset($searchResultData['results'][4]['tags'][0]), 'tags not set: ' . print_r($searchResultData['results'][4], true));
552         $this->assertEquals('phpunit-', substr($searchResultData['results'][4]['tags'][0]['name'], 0, 8));
553     
554         return $searchResultData;
555     }
556     
557     /**
558      * testSearchRecuringIncludesAndSort
559      */
560     public function testSearchRecuringIncludesAndSort()
561     {
562         $recurEvent = $this->testCreateRecurEvent();
563         
564         $from = $recurEvent['dtstart'];
565         $until = new Tinebase_DateTime($from);
566         $until->addWeek(5)->addHour(10);
567         $until = $until->get(Tinebase_Record_Abstract::ISO8601LONG);
568         
569         $filter = array(
570             array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_getTestCalendar()->getId()),
571             array('field' => 'period',       'operator' => 'within', 'value' => array('from' => $from, 'until' => $until)),
572         );
573         
574         $searchResultData = $this->_uit->searchEvents($filter, array('sort' => 'dtstart', 'dir' => 'DESC'));
575         
576         $this->assertEquals(6, $searchResultData['totalcount']);
577         
578         // check sorting
579         $this->assertEquals('2009-04-29 06:00:00', $searchResultData['results'][0]['dtstart']);
580         $this->assertEquals('2009-04-22 06:00:00', $searchResultData['results'][1]['dtstart']);
581     }
582     
583     /**
584      * testCreateRecurException
585      */
586     public function testCreateRecurException()
587     {
588         $recurSet = Tinebase_Helper::array_value('results', $this->testSearchRecuringIncludes());
589         
590         $persistentException = $recurSet[1];
591         $persistentException['summary'] = 'go sleeping';
592         
593         // create persistent exception
594         $this->_uit->createRecurException($persistentException, FALSE, FALSE);
595         
596         // create exception date
597         $updatedBaseEvent = Calendar_Controller_Event::getInstance()->getRecurBaseEvent(new Calendar_Model_Event($recurSet[2]));
598         $recurSet[2]['last_modified_time'] = $updatedBaseEvent->last_modified_time;
599         $this->_uit->createRecurException($recurSet[2], TRUE, FALSE);
600         
601         // delete all following (including this)
602         $updatedBaseEvent = Calendar_Controller_Event::getInstance()->getRecurBaseEvent(new Calendar_Model_Event($recurSet[4]));
603         $recurSet[4]['last_modified_time'] = $updatedBaseEvent->last_modified_time;
604         $this->_uit->createRecurException($recurSet[4], TRUE, TRUE);
605         
606         $from = $recurSet[0]['dtstart'];
607         $until = new Tinebase_DateTime($from);
608         $until->addWeek(5)->addHour(10);
609         $until = $until->get(Tinebase_Record_Abstract::ISO8601LONG);
610         
611         $filter = array(
612             array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_getTestCalendar()->getId()),
613             array('field' => 'period',       'operator' => 'within', 'value' => array('from' => $from, 'until' => $until)),
614         );
615         
616         $searchResultData = $this->_uit->searchEvents($filter, array('sort' => 'dtstart'));
617         
618         // we deleted one and cropped
619         $this->assertEquals(3, count($searchResultData['results']));
620         
621         $summaryMap = array();
622         foreach ($searchResultData['results'] as $event) {
623             $summaryMap[$event['dtstart']] = $event['summary'];
624         }
625         $this->assertTrue((isset($summaryMap['2009-04-01 06:00:00']) || array_key_exists('2009-04-01 06:00:00', $summaryMap)));
626         $this->assertEquals($persistentException['summary'], $summaryMap['2009-04-01 06:00:00']);
627         
628         return $searchResultData;
629     }
630     
631     /**
632      * testCreateRecurExceptionWithOtherUser
633      * 
634      * @see 0008172: displaycontainer_id not set when recur exception is created
635      */
636     public function testCreateRecurExceptionWithOtherUser()
637     {
638         $recurSet = Tinebase_Helper::array_value('results', $this->testSearchRecuringIncludes());
639         
640         // create persistent exception (just status update)
641         $persistentException = $recurSet[1];
642         $scleverAttender = $this->_findAttender($persistentException['attendee'], 'sclever');
643         $attendeeBackend = new Calendar_Backend_Sql_Attendee();
644         $status_authkey = $attendeeBackend->get($scleverAttender['id'])->status_authkey;
645         $scleverAttender['status'] = Calendar_Model_Attender::STATUS_ACCEPTED;
646         $scleverAttender['status_authkey'] = $status_authkey;
647         foreach ($persistentException['attendee'] as $key => $attender) {
648             if ($attender['id'] === $scleverAttender['id']) {
649                 $persistentException['attendee'][$key] = $scleverAttender;
650                 break;
651             }
652         }
653         
654         // sclever has only READ grant
655         Tinebase_Container::getInstance()->setGrants($this->_getTestCalendar(), new Tinebase_Record_RecordSet('Tinebase_Model_Grants', array(array(
656             'account_id'    => $this->_getTestUser()->getId(),
657             'account_type'  => 'user',
658             Tinebase_Model_Grants::GRANT_READ     => true,
659             Tinebase_Model_Grants::GRANT_ADD      => true,
660             Tinebase_Model_Grants::GRANT_EDIT     => true,
661             Tinebase_Model_Grants::GRANT_DELETE   => true,
662             Tinebase_Model_Grants::GRANT_PRIVATE  => true,
663             Tinebase_Model_Grants::GRANT_ADMIN    => true,
664             Tinebase_Model_Grants::GRANT_FREEBUSY => true,
665         ), array(
666             'account_id'    => $this->_getPersona('sclever')->getId(),
667             'account_type'  => 'user',
668             Tinebase_Model_Grants::GRANT_READ     => true,
669             Tinebase_Model_Grants::GRANT_FREEBUSY => true,
670         ))), TRUE);
671         
672         $unittestUser = Tinebase_Core::getUser();
673         Tinebase_Core::set(Tinebase_Core::USER, $this->_getPersona('sclever'));
674         
675         // create persistent exception
676         $createdException = $this->_uit->createRecurException($persistentException, FALSE, FALSE);
677         Tinebase_Core::set(Tinebase_Core::USER, $this->_originalTestUser);
678         
679         $sclever = $this->_findAttender($createdException['attendee'], 'sclever');
680         $defaultCal = $this->_getPersonasDefaultCals('sclever');
681         $this->assertEquals('Susan Clever', $sclever['user_id']['n_fn']);
682         $this->assertEquals(Calendar_Model_Attender::STATUS_ACCEPTED, $sclever['status'], 'status mismatch: ' . print_r($sclever, TRUE));
683         $this->assertTrue(is_array($sclever['displaycontainer_id']));
684         $this->assertEquals($defaultCal['id'], $sclever['displaycontainer_id']['id']);
685     }
686     
687     /**
688      * testUpdateRecurSeries
689      */
690     public function testUpdateRecurSeries()
691     {
692         $recurSet = Tinebase_Helper::array_value('results', $this->testSearchRecuringIncludes());
693         
694         $persistentException = $recurSet[1];
695         $persistentException['summary'] = 'go sleeping';
696         $persistentException['dtstart'] = '2009-04-01 20:00:00';
697         $persistentException['dtend']   = '2009-04-01 20:30:00';
698         
699         // create persistent exception
700         $recurResult = $this->_uit->createRecurException($persistentException, FALSE, FALSE);
701         
702         // update recurseries 
703         $someRecurInstance = $recurSet[2];
704         $someRecurInstance['summary'] = 'go fishing';
705         $someRecurInstance['dtstart'] = '2009-04-08 10:00:00';
706         $someRecurInstance['dtend']   = '2009-04-08 12:30:00';
707         
708         $someRecurInstance['seq'] = 3;
709         $this->_uit->updateRecurSeries($someRecurInstance, FALSE, FALSE);
710         
711         $searchResultData = $this->_searchRecurSeries($recurSet[0]);
712         $this->assertEquals(6, count($searchResultData['results']));
713         
714         $summaryMap = array();
715         foreach ($searchResultData['results'] as $event) {
716             $summaryMap[$event['dtstart']] = $event['summary'];
717         }
718         
719         $this->assertTrue((isset($summaryMap['2009-04-01 20:00:00']) || array_key_exists('2009-04-01 20:00:00', $summaryMap)));
720         $this->assertEquals('go sleeping', $summaryMap['2009-04-01 20:00:00']);
721         
722         $fishings = array_keys($summaryMap, 'go fishing');
723         $this->assertEquals(5, count($fishings));
724         foreach ($fishings as $dtstart) {
725             $this->assertEquals('10:00:00', substr($dtstart, -8), 'all fishing events should start at 10:00');
726         }
727     }
728     
729     /**
730      * search updated recur set
731      * 
732      * @param array $firstInstance
733      * @return array
734      */
735     protected function _searchRecurSeries($firstInstance)
736     {
737         $from = $firstInstance['dtstart'];
738         $until = new Tinebase_DateTime($from);
739         $until->addWeek(5)->addHour(10);
740         $until = $until->get(Tinebase_Record_Abstract::ISO8601LONG);
741         
742         $filter = array(
743             array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_getTestCalendar()->getId()),
744             array('field' => 'period',       'operator' => 'within', 'value' => array('from' => $from, 'until' => $until)),
745         );
746         
747         return $this->_uit->searchEvents($filter, array());
748     }
749     
750     /**
751      * testUpdateRecurExceptionsFromSeriesOverDstMove
752      * 
753      * @todo implement
754      */
755     public function testUpdateRecurExceptionsFromSeriesOverDstMove()
756     {
757         /*
758          * 1. create recur event 1 day befor dst move
759          * 2. create an exception and exdate
760          * 3. move dtstart from 1 over dst boundary
761          * 4. test recurid and exdate by calculating series
762          */
763     }
764     
765     /**
766      * testDeleteRecurSeries
767      */
768     public function testDeleteRecurSeries()
769     {
770         $recurSet = Tinebase_Helper::array_value('results', $this->testSearchRecuringIncludes());
771         
772         $persistentException = $recurSet[1];
773         $persistentException['summary'] = 'go sleeping';
774         
775         // create persistent exception
776         $this->_uit->createRecurException($persistentException, FALSE, FALSE);
777         
778         // delete recurseries 
779         $someRecurInstance = $persistentException = $recurSet[2];
780         $this->_uit->deleteRecurSeries($someRecurInstance);
781         
782         $from = $recurSet[0]['dtstart'];
783         $until = new Tinebase_DateTime($from);
784         $until->addWeek(5)->addHour(10);
785         $until = $until->get(Tinebase_Record_Abstract::ISO8601LONG);
786         
787         $filter = array(
788             array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_getTestCalendar()->getId()),
789             array('field' => 'period',       'operator' => 'within', 'value' => array('from' => $from, 'until' => $until)),
790         );
791         
792         $searchResultData = $this->_uit->searchEvents($filter, array());
793         
794         $this->assertEquals(0, count($searchResultData['results']));
795     }
796     
797     /**
798      * testMeAsAttenderFilter
799      */
800     public function testMeAsAttenderFilter()
801     {
802         $eventData = $this->testCreateEvent(TRUE);
803         
804         $filter = $this->_getEventFilterArray();
805         $filter[] = array('field' => 'attender'    , 'operator' => 'equals', 'value' => array(
806             'user_type' => Calendar_Model_Attender::USERTYPE_USER,
807             'user_id'   => Addressbook_Model_Contact::CURRENTCONTACT,
808         ));
809         
810         $searchResultData = $this->_uit->searchEvents($filter, array());
811         $this->assertTrue(! empty($searchResultData['results']));
812         $resultEventData = $searchResultData['results'][0];
813         
814         $this->_assertJsonEvent($eventData, $resultEventData, 'failed to filter for me as attender');
815     }
816     
817     /**
818      * testFreeBusyCleanup
819      * 
820      * @return array event data
821      */
822     public function testFreeBusyCleanup()
823     {
824         // give fb grants from sclever
825         $scleverCal = Tinebase_Container::getInstance()->getContainerById($this->_getPersonasDefaultCals('sclever'));
826         Tinebase_Container::getInstance()->setGrants($scleverCal->getId(), new Tinebase_Record_RecordSet('Tinebase_Model_Grants', array(array(
827             'account_id'    => $this->_getPersona('sclever')->getId(),
828             'account_type'  => 'user',
829             Tinebase_Model_Grants::GRANT_READ     => true,
830             Tinebase_Model_Grants::GRANT_ADD      => true,
831             Tinebase_Model_Grants::GRANT_EDIT     => true,
832             Tinebase_Model_Grants::GRANT_DELETE   => true,
833             Tinebase_Model_Grants::GRANT_PRIVATE  => true,
834             Tinebase_Model_Grants::GRANT_ADMIN    => true,
835             Tinebase_Model_Grants::GRANT_FREEBUSY => true,
836         ), array(
837             'account_id'    => $this->_getTestUser()->getId(),
838             'account_type'  => 'user',
839             Tinebase_Model_Grants::GRANT_FREEBUSY => true,
840         ))), TRUE);
841         
842         Tinebase_Core::set(Tinebase_Core::USER, $this->_getPersona('sclever'));
843         $eventData = $this->_getEvent()->toArray();
844         unset($eventData['organizer']);
845         $eventData['container_id'] = $scleverCal->getId();
846         $eventData['attendee'] = array(array(
847             'user_id' => $this->_getPersonasContacts('sclever')->getId()
848         ));
849         $eventData['organizer'] = $this->_getPersonasContacts('sclever')->getId();
850         $eventData = $this->_uit->saveEvent($eventData);
851         $filter = $this->_getEventFilterArray($this->_getPersonasDefaultCals('sclever')->getId());
852         $filter[] = array('field' => 'summary', 'operator' => 'equals', 'value' => 'Wakeup');
853         $searchResultData = $this->_uit->searchEvents($filter, array());
854         $this->assertTrue(! empty($searchResultData['results']), 'expected event in search result (search by sclever): ' 
855             . print_r($eventData, TRUE) . 'search filter: ' . print_r($filter, TRUE));
856         
857         Tinebase_Core::set(Tinebase_Core::USER, $this->_getTestUser());
858         $searchResultData = $this->_uit->searchEvents($filter, array());
859         $this->assertTrue(! empty($searchResultData['results']), 'expected (freebusy cleanup) event in search result: ' 
860             . print_r($eventData, TRUE) . 'search filter: ' . print_r($filter, TRUE));
861         $eventData = $searchResultData['results'][0];
862         
863         $this->assertFalse((isset($eventData['summary']) || array_key_exists('summary', $eventData)), 'summary not empty: ' . print_r($eventData, TRUE));
864         $this->assertFalse((isset($eventData['description']) || array_key_exists('description', $eventData)), 'description not empty');
865         $this->assertFalse((isset($eventData['tags']) || array_key_exists('tags', $eventData)), 'tags not empty');
866         $this->assertFalse((isset($eventData['notes']) || array_key_exists('notes', $eventData)), 'notes not empty');
867         $this->assertFalse((isset($eventData['attendee']) || array_key_exists('attendee', $eventData)), 'attendee not empty');
868         $this->assertFalse((isset($eventData['organizer']) || array_key_exists('organizer', $eventData)), 'organizer not empty');
869         $this->assertFalse((isset($eventData['alarms']) || array_key_exists('alarms', $eventData)), 'alarms not empty');
870         
871         return $eventData;
872     }
873
874     /**
875      * testFreeBusyCleanupOfNotes
876      * 
877      * @see 0009918: shared (only free/busy) calendar is showing event details within the history tab.
878      */
879     public function testFreeBusyCleanupOfNotes()
880     {
881         $eventData = $this->testFreeBusyCleanup();
882         
883         $tinebaseJson = new Tinebase_Frontend_Json();
884         $filter = array(array(
885             'field' => "record_model",
886             'operator' => "equals",
887             'value' => "Calendar_Model_Event"
888         ), array(
889             'field' => 'record_id',
890             'operator' => 'equals',
891             'value' => $eventData['id']
892         ));
893         $notes = $tinebaseJson->searchNotes($filter, array());
894         
895         $this->assertEquals(0, $notes['totalcount'], 'should not find any notes of record');
896         $this->assertEquals(0, count($notes['results']), 'should not find any notes of record');
897     }
898     
899     /**
900      * test deleting container and the containing events
901      * #6704: events do not disappear when shared calendar got deleted
902      * https://forge.tine20.org/mantisbt/view.php?id=6704
903      */
904     public function testDeleteContainerAndEvents()
905     {
906         $fe = new Tinebase_Frontend_Json_Container();
907         $container = $fe->addContainer('Calendar', 'testdeletecontacts', Tinebase_Model_Container::TYPE_SHARED, '');
908         // test if model is set automatically
909         $this->assertEquals($container['model'], 'Calendar_Model_Event');
910         
911         $date = new Tinebase_DateTime();
912         $event = new Calendar_Model_Event(array(
913             'dtstart' => $date,
914             'dtend'    => $date->subHour(1),
915             'summary' => 'bla bla',
916             'class'    => 'PUBLIC',
917             'transp'    => 'OPAQUE',
918             'container_id' => $container['id'],
919             'organizer' => Tinebase_Core::getUser()->contact_id
920             ));
921         $event = Calendar_Controller_Event::getInstance()->create($event);
922         $this->assertEquals($container['id'], $event->container_id);
923         
924         $fe->deleteContainer($container['id']);
925         
926         $e = new Exception('dummy');
927         
928         $cb = new Calendar_Backend_Sql();
929         $deletedEvent = $cb->get($event->getId(), true);
930         // record should be deleted
931         $this->assertEquals($deletedEvent->is_deleted, 1);
932         
933         try {
934             Calendar_Controller_Event::getInstance()->get($event->getId(), $container['id']);
935             $this->fail('The expected exception was not thrown');
936         } catch (Tinebase_Exception_NotFound $e) {
937             // ok;
938         }
939         // record should not be found
940         $this->assertEquals($e->getMessage(), 'Calendar_Model_Event record with id '.$event->getId().' not found!');
941     }
942     
943     /**
944      * compare expected event data with test event
945      *
946      * @param array $expectedEventData
947      * @param array $eventData
948      * @param string $msg
949      */
950     protected function _assertJsonEvent($expectedEventData, $eventData, $msg)
951     {
952         $this->assertEquals($expectedEventData['summary'], $eventData['summary'], $msg . ': failed to create/load event');
953         
954         // assert effective grants are set
955         $this->assertEquals((bool) $expectedEventData[Tinebase_Model_Grants::GRANT_EDIT], (bool) $eventData[Tinebase_Model_Grants::GRANT_EDIT], $msg . ': effective grants mismatch');
956         // container, assert attendee, tags
957         $this->assertEquals($expectedEventData['dtstart'], $eventData['dtstart'], $msg . ': dtstart mismatch');
958         $this->assertTrue(is_array($eventData['container_id']), $msg . ': failed to "resolve" container');
959         $this->assertTrue(is_array($eventData['container_id']['account_grants']), $msg . ': failed to "resolve" container account_grants');
960         $this->assertGreaterThan(0, count($eventData['attendee']));
961         $this->assertEquals(count($eventData['attendee']), count($expectedEventData['attendee']), $msg . ': failed to append attendee');
962         $this->assertTrue(is_array($eventData['attendee'][0]['user_id']), $msg . ': failed to resolve attendee user_id');
963         // NOTE: due to sorting isshues $eventData['attendee'][0] may be a non resolvable container (due to rights restrictions)
964         $this->assertTrue(is_array($eventData['attendee'][0]['displaycontainer_id']) || (isset($eventData['attendee'][1]) && is_array($eventData['attendee'][1]['displaycontainer_id'])), $msg . ': failed to resolve attendee displaycontainer_id');
965         $this->assertEquals(count($expectedEventData['tags']), count($eventData['tags']), $msg . ': failed to append tag');
966         $this->assertEquals(count($expectedEventData['notes']), count($eventData['notes']), $msg . ': failed to create note or wrong number of notes');
967         
968         if ((isset($expectedEventData['alarms']) || array_key_exists('alarms', $expectedEventData))) {
969             $this->assertTrue((isset($eventData['alarms']) || array_key_exists('alarms', $eventData)), ': failed to create alarms');
970             $this->assertEquals(count($expectedEventData['alarms']), count($eventData['alarms']), $msg . ': failed to create correct number of alarms');
971             if (count($expectedEventData['alarms']) > 0) {
972                 $this->assertTrue((isset($eventData['alarms'][0]['minutes_before']) || array_key_exists('minutes_before', $eventData['alarms'][0])));
973             }
974         }
975     }
976     
977     /**
978      * find attender 
979      *
980      * @param array $attendeeData
981      * @param string $name
982      * @return array
983      */
984     protected function _findAttender($attendeeData, $name) {
985         $attenderData = false;
986         $searchedId = $this->_getPersonasContacts($name)->getId();
987         
988         foreach ($attendeeData as $key => $attender) {
989             if ($attender['user_type'] == Calendar_Model_Attender::USERTYPE_USER) {
990                 if (is_array($attender['user_id']) && (isset($attender['user_id']['id']) || array_key_exists('id', $attender['user_id']))) {
991                     if ($attender['user_id']['id'] == $searchedId) {
992                         $attenderData = $attendeeData[$key];
993                     }
994                 }
995             }
996         }
997         
998         return $attenderData;
999     }
1000     
1001     /**
1002      * test filter with hidden group -> should return empty result
1003      * 
1004      * @see 0006934: setting a group that is hidden from adb as attendee filter throws exception
1005      */
1006     public function testHiddenGroupFilter()
1007     {
1008         $hiddenGroup = new Tinebase_Model_Group(array(
1009             'name'          => 'hiddengroup',
1010             'description'   => 'hidden group',
1011             'visibility'     => Tinebase_Model_Group::VISIBILITY_HIDDEN
1012         ));
1013         $hiddenGroup = Admin_Controller_Group::getInstance()->create($hiddenGroup);
1014         $this->_groupIdsToDelete[] = $hiddenGroup->getId();
1015         
1016         $filter = array(array(
1017             'field'    => 'attender',
1018             'operator' => 'equals',
1019             'value'    => array(
1020                 'user_id'   => $hiddenGroup->list_id,
1021                 'user_type' => 'group',
1022             ),
1023         ));
1024         $result = $this->_uit->searchEvents($filter, array());
1025         $this->assertEquals(0, $result['totalcount']);
1026     }
1027     
1028     /**
1029      * testExdateDeleteAll
1030      * 
1031      * @see 0007382: allow to edit / delete the whole series / thisandfuture when editing/deleting recur exceptions
1032      */
1033     public function testExdateDeleteAll()
1034     {
1035         $events = $this->testCreateRecurException();
1036         $exception = $this->_getException($events);
1037         $this->_uit->deleteEvents(array($exception['id']), Calendar_Model_Event::RANGE_ALL);
1038         
1039         $search = $this->_uit->searchEvents($events['filter'], NULL);
1040         $this->assertEquals(0, $search['totalcount'], 'all events should be deleted: ' . print_r($search,TRUE));
1041     }
1042     
1043     /**
1044      * get exception from event resultset
1045      * 
1046      * @param array $events
1047      * @param integer $index (1 = picks first, 2 = picks second, ...)
1048      * @return array|NULL
1049      */
1050     protected function _getException($events, $index = 1)
1051     {
1052         $event = NULL;
1053         $found = 0;
1054         foreach ($events['results'] as $event) {
1055             if (! empty($event['recurid'])) {
1056                 $found++;
1057                 if ($index === $found) {
1058                     return $event;
1059                 }
1060             }
1061         }
1062         
1063         return $event;
1064     }
1065     
1066     /**
1067      * testExdateDeleteThis
1068      * 
1069      * @see 0007382: allow to edit / delete the whole series / thisandfuture when editing/deleting recur exceptions
1070      */
1071     public function testExdateDeleteThis()
1072     {
1073         $events = $this->testCreateRecurException();
1074         $exception = $this->_getException($events);
1075         $this->_uit->deleteEvents(array($exception['id']));
1076         
1077         $search = $this->_uit->searchEvents($events['filter'], NULL);
1078         $this->assertEquals(2, $search['totalcount'], '2 events should remain: ' . print_r($search,TRUE));
1079     }
1080     
1081     /**
1082      * testExdateDeleteThisAndFuture
1083      * 
1084      * @see 0007382: allow to edit / delete the whole series / thisandfuture when editing/deleting recur exceptions
1085      */
1086     public function testExdateDeleteThisAndFuture()
1087     {
1088         $events = $this->testCreateRecurException();
1089         $exception = $this->_getException($events, 1);
1090         $this->_uit->deleteEvents(array($exception['id']), Calendar_Model_Event::RANGE_THISANDFUTURE);
1091         
1092         $search = $this->_uit->searchEvents($events['filter'], NULL);
1093         $this->assertEquals(1, $search['totalcount'], '1 event should remain: ' . print_r($search,TRUE));
1094     }
1095     
1096     /**
1097      * assert grant handling
1098      */
1099     public function testSaveResource($grants = array('readGrant' => true,'editGrant' => true))
1100     {
1101         $resoureData = array(
1102             'name'  => Tinebase_Record_Abstract::generateUID(),
1103             'email' => Tinebase_Record_Abstract::generateUID() . '@unittest.com',
1104             'grants' => array(array_merge($grants, array(
1105                 'account_id' => Tinebase_Core::getUser()->getId(),
1106                 'account_type' => 'user'
1107             )))
1108         );
1109         
1110         $resoureData = $this->_uit->saveResource($resoureData);
1111         $this->assertTrue(is_array($resoureData['grants']), 'grants are not resolved');
1112         
1113         return $resoureData;
1114     }
1115     
1116     /**
1117      * assert only resources with read grant are returned if the user has no manage right
1118      */
1119     public function testSearchResources()
1120     {
1121         $readableResoureData = $this->testSaveResource();
1122         $nonReadableResoureData = $this->testSaveResource(array());
1123         
1124         $filer = array(
1125             array('field' => 'name', 'operator' => 'in', 'value' => array(
1126                 $readableResoureData['name'],
1127                 $nonReadableResoureData['name'],
1128             ))
1129         );
1130         
1131         $searchResultManager = $this->_uit->searchResources($filer, array());
1132         $this->assertEquals(2, count($searchResultManager['results']), 'with manage grants all records should be found');
1133         
1134         // steal manage right and reactivate container checks
1135         $roleManager = Tinebase_Acl_Roles::getInstance();
1136         $roleManager->deleteRoles(array(
1137                 $roleManager->getRoleByName('manager role')->getId(),
1138                 $roleManager->getRoleByName('admin role')->getId()
1139                 ));
1140         
1141         Calendar_Controller_Resource::getInstance()->doContainerACLChecks(TRUE);
1142         
1143         $searchResult = $this->_uit->searchResources($filer, array());
1144         $this->assertEquals(1, count($searchResult['results']), 'without manage grants only one record should be found');
1145     }
1146     
1147     /**
1148      * assert status authkey with editGrant
1149      * assert stauts can be set with editGrant
1150      * assert stauts can't be set without editGrant
1151      */
1152     public function testResourceAttendeeGrants()
1153     {
1154         $editableResoureData = $this->testSaveResource();
1155         $nonEditableResoureData = $this->testSaveResource(array('readGrant'));
1156         
1157         $event = $this->_getEvent(TRUE);
1158         $event->attendee = new Tinebase_Record_RecordSet('Calendar_Model_Attender', array(
1159             array(
1160                 'user_type'  => Calendar_Model_Attender::USERTYPE_RESOURCE,
1161                 'user_id'    => $editableResoureData['id'],
1162                 'status'     => Calendar_Model_Attender::STATUS_ACCEPTED
1163             ),
1164             array(
1165                 'user_type'  => Calendar_Model_Attender::USERTYPE_RESOURCE,
1166                 'user_id'    => $nonEditableResoureData['id'],
1167                 'status'     => Calendar_Model_Attender::STATUS_ACCEPTED
1168             )
1169         ));
1170         
1171         $persistentEventData = $this->_uit->saveEvent($event->toArray());
1172         
1173         $attendee = new Tinebase_Record_RecordSet('Calendar_Model_Attender', $persistentEventData['attendee']);
1174         $this->assertEquals(1, count($attendee->filter('status', Calendar_Model_Attender::STATUS_ACCEPTED)), 'one accepted');
1175         $this->assertEquals(1, count($attendee->filter('status', Calendar_Model_Attender::STATUS_NEEDSACTION)), 'one needs action');
1176         
1177         $this->assertEquals(1, count($attendee->filter('status_authkey', '/[a-z0-9]+/', TRUE)), 'one has authkey');
1178         
1179         $attendee->status = Calendar_Model_Attender::STATUS_TENTATIVE;
1180         $persistentEventData['attendee'] = $attendee->toArray();
1181         
1182         $updatedEventData = $this->_uit->saveEvent($persistentEventData);
1183         $attendee = new Tinebase_Record_RecordSet('Calendar_Model_Attender', $updatedEventData['attendee']);
1184         $this->assertEquals(1, count($attendee->filter('status', Calendar_Model_Attender::STATUS_TENTATIVE)), 'one tentative');
1185     }
1186
1187     /**
1188      * testExdateUpdateAllSummary
1189      * 
1190      * @see 0007690: allow to update the whole series / thisandfuture when updating recur exceptions
1191      */
1192     public function testExdateUpdateAllSummary()
1193     {
1194         $events = $this->testCreateRecurException();
1195         $exception = $this->_getException($events, 1);
1196         $exception['summary'] = 'new summary';
1197         
1198         $event = $this->_uit->saveEvent($exception, FALSE, Calendar_Model_Event::RANGE_ALL);
1199         
1200         $search = $this->_uit->searchEvents($events['filter'], NULL);
1201         foreach ($search['results'] as $event) {
1202             $this->assertEquals('new summary', $event['summary']);
1203         }
1204     }
1205
1206     /**
1207      * testExdateUpdateAllDtStart
1208      * 
1209      * @see 0007690: allow to update the whole series / thisandfuture when updating recur exceptions
1210      * 
1211      * @todo finish
1212      */
1213     public function testExdateUpdateAllDtStart()
1214     {
1215         $events = $this->testCreateRecurException();
1216         $exception = $this->_getException($events, 1);
1217         $exception['dtstart'] = '2009-04-01 08:00:00';
1218         $exception['dtend'] = '2009-04-01 08:15:00';
1219         
1220         $event = $this->_uit->saveEvent($exception, FALSE, Calendar_Model_Event::RANGE_ALL);
1221         
1222         $search = $this->_uit->searchEvents($events['filter'], NULL);
1223         foreach ($search['results'] as $event) {
1224             $this->assertContains('08:00:00', $event['dtstart'], 'wrong dtstart: ' . print_r($event, TRUE));
1225             $this->assertContains('08:15:00', $event['dtend']);
1226         }
1227     }
1228     
1229     /**
1230      * testExdateUpdateThis
1231      * 
1232      * @see 0007690: allow to update the whole series / thisandfuture when updating recur exceptions
1233      */
1234     public function testExdateUpdateThis()
1235     {
1236         $events = $this->testCreateRecurException();
1237         $exception = $this->_getException($events, 1);
1238         $exception['summary'] = 'exception';
1239         
1240         $event = $this->_uit->saveEvent($exception);
1241         $this->assertEquals('exception', $event['summary']);
1242         
1243         // check for summary (only changed in one event)
1244         $search = $this->_uit->searchEvents($events['filter'], NULL);
1245         foreach ($search['results'] as $event) {
1246             if (! empty($event['recurid']) && ! preg_match('/^fakeid/', $event['id'])) {
1247                 $this->assertEquals('exception', $event['summary'], 'summary not changed in exception: ' . print_r($event, TRUE));
1248             } else {
1249                 $this->assertEquals('Wakeup', $event['summary']);
1250             }
1251         }
1252     }
1253
1254     /**
1255      * testExdateUpdateThisAndFuture
1256      * 
1257      * @see 0007690: allow to update the whole series / thisandfuture when updating recur exceptions
1258      */
1259     public function testExdateUpdateThisAndFuture()
1260     {
1261         $events = $this->testCreateRecurException();
1262         $exception = $this->_getException($events, 1);
1263         $exception['summary'] = 'new summary';
1264         
1265         $updatedEvent = $this->_uit->saveEvent($exception, FALSE, Calendar_Model_Event::RANGE_THISANDFUTURE);
1266         $this->assertEquals('new summary', $updatedEvent['summary'], 'summary not changed in exception: ' . print_r($updatedEvent, TRUE));
1267         
1268         $search = $this->_uit->searchEvents($events['filter'], NULL);
1269         foreach ($search['results'] as $event) {
1270             if ($event['dtstart'] >= $updatedEvent['dtstart']) {
1271                 $this->assertEquals('new summary', $event['summary'], 'summary not changed in event: ' . print_r($event, TRUE));
1272             } else {
1273                 $this->assertEquals('Wakeup', $event['summary']);
1274             }
1275         }
1276     }
1277
1278     /**
1279      * testExdateUpdateThisAndFutureWithRruleUntil
1280      * 
1281      * @see 0008244: "rrule until must not be before dtstart" when updating recur exception (THISANDFUTURE)
1282      */
1283     public function testExdateUpdateThisAndFutureWithRruleUntil()
1284     {
1285         $events = $this->testCreateRecurException();
1286         
1287         $exception = $this->_getException($events, 1);
1288         $exception['dtstart'] = Tinebase_DateTime::now()->toString();
1289         $exception['dtend'] = Tinebase_DateTime::now()->addHour(1)->toString();
1290         
1291         // move exception
1292         $updatedEvent = $this->_uit->saveEvent($exception);
1293         // try to update the whole series
1294         $updatedEvent['summary'] = 'new summary';
1295         $updatedEvent = $this->_uit->saveEvent($updatedEvent, FALSE, Calendar_Model_Event::RANGE_THISANDFUTURE);
1296         
1297         $this->assertEquals('new summary', $updatedEvent['summary'], 'summary not changed in event: ' . print_r($updatedEvent, TRUE));
1298     }
1299     
1300     /**
1301      * testExdateUpdateThisAndFutureRemoveAttendee
1302      * 
1303      * @see 0007690: allow to update the whole series / thisandfuture when updating recur exceptions
1304      */
1305     public function testExdateUpdateThisAndFutureRemoveAttendee()
1306     {
1307         $events = $this->testCreateRecurException();
1308         $exception = $this->_getException($events, 1);
1309         // remove susan from attendee
1310         unset($exception['attendee'][0]);
1311         
1312         $updatedEvent = $this->_uit->saveEvent($exception, FALSE, Calendar_Model_Event::RANGE_THISANDFUTURE);
1313         $this->assertEquals(1, count($updatedEvent['attendee']), 'attender not removed from exception: ' . print_r($updatedEvent, TRUE));
1314         
1315         $search = $this->_uit->searchEvents($events['filter'], NULL);
1316         foreach ($search['results'] as $event) {
1317             if ($event['dtstart'] >= $updatedEvent['dtstart']) {
1318                 $this->assertEquals(1, count($event['attendee']), 'attendee count mismatch: ' . print_r($event, TRUE));
1319             } else {
1320                 $this->assertEquals(2, count($event['attendee']), 'attendee count mismatch: ' . print_r($event, TRUE));
1321             }
1322         }
1323     }
1324
1325     /**
1326      * testExdateUpdateAllAddAttendee
1327      * 
1328      * @see 0007690: allow to update the whole series / thisandfuture when updating recur exceptions
1329      */
1330     public function testExdateUpdateAllAddAttendee()
1331     {
1332         $events = $this->testCreateRecurException();
1333         $exception = $this->_getException($events, 1);
1334         // add new attender
1335         $exception['attendee'][] = $this->_getUserTypeAttender();
1336         
1337         $updatedEvent = $this->_uit->saveEvent($exception, FALSE, Calendar_Model_Event::RANGE_ALL);
1338         $this->assertEquals(3, count($updatedEvent['attendee']), 'attender not added to exception: ' . print_r($updatedEvent, TRUE));
1339         
1340         $search = $this->_uit->searchEvents($events['filter'], NULL);
1341         foreach ($search['results'] as $event) {
1342             $this->assertEquals(3, count($event['attendee']), 'attendee count mismatch: ' . print_r($event, TRUE));
1343         }
1344     }
1345     
1346     /**
1347      * testExdateUpdateThisAndFutureChangeDtstart
1348      * 
1349      * @see 0007690: allow to update the whole series / thisandfuture when updating recur exceptions
1350      */
1351     public function testExdateUpdateThisAndFutureChangeDtstart()
1352     {
1353         $events = $this->testCreateRecurException();
1354         $exception = $this->_getException($events, 1);
1355         $exception['dtstart'] = '2009-04-01 08:00:00';
1356         $exception['dtend'] = '2009-04-01 08:15:00';
1357         
1358         $updatedEvent = $this->_uit->saveEvent($exception, FALSE, Calendar_Model_Event::RANGE_THISANDFUTURE);
1359         
1360         $search = $this->_uit->searchEvents($events['filter'], NULL);
1361         foreach ($search['results'] as $event) {
1362             if ($event['dtstart'] >= $updatedEvent['dtstart']) {
1363                 $this->assertContains('08:00:00', $event['dtstart'], 'wrong dtstart: ' . print_r($event, TRUE));
1364                 $this->assertContains('08:15:00', $event['dtend']);
1365             } else {
1366                 $this->assertContains('06:00:00', $event['dtstart'], 'wrong dtstart: ' . print_r($event, TRUE));
1367                 $this->assertContains('06:15:00', $event['dtend']);
1368             }
1369         }
1370     }
1371     
1372     /**
1373      * testExdateUpdateAllWithModlog
1374      * - change base event, then update all
1375      * 
1376      * @see 0007690: allow to update the whole series / thisandfuture when updating recur exceptions
1377      * @see 0009340: fix Calendar_JsonTests::testExdateUpdateAllWithModlog*
1378      */
1379     public function testExdateUpdateAllWithModlog()
1380     {
1381         $this->markTestSkipped('this test is broken: see 0009340: fix Calendar_JsonTests::testExdateUpdateAllWithModlog*');
1382         
1383         $events = $this->testCreateRecurException();
1384         $baseEvent = $events['results'][0];
1385         $exception = $this->_getException($events, 1);
1386         
1387         $baseEvent['summary'] = 'Get up, lazyboy!';
1388         $baseEvent = $this->_uit->saveEvent($baseEvent);
1389         sleep(1);
1390         
1391         $exception['summary'] = 'new summary';
1392         $updatedEvent = $this->_uit->saveEvent($exception, FALSE, Calendar_Model_Event::RANGE_ALL);
1393         
1394         $search = $this->_uit->searchEvents($events['filter'], NULL);
1395         foreach ($search['results'] as $event) {
1396             if ($event['dtstart'] == $updatedEvent['dtstart']) {
1397                 $this->assertEquals('new summary', $event['summary'], 'Recur exception should have the new summary');
1398             } else {
1399                 $this->assertEquals('Get up, lazyboy!', $event['summary'], 'Wrong summary in base/recur event: ' . print_r($event, TRUE));
1400             }
1401         }
1402     }
1403
1404     /**
1405      * testExdateUpdateAllWithModlogAddAttender
1406      * - change base event, then update all
1407      * 
1408      * @see 0007690: allow to update the whole series / thisandfuture when updating recur exceptions
1409      * @see 0007826: add attendee changes to modlog
1410      * @see 0009340: fix Calendar_JsonTests::testExdateUpdateAllWithModlog*
1411      */
1412     public function testExdateUpdateAllWithModlogAddAttender()
1413     {
1414         $this->markTestSkipped('0009340: fix Calendar_JsonTests::testExdateUpdateAllWithModlogAddAttender');
1415         
1416         $events = $this->testCreateRecurException();
1417         $baseEvent = $events['results'][0];
1418         $exception = $this->_getException($events, 1);
1419         
1420         // add new attender
1421         $baseEvent['attendee'][] = $this->_getUserTypeAttender();
1422         $baseEvent = $this->_uit->saveEvent($baseEvent);
1423         $this->assertEquals(3, count($baseEvent['attendee']), 'Attendee count mismatch in baseEvent: ' . print_r($baseEvent, TRUE));
1424         sleep(1);
1425         
1426         // check recent changes (needs to contain attendee change)
1427         $exdate = Calendar_Controller_Event::getInstance()->get($exception['id']);
1428         $recentChanges = Tinebase_Timemachine_ModificationLog::getInstance()->getModifications('Calendar', $baseEvent['id'], NULL, 'Sql', $exdate->creation_time);
1429         $this->assertGreaterThan(2, count($recentChanges), 'Did not get all recent changes: ' . print_r($recentChanges->toArray(), TRUE));
1430         $this->assertTrue(in_array('attendee', $recentChanges->modified_attribute), 'Attendee change missing: ' . print_r($recentChanges->toArray(), TRUE));
1431         
1432         $exception['attendee'][] = $this->_getUserTypeAttender('unittestnotexists@example.com');
1433         $updatedEvent = $this->_uit->saveEvent($exception, FALSE, Calendar_Model_Event::RANGE_ALL);
1434         
1435         $search = $this->_uit->searchEvents($events['filter'], NULL);
1436         foreach ($search['results'] as $event) {
1437             if ($event['dtstart'] == $updatedEvent['dtstart']) {
1438                 $this->assertEquals(3, count($event['attendee']), 'Attendee count mismatch in exdate: ' . print_r($event, TRUE));
1439             } else {
1440                 $this->assertEquals(4, count($event['attendee']), 'Attendee count mismatch: ' . print_r($event, TRUE));
1441             }
1442         }
1443     }
1444
1445     /**
1446      * testConcurrentAttendeeChangeAdd
1447      * 
1448      * @see 0008078: concurrent attendee change should be merged
1449      */
1450     public function testConcurrentAttendeeChangeAdd()
1451     {
1452         $eventData = $this->testCreateEvent();
1453         $numAttendee = count($eventData['attendee']);
1454         $eventData['attendee'][$numAttendee] = array(
1455             'user_id' => $this->_getPersonasContacts('pwulf')->getId(),
1456         );
1457         $this->_uit->saveEvent($eventData);
1458         
1459         $eventData['attendee'][$numAttendee] = array(
1460             'user_id' => $this->_getPersonasContacts('jsmith')->getId(),
1461         );
1462         $event = $this->_uit->saveEvent($eventData);
1463         
1464         $this->assertEquals(4, count($event['attendee']), 'both new attendee (pwulf + jsmith) should be added: ' . print_r($event['attendee'], TRUE));
1465     }
1466
1467     /**
1468      * testConcurrentAttendeeChangeRemove
1469      * 
1470      * @see 0008078: concurrent attendee change should be merged
1471      */
1472     public function testConcurrentAttendeeChangeRemove()
1473     {
1474         $eventData = $this->testCreateEvent();
1475         $currentAttendee = $eventData['attendee'];
1476         unset($eventData['attendee'][1]);
1477         $event = $this->_uit->saveEvent($eventData);
1478         
1479         $eventData['attendee'] = $currentAttendee;
1480         $numAttendee = count($eventData['attendee']);
1481         $eventData['attendee'][$numAttendee] = array(
1482             'user_id' => $this->_getPersonasContacts('pwulf')->getId(),
1483         );
1484         $event = $this->_uit->saveEvent($eventData);
1485         
1486         $this->assertEquals(2, count($event['attendee']), 'one attendee should added and one removed: ' . print_r($event['attendee'], TRUE));
1487     }
1488
1489     /**
1490      * testConcurrentAttendeeChangeUpdate
1491      * 
1492      * @see 0008078: concurrent attendee change should be merged
1493      */
1494     public function testConcurrentAttendeeChangeUpdate()
1495     {
1496         $eventData = $this->testCreateEvent();
1497         $currentAttendee = $eventData['attendee'];
1498         $adminIndex = ($eventData['attendee'][0]['user_id']['n_fn'] === 'Susan Clever') ? 1 : 0;
1499         $eventData['attendee'][$adminIndex]['status'] = Calendar_Model_Attender::STATUS_TENTATIVE;
1500         $event = $this->_uit->saveEvent($eventData);
1501         
1502         $loggedMods = Tinebase_Timemachine_ModificationLog::getInstance()->getModificationsBySeq(
1503             Tinebase_Application::getInstance()->getApplicationByName('Calendar')->getId(),
1504             new Calendar_Model_Attender($eventData['attendee'][$adminIndex]), 2);
1505         $this->assertEquals(1, count($loggedMods), 'attender modification has not been logged');
1506         
1507         $eventData['attendee'] = $currentAttendee;
1508         $scleverIndex = ($adminIndex === 1) ? 0 : 1;
1509         $attendeeBackend = new Calendar_Backend_Sql_Attendee();
1510         $eventData['attendee'][$scleverIndex]['status_authkey'] = $attendeeBackend->get($eventData['attendee'][$scleverIndex]['id'])->status_authkey;
1511         $eventData['attendee'][$scleverIndex]['status'] = Calendar_Model_Attender::STATUS_TENTATIVE;
1512         $event = $this->_uit->saveEvent($eventData);
1513
1514         foreach ($event['attendee'] as $attender) {
1515             $this->assertEquals(Calendar_Model_Attender::STATUS_TENTATIVE, $attender['status'], 'both attendee status should be TENTATIVE: ' . print_r($attender, TRUE));
1516         }
1517     }
1518
1519     /**
1520      * testFreeBusyCheckForExdates
1521      * 
1522      * @see 0008464: freebusy check does not work when creating recur exception
1523      */
1524     public function testFreeBusyCheckForExdates()
1525     {
1526         $events = $this->testCreateRecurException();
1527         $exception = $this->_getException($events, 1);
1528         
1529         $anotherEvent = $this->_getEvent(TRUE);
1530         $anotherEvent = $this->_uit->saveEvent($anotherEvent->toArray());
1531         
1532         $exception['dtstart'] = $anotherEvent['dtstart'];
1533         $exception['dtend'] = $anotherEvent['dtend'];
1534         
1535         try {
1536             $event = $this->_uit->saveEvent($exception, TRUE);
1537             $this->fail('Calendar_Exception_AttendeeBusy expected when saving exception: ' . print_r($exception, TRUE));
1538         } catch (Calendar_Exception_AttendeeBusy $ceab) {
1539             $this->assertEquals('Calendar_Exception_AttendeeBusy', get_class($ceab));
1540         }
1541     }
1542     
1543     /**
1544      * testAddAttachmentToRecurSeries
1545      * 
1546      * @see 0005024: allow to attach external files to records
1547      */
1548     public function testAddAttachmentToRecurSeries()
1549     {
1550         $tempFile = $this->_getTempFile();
1551         $recurSet = Tinebase_Helper::array_value('results', $this->testSearchRecuringIncludes());
1552         // update recurseries 
1553         $someRecurInstance = $recurSet[2];
1554         $someRecurInstance['attachments'] = array(array('tempFile' => array('id' => $tempFile->getId())));
1555         $someRecurInstance['seq'] = 2;
1556         $this->_uit->updateRecurSeries($someRecurInstance, FALSE, FALSE);
1557         
1558         $searchResultData = $this->_searchRecurSeries($recurSet[0]);
1559         foreach ($searchResultData['results'] as $recurInstance) {
1560             $this->assertTrue(isset($recurInstance['attachments']), 'no attachments found in event: ' . print_r($recurInstance, TRUE));
1561             $this->assertEquals(1, count($recurInstance['attachments']));
1562             $attachment = $recurInstance['attachments'][0];
1563             $this->assertEquals('text/plain', $attachment['contenttype'], print_r($attachment, TRUE));
1564         }
1565     }
1566     
1567     /**
1568      * checks if manipulated dtend and dtstart gets set to the correct values on creating or updating an event
1569      * 
1570      * @see 0009696: time is not grayed out for all-day events
1571      */
1572     public function testWholedayEventTimes()
1573     {
1574         $event = $this->_getEvent(TRUE);
1575         $event->is_all_day_event = TRUE;
1576         
1577         $event = Calendar_Controller_Event::getInstance()->create($event);
1578         $event->setTimezone(Tinebase_Core::getUserTimezone());
1579         
1580         $this->assertEquals('00:00:00', $event->dtstart->format('H:i:s'));
1581         $this->assertEquals('23:59:59', $event->dtend->format('H:i:s'));
1582         
1583         $event->dtstart = Tinebase_DateTime::now();
1584         $event->dtend   = Tinebase_DateTime::now()->addHour(1);
1585         
1586         $event = Calendar_Controller_Event::getInstance()->update($event);
1587         $event->setTimezone(Tinebase_Core::getUserTimezone());
1588         
1589         $this->assertEquals('00:00:00', $event->dtstart->format('H:i:s'));
1590         $this->assertEquals('23:59:59', $event->dtend->format('H:i:s'));
1591     }
1592     
1593      /**
1594      * testAttendeeChangeQuantityToInvalid
1595      * 
1596      * @see 9630: sanitize attender quantity
1597      */
1598     public function testAttendeeChangeQuantityToInvalid()
1599     {
1600         $eventData = $this->testCreateEvent();
1601         $currentAttendee = $eventData['attendee'];
1602         $eventData['attendee'][1]['quantity'] = '';
1603         $event = $this->_uit->saveEvent($eventData);
1604         $this->assertEquals(1, $event['attendee'][1]['quantity'], 'The invalid quantity should be saved as 1');
1605     }
1606
1607     /**
1608      * trigger caldav import by json frontend
1609      * 
1610      * @todo use mock as fallback (if server can not be reached by curl)
1611      * @todo get servername from unittest config / skip or mock if no servername found
1612      */
1613     public function testCalDAVImport()
1614     {
1615         // Skip if tine20.com.local could not be resolved
1616         if (gethostbyname('tine20.com.local') == 'tine20.com.local') {
1617             $this->markTestSkipped('Can\'t perform test, because instance is not reachable.');
1618         }
1619
1620         $this->_testNeedsTransaction();
1621         
1622         $event = $this->testCreateEvent(/* $now = */ true);
1623         
1624         $fe = new Calendar_Frontend_Json();
1625         $testUserCredentials = TestServer::getInstance()->getTestCredentials();
1626         $fe->importRemoteEvents(
1627             'http://tine20.com.local/calendars/' . Tinebase_Core::getUser()->contact_id . '/' . $event['container_id']['id'],
1628             Tinebase_Model_Import::INTERVAL_DAILY,
1629             array(
1630                 'container_id'          => 'remote_caldav_calendar',
1631                 'sourceType'            => 'remote_caldav',
1632                 'importFileByScheduler' => false,
1633                 'allowDuplicateEvents'  => true,
1634                 'username'              => $testUserCredentials['username'],
1635                 'password'              => $testUserCredentials['password'],
1636             ));
1637
1638         $importScheduler = Tinebase_Controller_ScheduledImport::getInstance();
1639         $record = $importScheduler->runNextScheduledImport();
1640
1641         $container = Tinebase_Container::getInstance()->getContainerByName('Calendar', 'remote_caldav_calendar', Tinebase_Model_Container::TYPE_PERSONAL, Tinebase_Core::getUser()->getId());
1642         $this->_testCalendars[] = $container;
1643         $this->assertTrue($container instanceof Tinebase_Model_Container, 'Container was not created');
1644
1645         $this->assertNotEquals($record, null, 'The import could not start!');
1646         
1647         $filter = $this->_getEventFilterArray($container->getId());
1648         $result = $this->_uit->searchEvents($filter, array());
1649         $this->assertEquals(1, $result['totalcount']);
1650     }
1651     
1652     /**
1653      * testGetRelations
1654      * 
1655      * @see 0009542: load event relations on demand
1656      */
1657     public function testGetRelations()
1658     {
1659         $contact = Addressbook_Controller_Contact::getInstance()->create(new Addressbook_Model_Contact(array(
1660             'n_family' => 'Contact for relations test'
1661         )));
1662         $eventData = $this->_getEvent()->toArray();
1663         $eventData['relations'] = array(
1664             array(
1665                 'own_model' => 'Calendar_Model_Event',
1666                 'own_backend' => 'Sql',
1667                 'own_id' => 0,
1668                 'related_degree' => Tinebase_Model_Relation::DEGREE_SIBLING,
1669                 'type' => '',
1670                 'related_backend' => 'Sql',
1671                 'related_id' => $contact->getId(),
1672                 'related_model' => 'Addressbook_Model_Contact',
1673                 'remark' => NULL,
1674             ));
1675         $event = $this->_uit->saveEvent($eventData);
1676
1677         $tfj = new Tinebase_Frontend_Json();
1678         $relations = $tfj->getRelations('Calendar_Model_Event', $event['id']);
1679
1680         $this->assertEquals(1, $relations['totalcount']);
1681         $this->assertEquals($contact->n_fn, $relations['results'][0]['related_record']['n_family'], print_r($relations['results'], true));
1682     }
1683 }