0011548 improve iOS defaultFolder attendee handling
[tine20] / tests / tine20 / Calendar / Controller / RecurTest.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) 2010-2014 Metaways Infosystems GmbH (http://www.metaways.de)
8  * @author      Goekmen Ciyiltepe <g.ciyiltepe@metaways.de>
9  */
10
11 /**
12  * Test class for Calendar_Controller_Event
13  * 
14  * @package     Calendar
15  */
16 class Calendar_Controller_RecurTest extends Calendar_TestCase
17 {
18     /**
19      * @var Calendar_Controller_Event controller
20      */
21     protected $_controller;
22     
23     public function setUp()
24     {
25         parent::setUp();
26         $this->_controller = Calendar_Controller_Event::getInstance();
27     }
28     
29     public function testInvalidRruleUntil()
30     {
31         $event = new Calendar_Model_Event(array(
32             'uid'           => Tinebase_Record_Abstract::generateUID(),
33             'summary'       => 'Abendessen',
34             'dtstart'       => '2012-06-01 18:00:00',
35             'dtend'         => '2012-06-01 18:30:00',
36             'originator_tz' => 'Europe/Berlin',
37             'rrule'         => 'FREQ=DAILY;INTERVAL=1;UNTIL=2011-05-31 17:30:00',
38             'container_id'  => $this->_testCalendar->getId(),
39         ));
40         
41         $this->setExpectedException('Tinebase_Exception_Record_Validation');
42         $persistentEvent = $this->_controller->create($event);
43     }
44     
45     /**
46      * imcomplete rrule clauses should be filled in automatically
47      */
48     public function testIncompleteRrule()
49     {
50         $event = $this->_getRecurEvent();
51         
52         $event->rrule = 'FREQ=WEEKLY';
53         $persistentEvent = $this->_controller->create(clone $event);
54         $this->assertEquals(Calendar_Model_Rrule::getWeekStart(), $persistentEvent->rrule->wkst, 'wkst not normalized');
55         $this->assertEquals('TH', $persistentEvent->rrule->byday, 'byday not normalized');
56         
57         $rrule = Calendar_Model_Rrule::getRruleFromString('FREQ=MONTHLY');
58         $rrule->normalize($event);
59         $this->assertEquals(20, $rrule->bymonthday, 'bymonthday not normalized');
60         
61         $rrule = Calendar_Model_Rrule::getRruleFromString('FREQ=MONTHLY;BYDAY=1TH');
62         $rrule->normalize($event);
63         $this->assertEquals(NULL, $rrule->bymonthday, 'bymonthday must not be added');
64         
65         $rrule = Calendar_Model_Rrule::getRruleFromString('FREQ=YEARLY');
66         $rrule->normalize($event);
67         $this->assertEquals(5, $rrule->bymonth, 'bymonth not normalized');
68         $this->assertEquals(20, $rrule->bymonthday, 'bymonthday not normalized');
69         
70         $rrule = Calendar_Model_Rrule::getRruleFromString('FREQ=YEARLY;BYDAY=1TH');
71         $rrule->normalize($event);
72         $this->assertEquals(5, $rrule->bymonth, 'bymonth not normalized');
73         $this->assertEquals(NULL, $rrule->bymonthday, 'bymonthday must not be added');
74     }
75     
76     public function testFirstInstanceException()
77     {
78         $from = new Tinebase_DateTime('2011-04-18 00:00:00');
79         $until = new Tinebase_DateTime('2011-04-24 23:59:59');
80         
81         $event = new Calendar_Model_Event(array(
82             'uid'           => Tinebase_Record_Abstract::generateUID(),
83             'summary'       => 'Abendessen',
84             'dtstart'       => '2011-04-20 14:00:00',
85             'dtend'         => '2011-04-20 15:30:00',
86             'originator_tz' => 'Europe/Berlin',
87             'rrule'         => 'FREQ=WEEKLY;INTERVAL=3;WKST=SU;BYDAY=TU,TH',
88             'container_id'  => $this->_testCalendar->getId(),
89             Tinebase_Model_Grants::GRANT_EDIT     => true,
90         ));
91         
92         $persistentEvent = $this->_controller->create($event);
93         
94         $eventException = clone $persistentEvent;
95         $eventException->summary = 'Dinner';
96         $eventException->dtstart->subHour(2);
97         $eventException->dtend->subHour(2);
98         $persistentEventException = $this->_controller->createRecurException($eventException);
99         
100         $weekviewEvents = $this->_controller->search(new Calendar_Model_EventFilter(array(
101             array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_testCalendar->getId()),
102         )));
103         
104         Calendar_Model_Rrule::mergeRecurrenceSet($weekviewEvents, $from, $until);
105         $this->assertEquals(2, count($weekviewEvents), 'there should only be 2 events in the set');
106         $this->assertFalse(in_array($persistentEvent->getId(), $weekviewEvents->getId()), 'baseEvent should not be in the set!');
107         
108         return $weekviewEvents;
109     }
110     
111     /**
112      * @see 8618: delete exdate / range this and future fails
113      */
114     public function testFirstInstanceExceptionDeleteRangeThisAndFuture()
115     {
116         $events = $this->testFirstInstanceException();
117         $firstInstanceException = $events->getFirstRecord();
118         
119         $this->_controller->delete($firstInstanceException->getId(), Calendar_Model_Event::RANGE_THISANDFUTURE);
120         
121         $this->setExpectedException('Tinebase_Exception_NotFound');
122         $this->_controller->get($firstInstanceException->getId());
123     }
124     
125     /**
126      * @see 8618: delete exdate / range this and future fails
127      */
128     public function testFirstInstanceExceptionUpdateRangeThisAndFuture()
129     {
130         $events = $this->testFirstInstanceException();
131         $firstInstanceException = $events->getFirstRecord();
132         $location = 'At Home';
133         $firstInstanceException->location = $location;
134     
135         $result = $this->_controller->update($firstInstanceException, FALSE, Calendar_Model_Event::RANGE_THISANDFUTURE);
136         $this->assertEquals($result->location, $location);
137     }
138
139     /**
140      * testFirstInstanceExceptionUpdateRangeAll
141      * 
142      * @see 0008826: update range:all does not work on first occurrence exception
143      */
144     public function testFirstInstanceExceptionUpdateRangeAll()
145     {
146         $events = $this->testFirstInstanceException();
147         $firstInstanceException = $events->getFirstRecord();
148         $location = 'At Home';
149         $firstInstanceException->location = $location;
150     
151         $result = $this->_controller->update($firstInstanceException, FALSE, Calendar_Model_Event::RANGE_ALL);
152         $this->assertEquals($result->location, $location);
153         
154         // @todo check other instances?
155     }
156     
157     /**
158      * @see #5802: moving last event of a recurring set with count part creates a instance a day later
159      */
160     public function testLastInstanceException()
161     {
162         $from = new Tinebase_DateTime('2012-02-20 00:00:00');
163         $until = new Tinebase_DateTime('2012-02-26 23:59:59');
164         
165         $event = new Calendar_Model_Event(array(
166                 'uid'           => Tinebase_Record_Abstract::generateUID(),
167                 'summary'       => 'Abendessen',
168                 'dtstart'       => '2012-02-22 14:00:00',
169                 'dtend'         => '2012-02-22 15:30:00',
170                 'originator_tz' => 'Europe/Berlin',
171                 'rrule'         => 'FREQ=DAILY;COUNT=3',
172                 'container_id'  => $this->_testCalendar->getId(),
173         ));
174         
175         $persistentEvent = $this->_controller->create($event);
176         
177         // create exception
178         $weekviewEvents = $this->_controller->search(new Calendar_Model_EventFilter(array(
179             array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_testCalendar->getId()),
180         )));
181         Calendar_Model_Rrule::mergeRecurrenceSet($weekviewEvents, $from, $until);
182         $weekviewEvents[2]->dtstart->subHour(5);
183         $weekviewEvents[2]->dtend->subHour(5);
184         $this->_controller->createRecurException($weekviewEvents[2]);
185         
186         // load series
187         $weekviewEvents = $this->_controller->search(new Calendar_Model_EventFilter(array(
188             array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_testCalendar->getId()),
189         )));
190         Calendar_Model_Rrule::mergeRecurrenceSet($weekviewEvents, $from, $until);
191         $weekviewEvents->sort('dtstart', 'ASC');
192         
193         $this->assertEquals(3, count($weekviewEvents), 'wrong count');
194         $this->assertEquals('2012-02-24 09:00:00', $weekviewEvents[2]->dtstart->toString());
195     }
196     
197     /**
198      * http://forge.tine20.org/mantisbt/view.php?id=4810
199      */
200     public function testWeeklyException()
201     {
202         $from = new Tinebase_DateTime('2011-09-01 00:00:00');
203         $until = new Tinebase_DateTime('2011-09-30 23:59:59');
204         
205         $event = new Calendar_Model_Event(array(
206             'uid'               => Tinebase_Record_Abstract::generateUID(),
207             'summary'           => 'weekly',
208             'dtstart'           => '2011-09-11 22:00:00',
209             'dtend'             => '2011-09-12 21:59:59',
210             'is_all_day_event'  => true,
211             'originator_tz' => 'Europe/Berlin',
212             'rrule'         => 'FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU,WE,TH',
213             'container_id'  => $this->_testCalendar->getId(),
214             Tinebase_Model_Grants::GRANT_EDIT     => true,
215         ));
216         
217         $persistentEvent = $this->_controller->create($event);
218         
219         $weekviewEvents = $this->_controller->search(new Calendar_Model_EventFilter(array(
220             array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_testCalendar->getId()),
221         )));
222         
223         Calendar_Model_Rrule::mergeRecurrenceSet($weekviewEvents, $from, $until);
224         $this->assertEquals(12, count($weekviewEvents), 'there should be 12 events in the set');
225         
226         // delte one instance
227         $exception = $weekviewEvents->filter('dtstart', new Tinebase_DateTime('2011-09-19 22:00:00'))->getFirstRecord();
228         $persistentEventException = $this->_controller->createRecurException($exception, TRUE);
229         
230         $weekviewEvents = $this->_controller->search(new Calendar_Model_EventFilter(array(
231             array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_testCalendar->getId()),
232         )));
233         
234         Calendar_Model_Rrule::mergeRecurrenceSet($weekviewEvents, $from, $until);
235         $this->assertEquals(11, count($weekviewEvents), 'there should be 11 events in the set');
236         
237         $exception = $weekviewEvents->filter('dtstart', new Tinebase_DateTime('2011-09-19 22:00:00'))->getFirstRecord();
238         $this->assertTrue(!$exception, 'exception must not be in eventset');
239     }
240     
241     public function testAttendeeSetStatusRecurException()
242     {
243         // note: 2009-03-29 Europe/Berlin switched to DST
244         $event = new Calendar_Model_Event(array(
245             'uid'           => Tinebase_Record_Abstract::generateUID(),
246             'summary'       => 'Abendessen',
247             'dtstart'       => '2009-03-25 18:00:00',
248             'dtend'         => '2009-03-25 18:30:00',
249             'originator_tz' => 'Europe/Berlin',
250             'rrule'         => 'FREQ=DAILY;INTERVAL=1;UNTIL=2009-03-31 17:30:00',
251             'exdate'        => '2009-03-27 18:00:00,2009-03-29 17:00:00',
252             'container_id'  => $this->_testCalendar->getId(),
253             Tinebase_Model_Grants::GRANT_EDIT     => true,
254         ));
255         $event->attendee = $this->_getAttendee();
256         unset($event->attendee[1]);
257         
258         $persistentEvent = $this->_controller->create($event);
259         $attendee = $persistentEvent->attendee[0];
260         
261         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
262         $from = new Tinebase_DateTime('2009-03-26 00:00:00');
263         $until = new Tinebase_DateTime('2009-04-01 23:59:59');
264         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($persistentEvent, $exceptions, $from, $until);
265         
266         $exception = $recurSet->getFirstRecord();
267         $attendee = $exception->attendee[0];
268         $attendee->status = Calendar_Model_Attender::STATUS_ACCEPTED;
269         
270         $this->_controller->attenderStatusCreateRecurException($exception, $attendee, $attendee->status_authkey);
271         
272         $events = $this->_controller->search(new Calendar_Model_EventFilter(array(
273             array('field' => 'period', 'operator' => 'within', 'value' => array('from' => $from, 'until' => $until)),
274             array('field' => 'uid', 'operator' => 'equals', 'value' => $persistentEvent->uid)
275         )));
276         
277         $recurid = array_values(array_filter($events->recurid));
278         $this->assertEquals(1, count($recurid), 'only recur instance must have a recurid');
279         $this->assertEquals('2009-03-26 18:00:00', substr($recurid[0], -19));
280         $this->assertEquals(2, count($events));
281     }
282     
283     public function testFirstInstanceAttendeeSetStatusRecurException()
284     {
285         $from = new Tinebase_DateTime('2011-04-18 00:00:00');
286         $until = new Tinebase_DateTime('2011-04-24 23:59:59');
287         
288         $event = new Calendar_Model_Event(array(
289             'uid'           => Tinebase_Record_Abstract::generateUID(),
290             'summary'       => 'Abendessen',
291             'dtstart'       => '2011-04-20 14:00:00',
292             'dtend'         => '2011-04-20 15:30:00',
293             'originator_tz' => 'Europe/Berlin',
294             'rrule'         => 'FREQ=WEEKLY;INTERVAL=3;WKST=SU;BYDAY=TU,TH',
295             'container_id'  => $this->_testCalendar->getId(),
296             Tinebase_Model_Grants::GRANT_EDIT     => true,
297         ));
298         $event->attendee = $this->_getAttendee();
299         unset($event->attendee[1]);
300         
301         $persistentEvent = $this->_controller->create($event);
302         $attendee = $persistentEvent->attendee[0];
303         $attendee->status = Calendar_Model_Attender::STATUS_ACCEPTED;
304         
305         $this->_controller->attenderStatusCreateRecurException(clone $persistentEvent, $attendee, $attendee->status_authkey);
306         
307         $weekviewEvents = $this->_controller->search(new Calendar_Model_EventFilter(array(
308             array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_testCalendar->getId()),
309         )));
310         
311         Calendar_Model_Rrule::mergeRecurrenceSet($weekviewEvents, $from, $until);
312         
313         $this->assertEquals(2, count($weekviewEvents), 'there should only be 2 events in the set');
314         $this->assertFalse(in_array($persistentEvent->getId(), $weekviewEvents->getId()), 'baseEvent should not be in the set!');
315     }
316     
317     /**
318      * Conflict between an existing and recurring event when create the event
319      */
320     public function testCreateConflictBetweenRecurAndExistEvent()
321     {
322         $event = $this->_getEvent();
323         $event->dtstart = '2010-05-20 06:00:00';
324         $event->dtend = '2010-05-20 06:15:00';
325         $event->attendee = new Tinebase_Record_RecordSet('Calendar_Model_Attender', array(
326             array('user_type' => Calendar_Model_Attender::USERTYPE_USER, 'user_id' => $this->_personasContacts['sclever']->getId()),
327             array('user_type' => Calendar_Model_Attender::USERTYPE_USER, 'user_id' => $this->_personasContacts['pwulf']->getId())
328         ));
329         $this->_controller->create($event);
330
331         $event1 = $this->_getRecurEvent();
332         $event1->attendee = new Tinebase_Record_RecordSet('Calendar_Model_Attender', array(
333             array('user_type' => Calendar_Model_Attender::USERTYPE_USER, 'user_id' => $this->_personasContacts['sclever']->getId()),
334             array('user_type' => Calendar_Model_Attender::USERTYPE_USER, 'user_id' => $this->_personasContacts['pwulf']->getId())
335         ));
336         
337         $this->setExpectedException('Calendar_Exception_AttendeeBusy');
338         $this->_controller->create($event1, TRUE);
339     }
340     
341     /**
342      * Conflict between an existing and recurring event when update the event
343      */
344     public function testUpdateConflictBetweenRecurAndExistEvent()
345     {
346         $event = $this->_getEvent();
347         $event->dtstart = '2010-05-20 06:00:00';
348         $event->dtend = '2010-05-20 06:15:00';
349         $event->attendee = new Tinebase_Record_RecordSet('Calendar_Model_Attender', array(
350             array('user_type' => Calendar_Model_Attender::USERTYPE_USER, 'user_id' => $this->_personasContacts['sclever']->getId()),
351             array('user_type' => Calendar_Model_Attender::USERTYPE_USER, 'user_id' => $this->_personasContacts['pwulf']->getId())
352         ));
353         $this->_controller->create($event);
354
355         $event1 = $this->_getRecurEvent();
356         $event1->attendee = new Tinebase_Record_RecordSet('Calendar_Model_Attender', array(
357             array('user_type' => Calendar_Model_Attender::USERTYPE_USER, 'user_id' => $this->_personasContacts['sclever']->getId()),
358             array('user_type' => Calendar_Model_Attender::USERTYPE_USER, 'user_id' => $this->_personasContacts['pwulf']->getId())
359         ));
360         
361         $event1 = $this->_controller->create($event1);
362         $event1->rrule = "FREQ=DAILY;INTERVAL=2";
363         
364         $this->setExpectedException('Calendar_Exception_AttendeeBusy');
365         $this->_controller->update($event1, TRUE);
366     }
367     
368     /**
369      * check that fake clones of dates of persistent exceptions are left out in recur set calculation
370      */
371     public function testRecurSetCalcLeafOutPersistentExceptionDates()
372     {
373         // month 
374         $from = new Tinebase_DateTime('2010-06-01 00:00:00');
375         $until = new Tinebase_DateTime('2010-06-31 23:59:59');
376         
377         $event = $this->_getRecurEvent();
378         $event->rrule = "FREQ=MONTHLY;INTERVAL=1;BYDAY=3TH";
379         $event->attendee = new Tinebase_Record_RecordSet('Calendar_Model_Attender', array(
380             array('user_type' => Calendar_Model_Attender::USERTYPE_USER, 'user_id' => $this->_personasContacts['sclever']->getId()),
381             array('user_type' => Calendar_Model_Attender::USERTYPE_USER, 'user_id' => $this->_personasContacts['pwulf']->getId())
382         ));
383         
384         $persistentRecurEvent = $this->_controller->create($event);
385         
386         // get first recurrance
387         $eventSet = new Tinebase_Record_RecordSet('Calendar_Model_Event', array($persistentRecurEvent));
388         Calendar_Model_Rrule::mergeRecurrenceSet($eventSet, 
389             new Tinebase_DateTime('2010-06-01 00:00:00'),
390             new Tinebase_DateTime('2010-06-31 23:59:59')
391         );
392         $firstRecurrance = $eventSet[1];
393         
394         // create exception of this first occurance: 17.6. -> 24.06.
395         $firstRecurrance->dtstart->add(1, Tinebase_DateTime::MODIFIER_WEEK);
396         $firstRecurrance->dtend->add(1, Tinebase_DateTime::MODIFIER_WEEK);
397         $this->_controller->createRecurException($firstRecurrance);
398         
399         // fetch weekview 14.06 - 20.06.
400         $from = new Tinebase_DateTime('2010-06-14 00:00:00');
401         $until = new Tinebase_DateTime('2010-06-20 23:59:59');
402         $weekviewEvents = $this->_controller->search(new Calendar_Model_EventFilter(array(
403             array('field' => 'uid', 'operator' => 'equals', 'value' => $persistentRecurEvent->uid),
404             array('field' => 'period', 'operator' => 'within', 'value' => array('from' => $from, 'until' => $until),
405         ))));
406         Calendar_Model_Rrule::mergeRecurrenceSet($weekviewEvents, $from, $until);
407         
408         // make sure the 17.6. is not in the set
409         $this->assertEquals(1, count($weekviewEvents),
410                 '17.6. is an exception date and must not be part of this weekview: '
411                 . print_r($weekviewEvents->toArray(), true));
412     }
413     
414     public function testCreateRecurExceptionPreserveAttendeeStatus()
415     {
416         $from = new Tinebase_DateTime('2012-03-01 00:00:00');
417         $until = new Tinebase_DateTime('2012-03-31 23:59:59');
418         
419         $event = new Calendar_Model_Event(array(
420                 'summary'       => 'Some Daily Event',
421                 'dtstart'       => '2012-03-13 09:00:00',
422                 'dtend'         => '2012-03-13 10:00:00',
423                 'rrule'         => 'FREQ=DAILY;INTERVAL=1',
424                 'container_id'  => $this->_testCalendar->getId(),
425                 'attendee'      => $this->_getAttendee(),
426         ));
427         
428         $persistentEvent = $this->_controller->create($event);
429         $persistentSClever = Calendar_Model_Attender::getAttendee($persistentEvent->attendee, $event->attendee[1]);
430         
431         // accept series for sclever
432         $persistentSClever->status = Calendar_Model_Attender::STATUS_ACCEPTED;
433         $this->_controller->attenderStatusUpdate($persistentEvent, $persistentSClever, $persistentSClever->status_authkey);
434         
435         // create recur exception w.o. scheduling change
436         $persistentEvent = $this->_controller->get($persistentEvent->getId());
437         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
438         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($persistentEvent, $exceptions, $from, $until);
439         
440         $recurSet[5]->description = 'From now on, everything will be better'; //2012-03-19
441         $updatedPersistentEvent = $this->_controller->createRecurException($recurSet[5], FALSE, FALSE);
442         
443         $updatedPersistentSClever = Calendar_Model_Attender::getAttendee($updatedPersistentEvent->attendee, $event->attendee[1]);
444         $this->assertEquals(Calendar_Model_Attender::STATUS_ACCEPTED, $updatedPersistentSClever->status, 'status must not change');
445         
446         
447         // create recur exception with scheduling change
448         $updatedBaseEvent = $this->_controller->getRecurBaseEvent($recurSet[6]);
449         $recurSet[6]->last_modified_time = $updatedBaseEvent->last_modified_time;
450         $recurSet[6]->dtstart->addHour(2);
451         $recurSet[6]->dtend->addHour(2);
452         $updatedPersistentEvent = $this->_controller->createRecurException($recurSet[6], FALSE, FALSE);
453         
454         $updatedPersistentSClever = Calendar_Model_Attender::getAttendee($updatedPersistentEvent->attendee, $event->attendee[1]);
455         $this->assertEquals(Calendar_Model_Attender::STATUS_NEEDSACTION, $updatedPersistentSClever->status, 'status must change');
456     }
457     
458     public function testCreateRecurExceptionAllFollowingGeneral()
459     {
460         $from = new Tinebase_DateTime('2011-04-21 00:00:00');
461         $until = new Tinebase_DateTime('2011-04-28 23:59:59');
462         
463         $event = new Calendar_Model_Event(array(
464             'uid'           => Tinebase_Record_Abstract::generateUID(),
465             'summary'       => 'Latte bei Schweinske',
466             'dtstart'       => '2011-04-21 10:00:00',
467             'dtend'         => '2011-04-21 12:00:00',
468             'originator_tz' => 'Europe/Berlin',
469             'rrule'         => 'FREQ=DAILY;INTERVAL=1;UNTIL=2011-04-28 21:59:59',
470             'container_id'  => $this->_testCalendar->getId()
471         ));
472         
473         $persistentEvent = $this->_controller->create($event);
474         
475         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
476         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($persistentEvent, $exceptions, $from, $until);
477         
478         // create exceptions
479         $recurSet->summary = 'Limo bei Schweinske';
480         $recurSet[5]->dtstart->addHour(2);
481         $recurSet[5]->dtend->addHour(2);
482
483         $recurSet[6]->dtstart->subDay(6);
484         $recurSet[6]->dtend->subDay(6);
485         
486         $this->_controller->createRecurException($recurSet[1], TRUE);  // (23) delete instance
487         
488         $updatedBaseEvent = $this->_controller->getRecurBaseEvent($recurSet[2]);
489         $recurSet[2]->last_modified_time = $updatedBaseEvent->last_modified_time;
490         $this->_controller->createRecurException($recurSet[2], FALSE); // (24) move instance
491         
492         $updatedBaseEvent = $this->_controller->getRecurBaseEvent($recurSet[4]);
493         $recurSet[4]->last_modified_time = $updatedBaseEvent->last_modified_time;
494         $this->_controller->createRecurException($recurSet[4], TRUE);  // (26) delete instance
495         
496         $updatedBaseEvent = $this->_controller->getRecurBaseEvent($recurSet[5]);
497         $recurSet[5]->last_modified_time = $updatedBaseEvent->last_modified_time;
498         $this->_controller->createRecurException($recurSet[5], FALSE); // (27) move instance
499
500         $updatedBaseEvent = $this->_controller->getRecurBaseEvent($recurSet[6]);
501         $recurSet[6]->last_modified_time = $updatedBaseEvent->last_modified_time;
502         $this->_controller->createRecurException($recurSet[6], FALSE); // (28) move instance to 22
503         
504         // now test update allfollowing
505         $recurSet[3]->summary = 'Spezi bei Schwinske';
506         $recurSet[3]->dtstart->addHour(4);
507         $recurSet[3]->dtend->addHour(4);
508         
509         $updatedBaseEvent = $this->_controller->getRecurBaseEvent($recurSet[3]);
510         $recurSet[3]->last_modified_time = $updatedBaseEvent->last_modified_time;
511         $newBaseEvent = $this->_controller->createRecurException($recurSet[3], FALSE, TRUE); // split at 25
512         
513         $events = $this->_controller->search(new Calendar_Model_EventFilter(array(
514             array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_testCalendar->getId()),
515             array('field' => 'period', 'operator' => 'within', 'value' => array('from' => $from, 'until' => $until),
516         ))));
517         
518         Calendar_Model_Rrule::mergeRecurrenceSet($events, $from, $until);
519         $this->assertEquals(7, count($events), 'there should be exactly 6 events');
520         
521         $oldSeries = $events->filter('uid', $persistentEvent->uid);
522         $newSeries = $events->filter('uid', $newBaseEvent->uid);
523         $this->assertEquals(3, count($oldSeries), 'there should be exactly 3 events with old uid');
524         $this->assertEquals(4, count($newSeries), 'there should be exactly 3 events with new uid');
525         
526         $this->assertEquals(1, count($oldSeries->filter('recurid', "/^$/", TRUE)), 'there should be exactly one old base event');
527         $this->assertEquals(1, count($newSeries->filter('recurid', "/^$/", TRUE)), 'there should be exactly one new base event');
528         
529         $this->assertEquals(1, count($oldSeries->filter('recurid', "/^.+/", TRUE)->filter('rrule', '/^$/', TRUE)), 'there should be exactly one old persitent event exception');
530         $this->assertEquals(2, count($newSeries->filter('recurid', "/^.+/", TRUE)->filter('rrule', '/^$/', TRUE)), 'there should be exactly one new persitent event exception');
531         
532         $this->assertEquals(1, count($oldSeries->filter('id', "/^fake.*/", TRUE)), 'there should be exactly one old fake event');
533         $this->assertEquals(1, count($newSeries->filter('id', "/^fake.*/", TRUE)), 'there should be exactly one new fake event'); //26 (reset)
534         
535         $oldBaseEvent = $oldSeries->filter('recurid', "/^$/", TRUE)->getFirstRecord();
536         $newBaseEvent = $newSeries->filter('recurid', "/^$/", TRUE)->getFirstRecord();
537         
538         $this->assertFalse(!!array_diff($oldBaseEvent->exdate, array(
539             new Tinebase_DateTime('2011-04-23 10:00:00'),
540             new Tinebase_DateTime('2011-04-24 10:00:00'),
541         )), 'exdate of old series');
542         
543         $this->assertFalse(!!array_diff($newBaseEvent->exdate, array(
544             new Tinebase_DateTime('2011-04-27 14:00:00'),
545             new Tinebase_DateTime('2011-04-28 14:00:00'),
546         )), 'exdate of new series');
547         
548         $this->assertFalse(!!array_diff($oldSeries->dtstart, array(
549             new Tinebase_DateTime('2011-04-21 10:00:00'),
550             new Tinebase_DateTime('2011-04-22 10:00:00'),
551             new Tinebase_DateTime('2011-04-24 10:00:00'),
552         )), 'dtstart of old series');
553
554         $this->assertFalse(!!array_diff($newSeries->dtstart, array(
555             new Tinebase_DateTime('2011-04-25 14:00:00'),
556             new Tinebase_DateTime('2011-04-26 14:00:00'),
557             new Tinebase_DateTime('2011-04-27 12:00:00'),
558             new Tinebase_DateTime('2011-04-22 10:00:00'),
559         )), 'dtstart of new series');
560     }
561
562     public function testCreateRecurExceptionAllFollowingAllDay()
563     {
564         $from = new Tinebase_DateTime('2015-07-01 00:00:00');
565         $until = new Tinebase_DateTime('2015-09-29 23:59:59');
566
567         $event = new Calendar_Model_Event(array(
568             'summary'           => 'Mettwoch',
569             'dtstart'           => '2015-02-10 23:00:00',
570             'dtend'             => '2015-02-11 22:59:59',
571             'is_all_day_event'  => 1,
572             'description'       => '2 Pfund Mett. 15 Brotchen. 1ne Zwiebel',
573             'rrule'             => 'FREQ=MONTHLY;INTERVAL=1;BYDAY=2WE',
574             'container_id'      => method_exists($this, '_getTestCalendar') ?
575                 $this->_getTestCalendar()->getId() :
576                 $this->_testCalendar->getId(),
577             'attendee'          => $this->_getAttendee(),
578         ));
579
580         $persistentEvent = $this->_controller->create($event);
581
582         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
583         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($persistentEvent, $exceptions, $from, $until);
584
585         $recurSet[1]->description = '4 Pfund Mett. 15 Brotchen. 2 Zwiebeln';
586         $newBaseEvent = $this->_controller->createRecurException($recurSet[1], FALSE, TRUE);
587
588         $oldBaseEvent = $this->_controller->get($persistentEvent->getId());
589         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
590         $oldRecurSet = Calendar_Model_Rrule::computeRecurrenceSet($oldBaseEvent, $exceptions, $from, $until);
591
592         $this->assertCount(1, $oldRecurSet, 'one event should be left in given period ');
593         $this->assertEquals('2015-07-07 22:00:00', $oldRecurSet[0]->dtstart);
594     }
595
596     /**
597      * if not resheduled, attendee status must be preserved
598      */
599     public function testCreateRecurExceptionAllFollowingPreserveAttendeeStatus()
600     {
601         $from = new Tinebase_DateTime('2012-02-01 00:00:00');
602         $until = new Tinebase_DateTime('2012-02-29 23:59:59');
603         
604         $event = new Calendar_Model_Event(array(
605             'summary'       => 'Some Daily Event',
606             'dtstart'       => '2012-02-03 09:00:00',
607             'dtend'         => '2012-02-03 10:00:00',
608             'rrule'         => 'FREQ=DAILY;INTERVAL=1',
609             'container_id'  => $this->_testCalendar->getId(),
610             'attendee'      => $this->_getAttendee(),
611         ));
612         
613         $persistentEvent = $this->_controller->create($event);
614         $persistentSClever = Calendar_Model_Attender::getAttendee($persistentEvent->attendee, $event->attendee[1]);
615         
616         // accept series for sclever
617         $persistentSClever->status = Calendar_Model_Attender::STATUS_ACCEPTED;
618         $this->_controller->attenderStatusUpdate($persistentEvent, $persistentSClever, $persistentSClever->status_authkey);
619         
620         // update "allfollowing" w.o. scheduling change
621         $persistentEvent = $this->_controller->get($persistentEvent->getId());
622         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
623         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($persistentEvent, $exceptions, $from, $until);
624         
625         $recurSet[5]->description = 'From now on, everything will be better'; //2012-02-09 
626         $updatedPersistentEvent = $this->_controller->createRecurException($recurSet[5], FALSE, TRUE);
627         
628         $updatedPersistentSClever = Calendar_Model_Attender::getAttendee($updatedPersistentEvent->attendee, $event->attendee[1]);
629         $this->assertEquals(Calendar_Model_Attender::STATUS_ACCEPTED, $updatedPersistentSClever->status, 'status must not change');
630     }
631     
632     /**
633      * @see https://forge.tine20.org/mantisbt/view.php?id=6548
634      */
635     public function testCreateRecurExceptionsConcurrently()
636     {
637         $from = new Tinebase_DateTime('2012-06-01 00:00:00');
638         $until = new Tinebase_DateTime('2012-06-30 23:59:59');
639         
640         $event = new Calendar_Model_Event(array(
641             'uid'           => Tinebase_Record_Abstract::generateUID(),
642             'summary'       => 'Concurrent Recur updates',
643             'dtstart'       => '2012-06-01 10:00:00',
644             'dtend'         => '2012-06-01 12:00:00',
645             'originator_tz' => 'Europe/Berlin',
646             'rrule'         => 'FREQ=WEEKLY;INTERVAL=1',
647             'container_id'  => $this->_testCalendar->getId()
648         ));
649         
650         $persistentEvent = $this->_controller->create($event);
651         
652         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
653         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($persistentEvent, $exceptions, $from, $until);
654         
655         // create all following exception with first session
656         $firstSessionExdate = clone $recurSet[1];
657         $firstSessionExdate->summary = 'all following update';
658         $this->_controller->createRecurException($firstSessionExdate, FALSE, TRUE);
659         
660         // try to update exception concurrently
661         $this->setExpectedException('Tinebase_Timemachine_Exception_ConcurrencyConflict');
662         $secondSessionExdate = clone $recurSet[1];
663         $secondSessionExdate->summary = 'just an update';
664         $this->_controller->createRecurException($secondSessionExdate, FALSE, TRUE);
665     }
666     
667     /**
668      * test implicit recur (exception) series creation for attendee status only
669      */
670     public function testAttendeeSetStatusRecurExceptionAllFollowing()
671     {
672         $from = new Tinebase_DateTime('2012-02-01 00:00:00');
673         $until = new Tinebase_DateTime('2012-02-29 23:59:59');
674         
675         $event = new Calendar_Model_Event(array(
676             'summary'       => 'Some Daily Event',
677             'dtstart'       => '2012-02-03 09:00:00',
678             'dtend'         => '2012-02-03 10:00:00',
679             'rrule'         => 'FREQ=DAILY;INTERVAL=1',
680             'container_id'  => $this->_testCalendar->getId(),
681             'attendee'      => $this->_getAttendee(),
682         ));
683         
684         $persistentEvent = $this->_controller->create($event);
685         
686         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
687         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($persistentEvent, $exceptions, $from, $until);
688         
689         // accept for sclever thisandfuture
690         $start = $recurSet[10];
691         $sclever = Calendar_Model_Attender::getAttendee($start->attendee, $event->attendee[1]);
692         $sclever->status = Calendar_Model_Attender::STATUS_ACCEPTED;
693         $this->_controller->attenderStatusCreateRecurException($start, $sclever, $sclever->status_authkey, TRUE);
694         
695         $events = $this->_controller->search(new Calendar_Model_EventFilter(array(
696             array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_testCalendar->getId())
697         )))->sort('dtstart', 'ASC');
698         
699         // assert two baseEvents
700         $this->assertTrue($events[0]->rrule_until instanceof Tinebase_DateTime, 'rrule_until of first baseEvent is not set');
701         $this->assertTrue($events[0]->rrule_until < new Tinebase_DateTime('2012-02-14 09:00:00'), 'rrule_until of first baseEvent is not adopted properly');
702         $this->assertEquals(Calendar_Model_Attender::STATUS_NEEDSACTION, Calendar_Model_Attender::getAttendee($events[0]->attendee, $event->attendee[1])->status, 'first baseEvent status must not be touched');
703         
704         $this->assertEquals($events[1]->dtstart, new Tinebase_DateTime('2012-02-14 09:00:00'), 'start of second baseEvent is wrong');
705         $this->assertTrue(empty($events[1]->recurid), 'second baseEvent is not a baseEvent');
706         $this->assertEquals((string) $event->rrule, (string) $events[1]->rrule, 'rrule of second baseEvent must be set');
707         $this->assertFalse($events[1]->rrule_until instanceof Tinebase_DateTime, 'rrule_until of second baseEvent must not be set');
708         $this->assertEquals(Calendar_Model_Attender::STATUS_ACCEPTED, Calendar_Model_Attender::getAttendee($events[1]->attendee, $event->attendee[1])->status, 'second baseEvent status is not touched');
709     }
710     
711    /**
712     * @see {http://forge.tine20.org/mantisbt/view.php?id=5686}
713     */
714     public function testCreateRecurExceptionAllFollowingAttendeeAdd()
715     {
716         $from = new Tinebase_DateTime('2012-02-01 00:00:00');
717         $until = new Tinebase_DateTime('2012-02-29 23:59:59');
718         
719         $persistentEvent = $this->_getDailyEvent(new Tinebase_DateTime('2012-02-03 09:00:00'));
720         
721         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
722         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($persistentEvent, $exceptions, $from, $until);
723         
724         $recurSet[5]->attendee->addRecord(new Calendar_Model_Attender(array(
725             'user_type'   => Calendar_Model_Attender::USERTYPE_USER,
726             'user_id'     => $this->_personasContacts['pwulf']->getId()
727         )));
728         
729         $updatedPersistentEvent = $this->_controller->createRecurException($recurSet[5], FALSE, TRUE);
730         
731         $this->assertEquals(3, count($updatedPersistentEvent->attendee));
732     }
733     
734     /**
735      * create daily recur series
736      * 
737      * @param Tinebase_DateTime $dtstart
738      * @return Calendar_Model_Event
739      */
740     protected function _getDailyEvent(Tinebase_DateTime $dtstart)
741     {
742         $event = new Calendar_Model_Event(array(
743             'summary'       => 'Some Daily Event',
744             'dtstart'       => $dtstart->toString(),
745             'dtend'         => $dtstart->addHour(1)->toString(),
746             'rrule'         => 'FREQ=DAILY;INTERVAL=1',
747             'container_id'  => $this->_testCalendar->getId(),
748             'attendee'      => $this->_getAttendee(),
749         ));
750         return $this->_controller->create($event);
751     }
752     
753    /**
754     * @see #5806: thisandfuture range updates with count part fail
755     */
756     public function testCreateRecurExceptionAllFollowingWithCount()
757     {
758         $from = new Tinebase_DateTime('2012-02-20 00:00:00');
759         $until = new Tinebase_DateTime('2012-02-26 23:59:59');
760         
761         $event = new Calendar_Model_Event(array(
762             'uid'           => Tinebase_Record_Abstract::generateUID(),
763             'summary'       => 'Abendessen',
764             'dtstart'       => '2012-02-21 14:00:00',
765             'dtend'         => '2012-02-21 15:30:00',
766             'originator_tz' => 'Europe/Berlin',
767             'rrule'         => 'FREQ=DAILY;COUNT=5',
768             'container_id'  => $this->_testCalendar->getId(),
769         ));
770         
771         $persistentEvent = $this->_controller->create($event);
772         
773         // create exception
774         $weekviewEvents = $this->_controller->search(new Calendar_Model_EventFilter(array(
775             array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_testCalendar->getId()),
776         )));
777         Calendar_Model_Rrule::mergeRecurrenceSet($weekviewEvents, $from, $until);
778         $weekviewEvents[2]->dtstart->subHour(5);
779         $weekviewEvents[2]->dtend->subHour(5);
780         $this->_controller->createRecurException($weekviewEvents[2], FALSE, TRUE);
781         
782         // load events
783         $weekviewEvents = $this->_controller->search(new Calendar_Model_EventFilter(array(
784             array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_testCalendar->getId()),
785         )));
786         Calendar_Model_Rrule::mergeRecurrenceSet($weekviewEvents, $from, $until);
787         $weekviewEvents->sort('dtstart', 'ASC');
788         
789         $this->assertEquals(2, count($weekviewEvents->filter('uid', $weekviewEvents[0]->uid)), 'shorten failed');
790         $this->assertEquals(5, count($weekviewEvents), 'wrong total count');
791     }
792     
793     /**
794      * testMoveRecurException
795      * 
796      * @see 0008704: moving a recur exception twice creates concurrency exception
797      * - this has been a client problem, server did everything right
798      */
799     public function testMoveRecurException()
800     {
801         $from = new Tinebase_DateTime('2012-02-01 00:00:00');
802         $until = new Tinebase_DateTime('2012-02-29 23:59:59');
803         
804         $persistentEvent1 = $this->_getDailyEvent(new Tinebase_DateTime('2012-02-03 09:00:00'));
805         $persistentEvent2 = $this->_getDailyEvent(new Tinebase_DateTime('2012-02-03 13:00:00'));
806         
807         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
808         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($persistentEvent1, $exceptions, $from, $until);
809         
810         $recurSet[5]->dtstart = new Tinebase_DateTime('2012-02-09 13:00:00');
811         $recurSet[5]->dtend = new Tinebase_DateTime('2012-02-09 14:00:00');
812         
813         $updatedPersistentEvent = $this->_controller->createRecurException($recurSet[5]);
814         
815         $updatedPersistentEvent->dtstart = new Tinebase_DateTime('2012-03-09 13:00:00');
816         $updatedPersistentEvent->dtend = new Tinebase_DateTime('2012-03-09 14:00:00');
817         
818         $this->setExpectedException('Tinebase_Timemachine_Exception_ConcurrencyConflict');
819         $updatedPersistentEvent = $this->_controller->createRecurException($updatedPersistentEvent);
820     }
821     
822     /**
823      * returns a simple recure event
824      *
825      * @return Calendar_Model_Event
826      */
827     protected function _getRecurEvent()
828     {
829         return new Calendar_Model_Event(array(
830             'summary'     => 'Breakfast',
831             'dtstart'     => '2010-05-20 06:00:00',
832             'dtend'       => '2010-05-20 06:15:00',
833             'description' => 'Breakfast',
834             'rrule'       => 'FREQ=DAILY;INTERVAL=1',    
835             'container_id' => $this->_testCalendar->getId(),
836             Tinebase_Model_Grants::GRANT_EDIT    => true,
837         ));
838     }
839 }