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