3ae358128e265b90ce73ffcabbd54290c76b6930
[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-2013 Metaways Infosystems GmbH (http://www.metaways.de)
8  * @author      Cornelius Weiss <c.weiss@metaways.de>
9  */
10
11 /**
12  * Test helper
13  */
14 require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'TestHelper.php';
15
16 /**
17  * Test class for Json Frontend
18  * 
19  * @package     Calendar
20  */
21 class Calendar_JsonTests extends Calendar_TestCase
22 {
23     /**
24      * Calendar Json Object
25      *
26      * @var Calendar_Frontend_Json
27      */
28     protected $_uit = null;
29     
30     /**
31      * (non-PHPdoc)
32      * @see Calendar/Calendar_TestCase::setUp()
33      */
34     public function setUp()
35     {
36         parent::setUp();
37         $this->_uit = new Calendar_Frontend_Json();
38     }
39     
40     /**
41      * testGetRegistryData
42      */
43     public function testGetRegistryData()
44     {
45         $registryData = $this->_uit->getRegistryData();
46         
47         $this->assertTrue(is_array($registryData['defaultContainer']['account_grants']));
48     }
49     
50     /**
51      * testCreateEvent
52      * 
53      * @param $now should the current date be used
54      */
55     public function testCreateEvent($now = FALSE)
56     {
57         $scleverDisplayContainerId = Tinebase_Core::getPreference('Calendar')->getValueForUser(Calendar_Preference::DEFAULTCALENDAR, $this->_personas['sclever']->getId());
58         $contentSeqBefore = Tinebase_Container::getInstance()->getContentSequence($scleverDisplayContainerId);
59         
60         $eventData = $this->_getEvent($now)->toArray();
61         
62         $tag = Tinebase_Tags::getInstance()->createTag(new Tinebase_Model_Tag(array(
63             'name' => 'phpunit-' . substr(Tinebase_Record_Abstract::generateUID(), 0, 10),
64             'type' => Tinebase_Model_Tag::TYPE_PERSONAL
65         )));
66         $eventData['tags'] = array($tag->toArray());
67         
68         $note = new Tinebase_Model_Note(array(
69             'note'         => 'very important note!',
70             'note_type_id' => Tinebase_Notes::getInstance()->getNoteTypes()->getFirstRecord()->getId(),
71         ));
72         $eventData['notes'] = array($note->toArray());
73         
74         $persistentEventData = $this->_uit->saveEvent($eventData);
75         $loadedEventData = $this->_uit->getEvent($persistentEventData['id']);
76         
77         $this->_assertJsonEvent($eventData, $loadedEventData, 'failed to create/load event');
78         
79         $contentSeqAfter = Tinebase_Container::getInstance()->getContentSequence($scleverDisplayContainerId);
80         $this->assertEquals($contentSeqBefore + 1, $contentSeqAfter,
81             'content sequence of display container should be increased by 1:' . $contentSeqAfter);
82         $this->assertEquals($contentSeqAfter, Tinebase_Container::getInstance()->get($scleverDisplayContainerId)->content_seq);
83         
84         return $loadedEventData;
85     }
86     
87     public function testStripWindowsLinebreaks()
88     {
89         $e = $this->_getEvent(TRUE);
90         $e->description = 'Hello my friend,' . chr(13) . chr(10) .'bla bla bla.'  . chr(13) . chr(10) .'good bye.';
91         $persistentEventData = $this->_uit->saveEvent($e->toArray());
92         $loadedEventData = $this->_uit->getEvent($persistentEventData['id']);
93         $this->assertEquals($loadedEventData['description'], 'Hello my friend,' . chr(10) . 'bla bla bla.' . chr(10) . 'good bye.');
94     }
95
96     /**
97     * testCreateEventWithNonExistantAttender
98     */
99     public function testCreateEventWithNonExistantAttender()
100     {
101         $testEmail = 'unittestnotexists@example.org';
102         $eventData = $this->_getEvent(TRUE)->toArray();
103         $eventData['attendee'][] = $this->_getUserTypeAttender($testEmail);
104         
105         $persistentEventData = $this->_uit->saveEvent($eventData);
106         $found = FALSE;
107         foreach ($persistentEventData['attendee'] as $attender) {
108             if ($attender['user_id']['email'] === $testEmail) {
109                 $this->assertEquals($testEmail, $attender['user_id']['n_fn']);
110                 $found = TRUE;
111             }
112         }
113         $this->assertTrue($found);
114     }
115     
116     /**
117      * get single attendee array
118      * 
119      * @param string $email
120      * @return array
121      */
122     protected function _getUserTypeAttender($email = 'unittestnotexists@example.org')
123     {
124         return array(
125             'user_id'        => $email,
126             'user_type'      => Calendar_Model_Attender::USERTYPE_USER,
127             'role'           => Calendar_Model_Attender::ROLE_REQUIRED,
128         );
129     }
130     
131     /**
132      * test create event with alarm
133      *
134      * @todo add testUpdateEventWithAlarm
135      */
136     public function testCreateEventWithAlarm()
137     {
138         $eventData = $this->_getEventWithAlarm(TRUE)->toArray();
139         $persistentEventData = $this->_uit->saveEvent($eventData);
140         $loadedEventData = $this->_uit->getEvent($persistentEventData['id']);
141         
142         //print_r($loadedEventData);
143         
144         // check if alarms are created / returned
145         $this->assertGreaterThan(0, count($loadedEventData['alarms']));
146         $this->assertEquals('Calendar_Model_Event', $loadedEventData['alarms'][0]['model']);
147         $this->assertEquals(Tinebase_Model_Alarm::STATUS_PENDING, $loadedEventData['alarms'][0]['sent_status']);
148         $this->assertTrue(array_key_exists('minutes_before', $loadedEventData['alarms'][0]), 'minutes_before is missing');
149         
150         $scheduler = Tinebase_Core::getScheduler();
151         $scheduler->addTask('Tinebase_Alarm', $this->createTask());
152         $scheduler->run();
153         
154         // check alarm status
155         $loadedEventData = $this->_uit->getEvent($persistentEventData['id']);
156         $this->assertEquals(Tinebase_Model_Alarm::STATUS_SUCCESS, $loadedEventData['alarms'][0]['sent_status']);
157     }
158     
159     /**
160      * createTask
161      */
162     public function createTask()
163     {
164         $request = new Zend_Controller_Request_Http();
165         $request->setControllerName('Tinebase_Alarm');
166         $request->setActionName('sendPendingAlarms');
167         $request->setParam('eventName', 'Tinebase_Event_Async_Minutely');
168         
169         $task = new Tinebase_Scheduler_Task();
170         $task->setMonths("Jan-Dec");
171         $task->setWeekdays("Sun-Sat");
172         $task->setDays("1-31");
173         $task->setHours("0-23");
174         $task->setMinutes("0/1");
175         $task->setRequest($request);
176         return $task;
177     }
178     
179     /**
180      * testUpdateEvent
181      */
182     public function testUpdateEvent()
183     {
184         $event = new Calendar_Model_Event($this->testCreateEvent(), true);
185         $event->dtstart->addHour(5);
186         $event->dtend->addHour(5);
187         $event->description = 'are you kidding?';
188         
189         $eventData = $event->toArray();
190         foreach ($eventData['attendee'] as $key => $attenderData) {
191             if ($eventData['attendee'][$key]['user_id'] != $this->_testUserContact->getId()) {
192                 unset($eventData['attendee'][$key]);
193             }
194         }
195         
196         $updatedEventData = $this->_uit->saveEvent($eventData);
197         
198         $this->_assertJsonEvent($eventData, $updatedEventData, 'failed to update event');
199         
200         return $updatedEventData;
201     }
202
203     /**
204      * testDeleteEvent
205      */
206     public function testDeleteEvent() {
207         $eventData = $this->testCreateEvent();
208         
209         $this->_uit->deleteEvents(array($eventData['id']));
210         
211         $this->setExpectedException('Tinebase_Exception_NotFound');
212         $this->_uit->getEvent($eventData['id']);
213     }
214     
215     /**
216      * testSearchEvents
217      */
218     public function testSearchEvents()
219     {
220         $eventData = $this->testCreateEvent(TRUE); 
221         
222         $filter = $this->_getEventFilterArray();
223         $searchResultData = $this->_uit->searchEvents($filter, array());
224         $resultEventData = $searchResultData['results'][0];
225         
226         $this->_assertJsonEvent($eventData, $resultEventData, 'failed to search event');
227         return $searchResultData;
228     }
229     
230     /**
231      * get filter array with container and period filter
232      * 
233      * @param string|int $containerId
234      * @return multitype:multitype:string Ambigous <number, multitype:>  multitype:string multitype:string
235      */
236     protected function _getEventFilterArray($containerId = NULL)
237     {
238         $containerId = ($containerId) ? $containerId : $this->_testCalendar->getId();
239         return array(
240             array('field' => 'container_id', 'operator' => 'equals', 'value' => $containerId),
241             array('field' => 'period', 'operator' => 'within', 'value' =>
242                 array("from" => '2009-03-20 06:15:00', "until" => Tinebase_DateTime::now()->addDay(1)->toString())
243             )
244         );
245     }
246     
247     /**
248      * testSearchEvents with period filter
249      * 
250      * @todo add an event that is in result set of Calendar_Controller_Event::search() 
251      *       but should be removed in Calendar_Frontend_Json::_multipleRecordsToJson()
252      */
253     public function testSearchEventsWithPeriodFilter()
254     {
255         $eventData = $this->testCreateRecurEvent();
256         
257         $filter = array(
258             array('field' => 'period', 'operator' => 'within', 'value' => array(
259                 'from'  => '2009-03-25 00:00:00',
260                 'until' => '2009-03-25 23:59:59',
261             )),
262             array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_testCalendar->getId()),
263         );
264         
265         $searchResultData = $this->_uit->searchEvents($filter, array());
266         $resultEventData = $searchResultData['results'][0];
267         
268         $this->_assertJsonEvent($eventData, $resultEventData, 'failed to search event');
269     }
270     
271     /**
272      * #7688: Internal Server Error on calendar search
273      * 
274      * add period filter if none is given
275      * 
276      * https://forge.tine20.org/mantisbt/view.php?id=7688
277      */
278     public function testSearchEventsWithOutPeriodFilter()
279     {
280         $eventData = $this->testCreateRecurEvent();
281         $filter = array(array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_testCalendar->getId()));
282         
283         $searchResultData = $this->_uit->searchEvents($filter, array());
284         $returnedFilter = $searchResultData['filter'];
285         $this->assertEquals(2, count($returnedFilter), 'Two filters shoud have been returned!');
286         $this->assertTrue($returnedFilter[1]['field'] == 'period' || $returnedFilter[0]['field'] == 'period', 'One returned filter shoud be a period filter');
287     }
288
289     /**
290      * testSearchEvents with organizer = me filter
291      * 
292      * @see #6716: default favorite "me" is not resolved properly
293      */
294     public function testSearchEventsWithOrganizerMeFilter()
295     {
296         $eventData = $this->testCreateEvent(TRUE);
297         
298         $filter = $this->_getEventFilterArray();
299         $filter[] = array('field' => 'organizer', 'operator' => 'equals', 'value' => Addressbook_Model_Contact::CURRENTCONTACT);
300         
301         $searchResultData = $this->_uit->searchEvents($filter, array());
302         $resultEventData = $searchResultData['results'][0];
303         $this->_assertJsonEvent($eventData, $resultEventData, 'failed to search event');
304         
305         // check organizer filter resolving
306         $organizerfilter = $searchResultData['filter'][2];
307         $this->assertTrue(is_array($organizerfilter['value']), 'organizer should be resolved: ' . print_r($organizerfilter, TRUE));
308         $this->assertEquals(Tinebase_Core::getUser()->contact_id, $organizerfilter['value']['id']);
309     }
310     
311     /**
312      * search event with alarm
313      *
314      */
315     public function testSearchEventsWithAlarm()
316     {
317         $eventData = $this->_getEventWithAlarm(TRUE)->toArray();
318         $persistentEventData = $this->_uit->saveEvent($eventData);
319         
320         $searchResultData = $this->_uit->searchEvents($this->_getEventFilterArray(), array());
321         $resultEventData = $searchResultData['results'][0];
322         
323         $this->_assertJsonEvent($persistentEventData, $resultEventData, 'failed to search event with alarm');
324     }
325     
326     /**
327      * testSetAttenderStatus
328      */
329     public function testSetAttenderStatus()
330     {
331         $eventData = $this->testCreateEvent();
332         $numAttendee = count($eventData['attendee']);
333         $eventData['attendee'][$numAttendee] = array(
334             'user_id' => $this->_personasContacts['pwulf']->getId(),
335         );
336         
337         $updatedEventData = $this->_uit->saveEvent($eventData);
338         $pwulf = $this->_findAttender($updatedEventData['attendee'], 'pwulf');
339         
340         // he he, we don't have his authkey, cause json class sorts it out due to rights restrictions.
341         $attendeeBackend = new Calendar_Backend_Sql_Attendee();
342         $pwulf['status_authkey'] = $attendeeBackend->get($pwulf['id'])->status_authkey;
343         
344         $updatedEventData['container_id'] = $updatedEventData['container_id']['id'];
345         
346         $pwulf['status'] = Calendar_Model_Attender::STATUS_ACCEPTED;
347         $this->_uit->setAttenderStatus($updatedEventData, $pwulf, $pwulf['status_authkey']);
348         
349         $loadedEventData = $this->_uit->getEvent($eventData['id']);
350         $loadedPwulf = $this->_findAttender($loadedEventData['attendee'], 'pwulf');
351         $this->assertEquals(Calendar_Model_Attender::STATUS_ACCEPTED, $loadedPwulf['status']);
352     }
353     
354     /**
355      * testCreateRecurEvent
356      */
357     public function testCreateRecurEvent()
358     {
359         $eventData = $this->testCreateEvent();
360         $eventData['rrule'] = array(
361             'freq'     => 'WEEKLY',
362             'interval' => 1,
363             'byday'    => 'WE'
364         );
365         
366         $updatedEventData = $this->_uit->saveEvent($eventData);
367         $this->assertTrue(is_array($updatedEventData['rrule']));
368
369         return $updatedEventData;
370     }
371     
372     /**
373      * testCreateRecurEventWithRruleUntil
374      * 
375      * @see 0008906: rrule_until is saved in usertime
376      */
377     public function testCreateRecurEventWithRruleUntil()
378     {
379         $eventData = $this->testCreateRecurEvent();
380         $localMidnight = Tinebase_DateTime::now()->setTime(23,59,59)->toString();
381         $eventData['rrule']['until'] = $localMidnight;
382         //$eventData['rrule']['freq']  = 'WEEKLY';
383         
384         $updatedEventData = $this->_uit->saveEvent($eventData);
385         $this->assertGreaterThanOrEqual($localMidnight, $updatedEventData['rrule']['until']);
386         
387         // check db record
388         $calbackend = new Calendar_Backend_Sql();
389         $db = $calbackend->getAdapter();
390         $select = $db->select();
391         $select->from(array($calbackend->getTableName() => $calbackend->getTablePrefix() . $calbackend->getTableName()), array('rrule_until', 'rrule'))->limit(1);
392         $select->where($db->quoteIdentifier($calbackend->getTableName() . '.id') . ' = ?', $updatedEventData['id']);
393         
394         $stmt = $db->query($select);
395         $queryResult = $stmt->fetch();
396         
397 //         echo Tinebase_Core::get(Tinebase_Core::USERTIMEZONE);
398 //         echo date_default_timezone_get();
399         
400         $midnightInUTC = new Tinebase_DateTime($queryResult['rrule_until']);
401         $this->assertEquals(Tinebase_DateTime::now()->setTime(23,59,59)->toString(), $midnightInUTC->setTimezone(Tinebase_Core::get(Tinebase_Core::USERTIMEZONE), TRUE)->toString());
402     }
403     
404     /**
405     * testSearchRecuringIncludes
406     */
407     public function testSearchRecuringIncludes()
408     {
409         $recurEvent = $this->testCreateRecurEvent();
410     
411         $from = $recurEvent['dtstart'];
412         $until = new Tinebase_DateTime($from);
413         $until->addWeek(5)->addHour(10);
414         $until = $until->get(Tinebase_Record_Abstract::ISO8601LONG);
415     
416         $filter = array(
417         array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_testCalendar->getId()),
418         array('field' => 'period',       'operator' => 'within', 'value' => array('from' => $from, 'until' => $until)),
419         );
420     
421         $searchResultData = $this->_uit->searchEvents($filter, array());
422     
423         $this->assertEquals(6, $searchResultData['totalcount']);
424         
425         // test appending tags to recurring instances
426         $this->assertEquals('phpunit-', substr($searchResultData['results'][4]['tags'][0]['name'], 0, 8));
427     
428         return $searchResultData;
429     }
430     
431     /**
432      * testSearchRecuringIncludesAndSort
433      */
434     public function testSearchRecuringIncludesAndSort()
435     {
436         $recurEvent = $this->testCreateRecurEvent();
437         
438         $from = $recurEvent['dtstart'];
439         $until = new Tinebase_DateTime($from);
440         $until->addWeek(5)->addHour(10);
441         $until = $until->get(Tinebase_Record_Abstract::ISO8601LONG);
442         
443         $filter = array(
444             array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_testCalendar->getId()),
445             array('field' => 'period',       'operator' => 'within', 'value' => array('from' => $from, 'until' => $until)),
446         );
447         
448         $searchResultData = $this->_uit->searchEvents($filter, array('sort' => 'dtstart', 'dir' => 'DESC'));
449         
450         $this->assertEquals(6, $searchResultData['totalcount']);
451         
452         // check sorting
453         $this->assertEquals('2009-04-29 06:00:00', $searchResultData['results'][0]['dtstart']);
454         $this->assertEquals('2009-04-22 06:00:00', $searchResultData['results'][1]['dtstart']);
455     }
456     
457     /**
458      * testCreateRecurException
459      */
460     public function testCreateRecurException()
461     {
462         $recurSet = array_value('results', $this->testSearchRecuringIncludes());
463         
464         $persistentException = $recurSet[1];
465         $persistentException['summary'] = 'go sleeping';
466         
467         // create persistent exception
468         $this->_uit->createRecurException($persistentException, FALSE, FALSE);
469         
470         // create exception date
471         $updatedBaseEvent = Calendar_Controller_Event::getInstance()->getRecurBaseEvent(new Calendar_Model_Event($recurSet[2]));
472         $recurSet[2]['last_modified_time'] = $updatedBaseEvent->last_modified_time;
473         $this->_uit->createRecurException($recurSet[2], TRUE, FALSE);
474         
475         // delete all following (including this)
476         $updatedBaseEvent = Calendar_Controller_Event::getInstance()->getRecurBaseEvent(new Calendar_Model_Event($recurSet[4]));
477         $recurSet[4]['last_modified_time'] = $updatedBaseEvent->last_modified_time;
478         $this->_uit->createRecurException($recurSet[4], TRUE, TRUE);
479         
480         $from = $recurSet[0]['dtstart'];
481         $until = new Tinebase_DateTime($from);
482         $until->addWeek(5)->addHour(10);
483         $until = $until->get(Tinebase_Record_Abstract::ISO8601LONG);
484         
485         $filter = array(
486             array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_testCalendar->getId()),
487             array('field' => 'period',       'operator' => 'within', 'value' => array('from' => $from, 'until' => $until)),
488         );
489         
490         $searchResultData = $this->_uit->searchEvents($filter, array('sort' => 'dtstart'));
491         
492         // we deleted one and cropped
493         $this->assertEquals(3, count($searchResultData['results']));
494         
495         $summaryMap = array();
496         foreach ($searchResultData['results'] as $event) {
497             $summaryMap[$event['dtstart']] = $event['summary'];
498         }
499         $this->assertTrue(array_key_exists('2009-04-01 06:00:00', $summaryMap));
500         $this->assertEquals($persistentException['summary'], $summaryMap['2009-04-01 06:00:00']);
501         
502         return $searchResultData;
503     }
504     
505     /**
506      * testCreateRecurExceptionWithOtherUser
507      * 
508      * @see 0008172: displaycontainer_id not set when recur exception is created
509      */
510     public function testCreateRecurExceptionWithOtherUser()
511     {
512         $recurSet = array_value('results', $this->testSearchRecuringIncludes());
513         
514         // create persistent exception (just status update)
515         $persistentException = $recurSet[1];
516         $scleverAttender = $this->_findAttender($persistentException['attendee'], 'sclever');
517         $attendeeBackend = new Calendar_Backend_Sql_Attendee();
518         $status_authkey = $attendeeBackend->get($scleverAttender['id'])->status_authkey;
519         $scleverAttender['status'] = Calendar_Model_Attender::STATUS_ACCEPTED;
520         $scleverAttender['status_authkey'] = $status_authkey;
521         foreach ($persistentException['attendee'] as $key => $attender) {
522             if ($attender['id'] === $scleverAttender['id']) {
523                 $persistentException['attendee'][$key] = $scleverAttender;
524                 break;
525             }
526         }
527         
528         // sclever has only READ grant
529         Tinebase_Container::getInstance()->setGrants($this->_testCalendar, new Tinebase_Record_RecordSet('Tinebase_Model_Grants', array(array(
530             'account_id'    => $this->_testUser->getId(),
531             'account_type'  => 'user',
532             Tinebase_Model_Grants::GRANT_READ     => true,
533             Tinebase_Model_Grants::GRANT_ADD      => true,
534             Tinebase_Model_Grants::GRANT_EDIT     => true,
535             Tinebase_Model_Grants::GRANT_DELETE   => true,
536             Tinebase_Model_Grants::GRANT_PRIVATE  => true,
537             Tinebase_Model_Grants::GRANT_ADMIN    => true,
538             Tinebase_Model_Grants::GRANT_FREEBUSY => true,
539         ), array(
540             'account_id'    => $this->_personas['sclever']->getId(),
541             'account_type'  => 'user',
542             Tinebase_Model_Grants::GRANT_READ     => true,
543             Tinebase_Model_Grants::GRANT_FREEBUSY => true,
544         ))), TRUE);
545         
546         $unittestUser = Tinebase_Core::getUser();
547         Tinebase_Core::set(Tinebase_Core::USER, $this->_personas['sclever']);
548         
549         // create persistent exception
550         $createdException = $this->_uit->createRecurException($persistentException, FALSE, FALSE);
551         Tinebase_Core::set(Tinebase_Core::USER, $unittestUser);
552         
553         $sclever = $this->_findAttender($createdException['attendee'], 'sclever');
554         $this->assertEquals('Susan Clever', $sclever['user_id']['n_fn']);
555         $this->assertEquals(Calendar_Model_Attender::STATUS_ACCEPTED, $sclever['status'], 'status mismatch: ' . print_r($sclever, TRUE));
556         $this->assertTrue(is_array($sclever['displaycontainer_id']));
557         $this->assertEquals($this->_personasDefaultCals['sclever']['id'], $sclever['displaycontainer_id']['id']);
558     }
559     
560     /**
561      * testUpdateRecurSeries
562      */
563     public function testUpdateRecurSeries()
564     {
565         $recurSet = array_value('results', $this->testSearchRecuringIncludes());
566         
567         $persistentException = $recurSet[1];
568         $persistentException['summary'] = 'go sleeping';
569         $persistentException['dtstart'] = '2009-04-01 20:00:00';
570         $persistentException['dtend']   = '2009-04-01 20:30:00';
571         
572         // create persistent exception
573         $recurResult = $this->_uit->createRecurException($persistentException, FALSE, FALSE);
574         
575         // update recurseries 
576         $someRecurInstance = $recurSet[2];
577         $someRecurInstance['summary'] = 'go fishing';
578         $someRecurInstance['dtstart'] = '2009-04-08 10:00:00';
579         $someRecurInstance['dtend']   = '2009-04-08 12:30:00';
580         
581         $someRecurInstance['seq'] = 2;
582         $this->_uit->updateRecurSeries($someRecurInstance, FALSE, FALSE);
583         
584         $from = $recurSet[0]['dtstart'];
585         $until = new Tinebase_DateTime($from);
586         $until->addWeek(5)->addHour(10);
587         $until = $until->get(Tinebase_Record_Abstract::ISO8601LONG);
588         
589         $filter = array(
590             array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_testCalendar->getId()),
591             array('field' => 'period',       'operator' => 'within', 'value' => array('from' => $from, 'until' => $until)),
592         );
593         
594         $searchResultData = $this->_uit->searchEvents($filter, array());
595         
596         $this->assertEquals(6, count($searchResultData['results']));
597         
598         $summaryMap = array();
599         foreach ($searchResultData['results'] as $event) {
600             $summaryMap[$event['dtstart']] = $event['summary'];
601         }
602         
603         $this->assertTrue(array_key_exists('2009-04-01 20:00:00', $summaryMap));
604         $this->assertEquals('go sleeping', $summaryMap['2009-04-01 20:00:00']);
605         
606         $fishings = array_keys($summaryMap, 'go fishing');
607         $this->assertEquals(5, count($fishings));
608         foreach ($fishings as $dtstart) {
609             $this->assertEquals('10:00:00', substr($dtstart, -8), 'all fishing events should start at 10:00');
610         }
611     }
612     
613     /**
614      * testUpdateRecurExceptionsFromSeriesOverDstMove
615      * 
616      * @todo implement
617      */
618     public function testUpdateRecurExceptionsFromSeriesOverDstMove()
619     {
620         /*
621          * 1. create recur event 1 day befor dst move
622          * 2. create an exception and exdate
623          * 3. move dtstart from 1 over dst boundary
624          * 4. test recurid and exdate by calculating series
625          */
626     }
627     
628     /**
629      * testDeleteRecurSeries
630      */
631     public function testDeleteRecurSeries()
632     {
633         $recurSet = array_value('results', $this->testSearchRecuringIncludes());
634         
635         $persistentException = $recurSet[1];
636         $persistentException['summary'] = 'go sleeping';
637         
638         // create persistent exception
639         $this->_uit->createRecurException($persistentException, FALSE, FALSE);
640         
641         // delete recurseries 
642         $someRecurInstance = $persistentException = $recurSet[2];
643         $this->_uit->deleteRecurSeries($someRecurInstance);
644         
645         $from = $recurSet[0]['dtstart'];
646         $until = new Tinebase_DateTime($from);
647         $until->addWeek(5)->addHour(10);
648         $until = $until->get(Tinebase_Record_Abstract::ISO8601LONG);
649         
650         $filter = array(
651             array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_testCalendar->getId()),
652             array('field' => 'period',       'operator' => 'within', 'value' => array('from' => $from, 'until' => $until)),
653         );
654         
655         $searchResultData = $this->_uit->searchEvents($filter, array());
656         
657         $this->assertEquals(0, count($searchResultData['results']));
658     }
659     
660     /**
661      * testMeAsAttenderFilter
662      */
663     public function testMeAsAttenderFilter()
664     {
665         $eventData = $this->testCreateEvent(TRUE);
666         
667         $filter = $this->_getEventFilterArray();
668         $filter[] = array('field' => 'attender'    , 'operator' => 'equals', 'value' => array(
669             'user_type' => Calendar_Model_Attender::USERTYPE_USER,
670             'user_id'   => Addressbook_Model_Contact::CURRENTCONTACT,
671         ));
672         
673         $searchResultData = $this->_uit->searchEvents($filter, array());
674         $resultEventData = $searchResultData['results'][0];
675         
676         $this->_assertJsonEvent($eventData, $resultEventData, 'failed to filter for me as attender');
677     }
678     
679     /**
680      * testFreeBusyCleanup
681      */
682     public function testFreeBusyCleanup()
683     {
684         // give fb grants from sclever
685         $scleverCal = Tinebase_Container::getInstance()->getContainerById($this->_personasDefaultCals['sclever']);
686         Tinebase_Container::getInstance()->setGrants($scleverCal->getId(), new Tinebase_Record_RecordSet('Tinebase_Model_Grants', array(array(
687             'account_id'    => $this->_personas['sclever']->getId(),
688             'account_type'  => 'user',
689             Tinebase_Model_Grants::GRANT_READ     => true,
690             Tinebase_Model_Grants::GRANT_ADD      => true,
691             Tinebase_Model_Grants::GRANT_EDIT     => true,
692             Tinebase_Model_Grants::GRANT_DELETE   => true,
693             Tinebase_Model_Grants::GRANT_PRIVATE  => true,
694             Tinebase_Model_Grants::GRANT_ADMIN    => true,
695             Tinebase_Model_Grants::GRANT_FREEBUSY => true,
696         ), array(
697             'account_id'    => $this->_testUser->getId(),
698             'account_type'  => 'user',
699             Tinebase_Model_Grants::GRANT_FREEBUSY => true,
700         ))), TRUE);
701         
702         Tinebase_Core::set(Tinebase_Core::USER, $this->_personas['sclever']);
703         $eventData = $this->_getEvent()->toArray();
704         unset($eventData['organizer']);
705         $eventData['container_id'] = $scleverCal->getId();
706         $eventData['attendee'] = array(array(
707             'user_id' => $this->_personasContacts['sclever']->getId()
708         ));
709         $eventData['organizer'] = $this->_personasContacts['sclever']->getId();
710         $eventData = $this->_uit->saveEvent($eventData);
711         $filter = $this->_getEventFilterArray($this->_personasDefaultCals['sclever']->getId());
712         $searchResultData = $this->_uit->searchEvents($filter, array());
713         $this->assertTrue(! empty($searchResultData['results']), 'expected event in search result (search by sclever): ' 
714             . print_r($eventData, TRUE) . 'search filter: ' . print_r($filter, TRUE));
715         
716         Tinebase_Core::set(Tinebase_Core::USER, $this->_testUser);
717         $searchResultData = $this->_uit->searchEvents($filter, array());
718         $this->assertTrue(! empty($searchResultData['results']), 'expected (freebusy cleanup) event in search result: ' 
719             . print_r($eventData, TRUE) . 'search filter: ' . print_r($filter, TRUE));
720         $eventData = $searchResultData['results'][0];
721         
722         $this->assertFalse(array_key_exists('summary', $eventData), 'summary not empty: ' . print_r($eventData, TRUE));
723         $this->assertFalse(array_key_exists('description', $eventData), 'description not empty');
724         $this->assertFalse(array_key_exists('tags', $eventData), 'tags not empty');
725         $this->assertFalse(array_key_exists('notes', $eventData), 'notes not empty');
726         $this->assertFalse(array_key_exists('attendee', $eventData), 'attendee not empty');
727         $this->assertFalse(array_key_exists('organizer', $eventData), 'organizer not empty');
728         $this->assertFalse(array_key_exists('alarms', $eventData), 'alarms not empty');
729     }
730     
731     /**
732      * test deleting container and the containing events
733      * #6704: events do not disappear when shared calendar got deleted
734      * https://forge.tine20.org/mantisbt/view.php?id=6704
735      */
736     public function testDeleteContainerAndEvents()
737     {
738         $fe = new Tinebase_Frontend_Json_Container();
739         $container = $fe->addContainer('Calendar', 'testdeletecontacts', Tinebase_Model_Container::TYPE_SHARED, '');
740         // test if model is set automatically
741         $this->assertEquals($container['model'], 'Calendar_Model_Event');
742         
743         $date = new Tinebase_DateTime();
744         $event = new Calendar_Model_Event(array(
745             'dtstart' => $date,
746             'dtend'    => $date->subHour(1),
747             'summary' => 'bla bla',
748             'class'    => 'PUBLIC',
749             'transp'    => 'OPAQUE',
750             'container_id' => $container['id']
751             ));
752         $event = Calendar_Controller_Event::getInstance()->create($event);
753         $this->assertEquals($container['id'], $event->container_id);
754         
755         $fe->deleteContainer($container['id']);
756         
757         $e = new Exception('dummy');
758         
759         $cb = new Calendar_Backend_Sql();
760         $deletedEvent = $cb->get($event->getId(), true);
761         // record should be deleted
762         $this->assertEquals($deletedEvent->is_deleted, 1);
763         
764         try {
765             Calendar_Controller_Event::getInstance()->get($event->getId(), $container['id']);
766             $this->fail('The expected exception was not thrown');
767         } catch (Tinebase_Exception_NotFound $e) {
768             // ok;
769         }
770         // record should not be found
771         $this->assertEquals($e->getMessage(), 'Calendar_Model_Event record with id '.$event->getId().' not found!');
772     }
773     
774     /**
775      * compare expected event data with test event
776      *
777      * @param array $expectedEventData
778      * @param array $eventData
779      * @param string $msg
780      */
781     protected function _assertJsonEvent($expectedEventData, $eventData, $msg)
782     {
783         $this->assertEquals($expectedEventData['summary'], $eventData['summary'], $msg . ': failed to create/load event');
784         
785         // assert effective grants are set
786         $this->assertEquals((bool) $expectedEventData[Tinebase_Model_Grants::GRANT_EDIT], (bool) $eventData[Tinebase_Model_Grants::GRANT_EDIT], $msg . ': effective grants mismatch');
787         // container, assert attendee, tags, relations
788         $this->assertEquals($expectedEventData['dtstart'], $eventData['dtstart'], $msg . ': dtstart mismatch');
789         $this->assertTrue(is_array($eventData['container_id']), $msg . ': failed to "resolve" container');
790         $this->assertTrue(is_array($eventData['container_id']['account_grants']), $msg . ': failed to "resolve" container account_grants');
791         $this->assertGreaterThan(0, count($eventData['attendee']));
792         $this->assertEquals(count($eventData['attendee']), count($expectedEventData['attendee']), $msg . ': failed to append attendee');
793         $this->assertTrue(is_array($eventData['attendee'][0]['user_id']), $msg . ': failed to resolve attendee user_id');
794         // NOTE: due to sorting isshues $eventData['attendee'][0] may be a non resolvable container (due to rights restrictions)
795         $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');
796         $this->assertEquals(count($expectedEventData['tags']), count($eventData['tags']), $msg . ': failed to append tag');
797         $this->assertEquals(count($expectedEventData['notes']), count($eventData['notes']), $msg . ': failed to create note or wrong number of notes');
798         
799         if (array_key_exists('alarms', $expectedEventData)) {
800             $this->assertTrue(array_key_exists('alarms', $eventData), ': failed to create alarms');
801             $this->assertEquals(count($expectedEventData['alarms']), count($eventData['alarms']), $msg . ': failed to create correct number of alarms');
802             if (count($expectedEventData['alarms']) > 0) {
803                 $this->assertTrue(array_key_exists('minutes_before', $eventData['alarms'][0]));
804             }
805         }
806     }
807     
808     /**
809      * find attender 
810      *
811      * @param array $attendeeData
812      * @param string $name
813      * @return array
814      */
815     protected function _findAttender($attendeeData, $name) {
816         $attenderData = false;
817         $searchedId = $this->_personasContacts[$name]->getId();
818         
819         foreach ($attendeeData as $key => $attender) {
820             if ($attender['user_type'] == Calendar_Model_Attender::USERTYPE_USER) {
821                 if (is_array($attender['user_id']) && array_key_exists('id', $attender['user_id'])) {
822                     if ($attender['user_id']['id'] == $searchedId) {
823                         $attenderData = $attendeeData[$key];
824                     }
825                 }
826             }
827         }
828         
829         return $attenderData;
830     }
831     
832     /**
833      * test filter with hidden group -> should return empty result
834      * 
835      * @see 0006934: setting a group that is hidden from adb as attendee filter throws exception
836      */
837     public function testHiddenGroupFilter()
838     {
839         $hiddenGroup = new Tinebase_Model_Group(array(
840             'name'          => 'hiddengroup',
841             'description'   => 'hidden group',
842             'visibility'     => Tinebase_Model_Group::VISIBILITY_HIDDEN
843         ));
844         $hiddenGroup = Admin_Controller_Group::getInstance()->create($hiddenGroup);
845         
846         $filter = array(array(
847             'field'    => 'attender',
848             'operator' => 'equals',
849             'value'    => array(
850                 'user_id'   => $hiddenGroup->list_id,
851                 'user_type' => 'group',
852             ),
853         ));
854         $result = $this->_uit->searchEvents($filter, array());
855         $this->assertEquals(0, $result['totalcount']);
856     }
857     
858     /**
859      * testExdateDeleteAll
860      * 
861      * @see 0007382: allow to edit / delete the whole series / thisandfuture when editing/deleting recur exceptions
862      */
863     public function testExdateDeleteAll()
864     {
865         $events = $this->testCreateRecurException();
866         $exception = $this->_getException($events);
867         $this->_uit->deleteEvents(array($exception['id']), Calendar_Model_Event::RANGE_ALL);
868         
869         $search = $this->_uit->searchEvents($events['filter'], NULL);
870         $this->assertEquals(0, $search['totalcount'], 'all events should be deleted: ' . print_r($search,TRUE));
871     }
872     
873     /**
874      * get exception from event resultset
875      * 
876      * @param array $events
877      * @param integer $index (1 = picks first, 2 = picks second, ...)
878      * @return array|NULL
879      */
880     protected function _getException($events, $index = 1)
881     {
882         $event = NULL;
883         $found = 0;
884         foreach ($events['results'] as $event) {
885             if (! empty($event['recurid'])) {
886                 $found++;
887                 if ($index === $found) {
888                     return $event;
889                 }
890             }
891         }
892         
893         return $event;
894     }
895     
896     /**
897      * testExdateDeleteThis
898      * 
899      * @see 0007382: allow to edit / delete the whole series / thisandfuture when editing/deleting recur exceptions
900      */
901     public function testExdateDeleteThis()
902     {
903         $events = $this->testCreateRecurException();
904         $exception = $this->_getException($events);
905         $this->_uit->deleteEvents(array($exception['id']));
906         
907         $search = $this->_uit->searchEvents($events['filter'], NULL);
908         $this->assertEquals(2, $search['totalcount'], '2 events should remain: ' . print_r($search,TRUE));
909     }
910     
911     /**
912      * testExdateDeleteThisAndFuture
913      * 
914      * @see 0007382: allow to edit / delete the whole series / thisandfuture when editing/deleting recur exceptions
915      */
916     public function testExdateDeleteThisAndFuture()
917     {
918         $events = $this->testCreateRecurException();
919         $exception = $this->_getException($events, 1);
920         $this->_uit->deleteEvents(array($exception['id']), Calendar_Model_Event::RANGE_THISANDFUTURE);
921         
922         $search = $this->_uit->searchEvents($events['filter'], NULL);
923         $this->assertEquals(1, $search['totalcount'], '1 event should remain: ' . print_r($search,TRUE));
924     }
925     
926     /**
927      * assert grant handling
928      */
929     public function testSaveResource($grants = array('readGrant' => true,'editGrant' => true))
930     {
931         $resoureData = array(
932             'name'  => Tinebase_Record_Abstract::generateUID(),
933             'email' => Tinebase_Record_Abstract::generateUID() . '@unittest.com',
934             'grants' => array(array_merge($grants, array(
935                 'account_id' => Tinebase_Core::getUser()->getId(),
936                 'account_type' => 'user'
937             )))
938         );
939         
940         $resoureData = $this->_uit->saveResource($resoureData);
941         $this->assertTrue(is_array($resoureData['grants']), 'grants are not resolved');
942         
943         return $resoureData;
944     }
945     
946     /**
947      * assert only resources with read grant are returned if the user has no manage right
948      */
949     public function testSearchResources()
950     {
951         $readableResoureData = $this->testSaveResource();
952         $nonReadableResoureData = $this->testSaveResource(array());
953         
954         $filer = array(
955             array('field' => 'name', 'operator' => 'in', 'value' => array(
956                 $readableResoureData['name'],
957                 $nonReadableResoureData['name'],
958             ))
959         );
960         
961         $searchResultManager = $this->_uit->searchResources($filer, array());
962         $this->assertEquals(2, count($searchResultManager['results']), 'with manage grants all records should be found');
963         
964         // steal manage right and reactivate container checks
965         Tinebase_Acl_Roles::getInstance()->deleteAllRoles();
966         Calendar_Controller_Resource::getInstance()->doContainerACLChecks(TRUE);
967         
968         $searchResult = $this->_uit->searchResources($filer, array());
969         $this->assertEquals(1, count($searchResult['results']), 'without manage grants only one record should be found');
970     }
971     
972     /**
973      * assert status authkey with editGrant
974      * assert stauts can be set with editGrant
975      * assert stauts can't be set without editGrant
976      */
977     public function testResourceAttendeeGrants()
978     {
979         $editableResoureData = $this->testSaveResource();
980         $nonEditableResoureData = $this->testSaveResource(array('readGrant'));
981         
982         $event = $this->_getEvent(TRUE);
983         $event->attendee = new Tinebase_Record_RecordSet('Calendar_Model_Attender', array(
984             array(
985                 'user_type'  => Calendar_Model_Attender::USERTYPE_RESOURCE,
986                 'user_id'    => $editableResoureData['id'],
987                 'status'     => Calendar_Model_Attender::STATUS_ACCEPTED
988             ),
989             array(
990                 'user_type'  => Calendar_Model_Attender::USERTYPE_RESOURCE,
991                 'user_id'    => $nonEditableResoureData['id'],
992                 'status'     => Calendar_Model_Attender::STATUS_ACCEPTED
993             )
994         ));
995         
996         $persistentEventData = $this->_uit->saveEvent($event->toArray());
997         
998         $attendee = new Tinebase_Record_RecordSet('Calendar_Model_Attender', $persistentEventData['attendee']);
999         $this->assertEquals(1, count($attendee->filter('status', Calendar_Model_Attender::STATUS_ACCEPTED)), 'one accepted');
1000         $this->assertEquals(1, count($attendee->filter('status', Calendar_Model_Attender::STATUS_NEEDSACTION)), 'one needs action');
1001         
1002         $this->assertEquals(1, count($attendee->filter('status_authkey', '/[a-z0-9]+/', TRUE)), 'one has authkey');
1003         
1004         $attendee->status = Calendar_Model_Attender::STATUS_TENTATIVE;
1005         $persistentEventData['attendee'] = $attendee->toArray();
1006         
1007         $updatedEventData = $this->_uit->saveEvent($persistentEventData);
1008         $attendee = new Tinebase_Record_RecordSet('Calendar_Model_Attender', $updatedEventData['attendee']);
1009         $this->assertEquals(1, count($attendee->filter('status', Calendar_Model_Attender::STATUS_TENTATIVE)), 'one tentative');
1010     }
1011
1012     /**
1013      * testExdateUpdateAllSummary
1014      * 
1015      * @see 0007690: allow to update the whole series / thisandfuture when updating recur exceptions
1016      */
1017     public function testExdateUpdateAllSummary()
1018     {
1019         $events = $this->testCreateRecurException();
1020         $exception = $this->_getException($events, 1);
1021         $exception['summary'] = 'new summary';
1022         
1023         $event = $this->_uit->saveEvent($exception, FALSE, Calendar_Model_Event::RANGE_ALL);
1024         
1025         $search = $this->_uit->searchEvents($events['filter'], NULL);
1026         foreach ($search['results'] as $event) {
1027             $this->assertEquals('new summary', $event['summary']);
1028         }
1029     }
1030
1031     /**
1032      * testExdateUpdateAllDtStart
1033      * 
1034      * @see 0007690: allow to update the whole series / thisandfuture when updating recur exceptions
1035      * 
1036      * @todo finish
1037      */
1038     public function testExdateUpdateAllDtStart()
1039     {
1040         $events = $this->testCreateRecurException();
1041         $exception = $this->_getException($events, 1);
1042         $exception['dtstart'] = '2009-04-01 08:00:00';
1043         $exception['dtend'] = '2009-04-01 08:15:00';
1044         
1045         $event = $this->_uit->saveEvent($exception, FALSE, Calendar_Model_Event::RANGE_ALL);
1046         
1047         $search = $this->_uit->searchEvents($events['filter'], NULL);
1048         foreach ($search['results'] as $event) {
1049             $this->assertContains('08:00:00', $event['dtstart'], 'wrong dtstart: ' . print_r($event, TRUE));
1050             $this->assertContains('08:15:00', $event['dtend']);
1051         }
1052     }
1053     
1054     /**
1055      * testExdateUpdateThis
1056      * 
1057      * @see 0007690: allow to update the whole series / thisandfuture when updating recur exceptions
1058      */
1059     public function testExdateUpdateThis()
1060     {
1061         $events = $this->testCreateRecurException();
1062         $exception = $this->_getException($events, 1);
1063         $exception['summary'] = 'exception';
1064         
1065         $event = $this->_uit->saveEvent($exception);
1066         $this->assertEquals('exception', $event['summary']);
1067         
1068         // check for summary (only changed in one event)
1069         $search = $this->_uit->searchEvents($events['filter'], NULL);
1070         foreach ($search['results'] as $event) {
1071             if (! empty($event['recurid']) && ! preg_match('/^fakeid/', $event['id'])) {
1072                 $this->assertEquals('exception', $event['summary'], 'summary not changed in exception: ' . print_r($event, TRUE));
1073             } else {
1074                 $this->assertEquals('Wakeup', $event['summary']);
1075             }
1076         }
1077     }
1078
1079     /**
1080      * testExdateUpdateThisAndFuture
1081      * 
1082      * @see 0007690: allow to update the whole series / thisandfuture when updating recur exceptions
1083      */
1084     public function testExdateUpdateThisAndFuture()
1085     {
1086         $events = $this->testCreateRecurException();
1087         $exception = $this->_getException($events, 1);
1088         $exception['summary'] = 'new summary';
1089         
1090         $updatedEvent = $this->_uit->saveEvent($exception, FALSE, Calendar_Model_Event::RANGE_THISANDFUTURE);
1091         $this->assertEquals('new summary', $updatedEvent['summary'], 'summary not changed in exception: ' . print_r($updatedEvent, TRUE));
1092         
1093         $search = $this->_uit->searchEvents($events['filter'], NULL);
1094         foreach ($search['results'] as $event) {
1095             if ($event['dtstart'] >= $updatedEvent['dtstart']) {
1096                 $this->assertEquals('new summary', $event['summary'], 'summary not changed in event: ' . print_r($event, TRUE));
1097             } else {
1098                 $this->assertEquals('Wakeup', $event['summary']);
1099             }
1100         }
1101     }
1102
1103     /**
1104      * testExdateUpdateThisAndFutureWithRruleUntil
1105      * 
1106      * @see 0008244: "rrule until must not be before dtstart" when updating recur exception (THISANDFUTURE)
1107      */
1108     public function testExdateUpdateThisAndFutureWithRruleUntil()
1109     {
1110         $events = $this->testCreateRecurException();
1111         
1112         $exception = $this->_getException($events, 1);
1113         $exception['dtstart'] = Tinebase_DateTime::now()->toString();
1114         $exception['dtend'] = Tinebase_DateTime::now()->addHour(1)->toString();
1115         
1116         // move exception
1117         $updatedEvent = $this->_uit->saveEvent($exception);
1118         // try to update the whole series
1119         $updatedEvent['summary'] = 'new summary';
1120         $updatedEvent = $this->_uit->saveEvent($updatedEvent, FALSE, Calendar_Model_Event::RANGE_THISANDFUTURE);
1121         
1122         $this->assertEquals('new summary', $updatedEvent['summary'], 'summary not changed in event: ' . print_r($updatedEvent, TRUE));
1123     }
1124     
1125     /**
1126      * testExdateUpdateThisAndFutureRemoveAttendee
1127      * 
1128      * @see 0007690: allow to update the whole series / thisandfuture when updating recur exceptions
1129      */
1130     public function testExdateUpdateThisAndFutureRemoveAttendee()
1131     {
1132         $events = $this->testCreateRecurException();
1133         $exception = $this->_getException($events, 1);
1134         // remove susan from attendee
1135         unset($exception['attendee'][0]);
1136         
1137         $updatedEvent = $this->_uit->saveEvent($exception, FALSE, Calendar_Model_Event::RANGE_THISANDFUTURE);
1138         $this->assertEquals(1, count($updatedEvent['attendee']), 'attender not removed from exception: ' . print_r($updatedEvent, TRUE));
1139         
1140         $search = $this->_uit->searchEvents($events['filter'], NULL);
1141         foreach ($search['results'] as $event) {
1142             if ($event['dtstart'] >= $updatedEvent['dtstart']) {
1143                 $this->assertEquals(1, count($event['attendee']), 'attendee count mismatch: ' . print_r($event, TRUE));
1144             } else {
1145                 $this->assertEquals(2, count($event['attendee']), 'attendee count mismatch: ' . print_r($event, TRUE));
1146             }
1147         }
1148     }
1149
1150     /**
1151      * testExdateUpdateAllAddAttendee
1152      * 
1153      * @see 0007690: allow to update the whole series / thisandfuture when updating recur exceptions
1154      */
1155     public function testExdateUpdateAllAddAttendee()
1156     {
1157         $events = $this->testCreateRecurException();
1158         $exception = $this->_getException($events, 1);
1159         // add new attender
1160         $exception['attendee'][] = $this->_getUserTypeAttender();
1161         
1162         $updatedEvent = $this->_uit->saveEvent($exception, FALSE, Calendar_Model_Event::RANGE_ALL);
1163         $this->assertEquals(3, count($updatedEvent['attendee']), 'attender not added to exception: ' . print_r($updatedEvent, TRUE));
1164         
1165         $search = $this->_uit->searchEvents($events['filter'], NULL);
1166         foreach ($search['results'] as $event) {
1167             $this->assertEquals(3, count($event['attendee']), 'attendee count mismatch: ' . print_r($event, TRUE));
1168         }
1169     }
1170     
1171     /**
1172      * testExdateUpdateThisAndFutureChangeDtstart
1173      * 
1174      * @see 0007690: allow to update the whole series / thisandfuture when updating recur exceptions
1175      */
1176     public function testExdateUpdateThisAndFutureChangeDtstart()
1177     {
1178         $events = $this->testCreateRecurException();
1179         $exception = $this->_getException($events, 1);
1180         $exception['dtstart'] = '2009-04-01 08:00:00';
1181         $exception['dtend'] = '2009-04-01 08:15:00';
1182         
1183         $updatedEvent = $this->_uit->saveEvent($exception, FALSE, Calendar_Model_Event::RANGE_THISANDFUTURE);
1184         
1185         $search = $this->_uit->searchEvents($events['filter'], NULL);
1186         foreach ($search['results'] as $event) {
1187             if ($event['dtstart'] >= $updatedEvent['dtstart']) {
1188                 $this->assertContains('08:00:00', $event['dtstart'], 'wrong dtstart: ' . print_r($event, TRUE));
1189                 $this->assertContains('08:15:00', $event['dtend']);
1190             } else {
1191                 $this->assertContains('06:00:00', $event['dtstart'], 'wrong dtstart: ' . print_r($event, TRUE));
1192                 $this->assertContains('06:15:00', $event['dtend']);
1193             }
1194         }
1195     }
1196     
1197     /**
1198      * testExdateUpdateAllWithModlog
1199      * - change base event, then update all
1200      * 
1201      * @see 0007690: allow to update the whole series / thisandfuture when updating recur exceptions
1202      */
1203     public function testExdateUpdateAllWithModlog()
1204     {
1205         $events = $this->testCreateRecurException();
1206         $baseEvent = $events['results'][0];
1207         $exception = $this->_getException($events, 1);
1208         
1209         $baseEvent['summary'] = 'Get up, lazyboy!';
1210         $baseEvent = $this->_uit->saveEvent($baseEvent);
1211         sleep(1);
1212         
1213         $exception['summary'] = 'new summary';
1214         $updatedEvent = $this->_uit->saveEvent($exception, FALSE, Calendar_Model_Event::RANGE_ALL);
1215         
1216         $search = $this->_uit->searchEvents($events['filter'], NULL);
1217         foreach ($search['results'] as $event) {
1218             if ($event['dtstart'] == $updatedEvent['dtstart']) {
1219                 $this->assertEquals('new summary', $event['summary'], 'Recur exception should have the new summary');
1220             } else {
1221                 $this->assertEquals('Get up, lazyboy!', $event['summary'], 'Wrong summary in base/recur event: ' . print_r($event, TRUE));
1222             }
1223         }
1224     }
1225
1226     /**
1227      * testExdateUpdateAllWithModlogAddAttender
1228      * - change base event, then update all
1229      * 
1230      * @see 0007690: allow to update the whole series / thisandfuture when updating recur exceptions
1231      * @see 0007826: add attendee changes to modlog
1232      */
1233     public function testExdateUpdateAllWithModlogAddAttender()
1234     {
1235         $events = $this->testCreateRecurException();
1236         $baseEvent = $events['results'][0];
1237         $exception = $this->_getException($events, 1);
1238         
1239         // add new attender
1240         $baseEvent['attendee'][] = $this->_getUserTypeAttender();
1241         $baseEvent = $this->_uit->saveEvent($baseEvent);
1242         $this->assertEquals(3, count($baseEvent['attendee']), 'Attendee count mismatch in baseEvent: ' . print_r($baseEvent, TRUE));
1243         sleep(1);
1244         
1245         // check recent changes (needs to contain attendee change)
1246         $exdate = Calendar_Controller_Event::getInstance()->get($exception['id']);
1247         $recentChanges = Tinebase_Timemachine_ModificationLog::getInstance()->getModifications('Calendar', $baseEvent['id'], NULL, 'Sql', $exdate->creation_time);
1248         $this->assertGreaterThan(2, count($recentChanges), 'Did not get all recent changes: ' . print_r($recentChanges->toArray(), TRUE));
1249         $this->assertTrue(in_array('attendee', $recentChanges->modified_attribute), 'Attendee change missing: ' . print_r($recentChanges->toArray(), TRUE));
1250         
1251         $exception['attendee'][] = $this->_getUserTypeAttender('unittestnotexists@example.com');
1252         $updatedEvent = $this->_uit->saveEvent($exception, FALSE, Calendar_Model_Event::RANGE_ALL);
1253         
1254         $search = $this->_uit->searchEvents($events['filter'], NULL);
1255         foreach ($search['results'] as $event) {
1256             if ($event['dtstart'] == $updatedEvent['dtstart']) {
1257                 $this->assertEquals(3, count($event['attendee']), 'Attendee count mismatch in exdate: ' . print_r($event, TRUE));
1258             } else {
1259                 $this->assertEquals(4, count($event['attendee']), 'Attendee count mismatch: ' . print_r($event, TRUE));
1260             }
1261         }
1262     }
1263
1264     /**
1265      * testConcurrentAttendeeChangeAdd
1266      * 
1267      * @see 0008078: concurrent attendee change should be merged
1268      */
1269     public function testConcurrentAttendeeChangeAdd()
1270     {
1271         $eventData = $this->testCreateEvent();
1272         $numAttendee = count($eventData['attendee']);
1273         $eventData['attendee'][$numAttendee] = array(
1274             'user_id' => $this->_personasContacts['pwulf']->getId(),
1275         );
1276         $this->_uit->saveEvent($eventData);
1277         
1278         $eventData['attendee'][$numAttendee] = array(
1279             'user_id' => $this->_personasContacts['jsmith']->getId(),
1280         );
1281         $event = $this->_uit->saveEvent($eventData);
1282         
1283         $this->assertEquals(4, count($event['attendee']), 'both new attendee (pwulf + jsmith) should be added: ' . print_r($event['attendee'], TRUE));
1284     }
1285
1286     /**
1287      * testConcurrentAttendeeChangeRemove
1288      * 
1289      * @see 0008078: concurrent attendee change should be merged
1290      */
1291     public function testConcurrentAttendeeChangeRemove()
1292     {
1293         $eventData = $this->testCreateEvent();
1294         $currentAttendee = $eventData['attendee'];
1295         unset($eventData['attendee'][1]);
1296         $event = $this->_uit->saveEvent($eventData);
1297         
1298         $eventData['attendee'] = $currentAttendee;
1299         $numAttendee = count($eventData['attendee']);
1300         $eventData['attendee'][$numAttendee] = array(
1301             'user_id' => $this->_personasContacts['pwulf']->getId(),
1302         );
1303         $event = $this->_uit->saveEvent($eventData);
1304         
1305         $this->assertEquals(2, count($event['attendee']), 'one attendee should added and one removed: ' . print_r($event['attendee'], TRUE));
1306     }
1307
1308     /**
1309      * testConcurrentAttendeeChangeUpdate
1310      * 
1311      * @see 0008078: concurrent attendee change should be merged
1312      */
1313     public function testConcurrentAttendeeChangeUpdate()
1314     {
1315         $eventData = $this->testCreateEvent();
1316         $currentAttendee = $eventData['attendee'];
1317         $adminIndex = ($eventData['attendee'][0]['user_id']['n_fn'] === 'Susan Clever') ? 1 : 0;
1318         $eventData['attendee'][$adminIndex]['status'] = Calendar_Model_Attender::STATUS_TENTATIVE;
1319         $event = $this->_uit->saveEvent($eventData);
1320         
1321         $loggedMods = Tinebase_Timemachine_ModificationLog::getInstance()->getModificationsBySeq(new Calendar_Model_Attender($eventData['attendee'][$adminIndex]), 1);
1322         $this->assertEquals(1, count($loggedMods), 'attender modification has not been logged');
1323         
1324         $eventData['attendee'] = $currentAttendee;
1325         $scleverIndex = ($adminIndex === 1) ? 0 : 1;
1326         $attendeeBackend = new Calendar_Backend_Sql_Attendee();
1327         $eventData['attendee'][$scleverIndex]['status_authkey'] = $attendeeBackend->get($eventData['attendee'][$scleverIndex]['id'])->status_authkey;
1328         $eventData['attendee'][$scleverIndex]['status'] = Calendar_Model_Attender::STATUS_TENTATIVE;
1329         $event = $this->_uit->saveEvent($eventData);
1330
1331         foreach ($event['attendee'] as $attender) {
1332             $this->assertEquals(Calendar_Model_Attender::STATUS_TENTATIVE, $attender['status'], 'both attendee status should be TENTATIVE: ' . print_r($attender, TRUE));
1333         }
1334     }
1335
1336     /**
1337      * testFreeBusyCheckForExdates
1338      * 
1339      * @see 0008464: freebusy check does not work when creating recur exception
1340      */
1341     public function testFreeBusyCheckForExdates()
1342     {
1343         $events = $this->testCreateRecurException();
1344         $exception = $this->_getException($events, 1);
1345         
1346         $anotherEvent = $this->_getEvent(TRUE);
1347         $anotherEvent = $this->_uit->saveEvent($anotherEvent->toArray());
1348         
1349         $exception['dtstart'] = $anotherEvent['dtstart'];
1350         $exception['dtend'] = $anotherEvent['dtend'];
1351         
1352         try {
1353             $event = $this->_uit->saveEvent($exception, TRUE);
1354             $this->fail('Calendar_Exception_AttendeeBusy expected when saving exception: ' . print_r($exception, TRUE));
1355         } catch (Calendar_Exception_AttendeeBusy $ceab) {
1356             $this->assertEquals('Calendar_Exception_AttendeeBusy', get_class($ceab));
1357         }
1358     }
1359 }