Merge branch 'pu/2013.10-icsimport'
[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->_getTestCalendar()->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->_getTestCalendar()->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->_getTestCalendar()->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->_getTestCalendar()->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->_getTestCalendar()->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->_getTestCalendar()->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->_getTestCalendar()->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->_getTestCalendar()->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->_getTestCalendar()->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->_getTestCalendar()->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->_getTestCalendar()->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->_getTestCalendar()->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->_getPersonasContacts('sclever')->getId()),
327             array('user_type' => Calendar_Model_Attender::USERTYPE_USER, 'user_id' => $this->_getPersonasContacts('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->_getPersonasContacts('sclever')->getId()),
334             array('user_type' => Calendar_Model_Attender::USERTYPE_USER, 'user_id' => $this->_getPersonasContacts('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->_getPersonasContacts('sclever')->getId()),
351             array('user_type' => Calendar_Model_Attender::USERTYPE_USER, 'user_id' => $this->_getPersonasContacts('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->_getPersonasContacts('sclever')->getId()),
358             array('user_type' => Calendar_Model_Attender::USERTYPE_USER, 'user_id' => $this->_getPersonasContacts('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->_getPersonasContacts('sclever')->getId()),
381             array('user_type' => Calendar_Model_Attender::USERTYPE_USER, 'user_id' => $this->_getPersonasContacts('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(0, 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->_getTestCalendar()->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-27 21:59:59',
470             'container_id'  => $this->_getTestCalendar()->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         $this->_controller->createRecurException($recurSet[1], TRUE);  // (23) delete instance
484         
485         $updatedBaseEvent = $this->_controller->getRecurBaseEvent($recurSet[2]);
486         $recurSet[2]->last_modified_time = $updatedBaseEvent->last_modified_time;
487         $this->_controller->createRecurException($recurSet[2], FALSE); // (24) move instance
488         
489         $updatedBaseEvent = $this->_controller->getRecurBaseEvent($recurSet[4]);
490         $recurSet[4]->last_modified_time = $updatedBaseEvent->last_modified_time;
491         $this->_controller->createRecurException($recurSet[4], TRUE);  // (26) delete instance
492         
493         $updatedBaseEvent = $this->_controller->getRecurBaseEvent($recurSet[5]);
494         $recurSet[5]->last_modified_time = $updatedBaseEvent->last_modified_time;
495         $this->_controller->createRecurException($recurSet[5], FALSE); // (27) move instance
496         
497         // now test update allfollowing
498         $recurSet[3]->summary = 'Spezi bei Schwinske';
499         $recurSet[3]->dtstart->addHour(4);
500         $recurSet[3]->dtend->addHour(4);
501         
502         $updatedBaseEvent = $this->_controller->getRecurBaseEvent($recurSet[3]);
503         $recurSet[3]->last_modified_time = $updatedBaseEvent->last_modified_time;
504         $newBaseEvent = $this->_controller->createRecurException($recurSet[3], FALSE, TRUE);
505         
506         $events = $this->_controller->search(new Calendar_Model_EventFilter(array(
507             array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_getTestCalendar()->getId()),
508             array('field' => 'period', 'operator' => 'within', 'value' => array('from' => $from, 'until' => $until),
509         ))));
510         
511         Calendar_Model_Rrule::mergeRecurrenceSet($events, $from, $until);
512         $this->assertEquals(6, count($events), 'there should be exactly 6 events');
513         
514         $oldSeries = $events->filter('uid', $persistentEvent->uid);
515         $newSeries = $events->filter('uid', $newBaseEvent->uid);
516         $this->assertEquals(3, count($oldSeries), 'there should be exactly 3 events with old uid');
517         $this->assertEquals(3, count($newSeries), 'there should be exactly 3 events with new uid');
518         
519         $this->assertEquals(1, count($oldSeries->filter('recurid', "/^$/", TRUE)), 'there should be exactly one old base event');
520         $this->assertEquals(1, count($newSeries->filter('recurid', "/^$/", TRUE)), 'there should be exactly one new base event');
521         
522         $this->assertEquals(1, count($oldSeries->filter('recurid', "/^.+/", TRUE)->filter('rrule', '/^$/', TRUE)), 'there should be exactly one old persitent event exception');
523         $this->assertEquals(1, count($newSeries->filter('recurid', "/^.+/", TRUE)->filter('rrule', '/^$/', TRUE)), 'there should be exactly one new persitent event exception');
524         
525         $this->assertEquals(1, count($oldSeries->filter('id', "/^fake.*/", TRUE)), 'there should be exactly one old fake event');
526         $this->assertEquals(1, count($newSeries->filter('id', "/^fake.*/", TRUE)), 'there should be exactly one new fake event'); //26 (reset)
527         
528         $oldBaseEvent = $oldSeries->filter('recurid', "/^$/", TRUE)->getFirstRecord();
529         $newBaseEvent = $newSeries->filter('recurid', "/^$/", TRUE)->getFirstRecord();
530         
531         $this->assertFalse(!!array_diff($oldBaseEvent->exdate, array(
532             new Tinebase_DateTime('2011-04-23 10:00:00'),
533             new Tinebase_DateTime('2011-04-24 10:00:00'),
534         )), 'exdate of old series');
535         
536         $this->assertFalse(!!array_diff($newBaseEvent->exdate, array(
537             new Tinebase_DateTime('2011-04-27 14:00:00'),
538         )), 'exdate of new series');
539         
540         $this->assertFalse(!!array_diff($oldSeries->dtstart, array(
541             new Tinebase_DateTime('2011-04-21 10:00:00'),
542             new Tinebase_DateTime('2011-04-22 10:00:00'),
543             new Tinebase_DateTime('2011-04-24 10:00:00'),
544         )), 'dtstart of old series');
545         
546         $this->assertFalse(!!array_diff($newSeries->dtstart, array(
547             new Tinebase_DateTime('2011-04-25 14:00:00'),
548             new Tinebase_DateTime('2011-04-26 14:00:00'),
549             new Tinebase_DateTime('2011-04-27 12:00:00'),
550         )), 'dtstart of new series');
551     }
552     
553     /**
554      * if not resheduled, attendee status must be preserved
555      */
556     public function testCreateRecurExceptionAllFollowingPreserveAttendeeStatus()
557     {
558         $from = new Tinebase_DateTime('2012-02-01 00:00:00');
559         $until = new Tinebase_DateTime('2012-02-29 23:59:59');
560         
561         $event = new Calendar_Model_Event(array(
562             'summary'       => 'Some Daily Event',
563             'dtstart'       => '2012-02-03 09:00:00',
564             'dtend'         => '2012-02-03 10:00:00',
565             'rrule'         => 'FREQ=DAILY;INTERVAL=1',
566             'container_id'  => $this->_getTestCalendar()->getId(),
567             'attendee'      => $this->_getAttendee(),
568         ));
569         
570         $persistentEvent = $this->_controller->create($event);
571         $persistentSClever = Calendar_Model_Attender::getAttendee($persistentEvent->attendee, $event->attendee[1]);
572         
573         // accept series for sclever
574         $persistentSClever->status = Calendar_Model_Attender::STATUS_ACCEPTED;
575         $this->_controller->attenderStatusUpdate($persistentEvent, $persistentSClever, $persistentSClever->status_authkey);
576         
577         // update "allfollowing" w.o. scheduling change
578         $persistentEvent = $this->_controller->get($persistentEvent->getId());
579         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
580         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($persistentEvent, $exceptions, $from, $until);
581         
582         $recurSet[5]->description = 'From now on, everything will be better'; //2012-02-09 
583         $updatedPersistentEvent = $this->_controller->createRecurException($recurSet[5], FALSE, TRUE);
584         
585         $updatedPersistentSClever = Calendar_Model_Attender::getAttendee($updatedPersistentEvent->attendee, $event->attendee[1]);
586         $this->assertEquals(Calendar_Model_Attender::STATUS_ACCEPTED, $updatedPersistentSClever->status, 'status must not change');
587     }
588     
589     /**
590      * @see https://forge.tine20.org/mantisbt/view.php?id=6548
591      */
592     public function testCreateRecurExceptionsConcurrently()
593     {
594         $from = new Tinebase_DateTime('2012-06-01 00:00:00');
595         $until = new Tinebase_DateTime('2012-06-30 23:59:59');
596         
597         $event = new Calendar_Model_Event(array(
598             'uid'           => Tinebase_Record_Abstract::generateUID(),
599             'summary'       => 'Concurrent Recur updates',
600             'dtstart'       => '2012-06-01 10:00:00',
601             'dtend'         => '2012-06-01 12:00:00',
602             'originator_tz' => 'Europe/Berlin',
603             'rrule'         => 'FREQ=WEEKLY;INTERVAL=1',
604             'container_id'  => $this->_getTestCalendar()->getId()
605         ));
606         
607         $persistentEvent = $this->_controller->create($event);
608         
609         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
610         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($persistentEvent, $exceptions, $from, $until);
611         
612         // create all following exception with first session
613         $firstSessionExdate = clone $recurSet[1];
614         $firstSessionExdate->summary = 'all following update';
615         $this->_controller->createRecurException($firstSessionExdate, FALSE, TRUE);
616         
617         // try to update exception concurrently
618         $this->setExpectedException('Tinebase_Timemachine_Exception_ConcurrencyConflict');
619         $secondSessionExdate = clone $recurSet[1];
620         $secondSessionExdate->summary = 'just an update';
621         $this->_controller->createRecurException($secondSessionExdate, FALSE, TRUE);
622     }
623     
624     /**
625      * test implicit recur (exception) series creation for attendee status only
626      */
627     public function testAttendeeSetStatusRecurExceptionAllFollowing()
628     {
629         $from = new Tinebase_DateTime('2012-02-01 00:00:00');
630         $until = new Tinebase_DateTime('2012-02-29 23:59:59');
631         
632         $event = new Calendar_Model_Event(array(
633             'summary'       => 'Some Daily Event',
634             'dtstart'       => '2012-02-03 09:00:00',
635             'dtend'         => '2012-02-03 10:00:00',
636             'rrule'         => 'FREQ=DAILY;INTERVAL=1',
637             'container_id'  => $this->_getTestCalendar()->getId(),
638             'attendee'      => $this->_getAttendee(),
639         ));
640         
641         $persistentEvent = $this->_controller->create($event);
642         
643         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
644         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($persistentEvent, $exceptions, $from, $until);
645         
646         // accept for sclever thisandfuture
647         $start = $recurSet[10];
648         $sclever = Calendar_Model_Attender::getAttendee($start->attendee, $event->attendee[1]);
649         $sclever->status = Calendar_Model_Attender::STATUS_ACCEPTED;
650         $this->_controller->attenderStatusCreateRecurException($start, $sclever, $sclever->status_authkey, TRUE);
651         
652         $events = $this->_controller->search(new Calendar_Model_EventFilter(array(
653             array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_getTestCalendar()->getId())
654         )))->sort('dtstart', 'ASC');
655         
656         // assert two baseEvents
657         $this->assertTrue($events[0]->rrule_until instanceof Tinebase_DateTime, 'rrule_until of first baseEvent is not set');
658         $this->assertTrue($events[0]->rrule_until < new Tinebase_DateTime('2012-02-14 09:00:00'), 'rrule_until of first baseEvent is not adopted properly');
659         $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');
660         
661         $this->assertEquals($events[1]->dtstart, new Tinebase_DateTime('2012-02-14 09:00:00'), 'start of second baseEvent is wrong');
662         $this->assertTrue(empty($events[1]->recurid), 'second baseEvent is not a baseEvent');
663         $this->assertEquals((string) $event->rrule, (string) $events[1]->rrule, 'rrule of second baseEvent must be set');
664         $this->assertFalse($events[1]->rrule_until instanceof Tinebase_DateTime, 'rrule_until of second baseEvent must not be set');
665         $this->assertEquals(Calendar_Model_Attender::STATUS_ACCEPTED, Calendar_Model_Attender::getAttendee($events[1]->attendee, $event->attendee[1])->status, 'second baseEvent status is not touched');
666     }
667     
668    /**
669     * @see {http://forge.tine20.org/mantisbt/view.php?id=5686}
670     */
671     public function testCreateRecurExceptionAllFollowingAttendeeAdd()
672     {
673         $from = new Tinebase_DateTime('2012-02-01 00:00:00');
674         $until = new Tinebase_DateTime('2012-02-29 23:59:59');
675         
676         $persistentEvent = $this->_getDailyEvent(new Tinebase_DateTime('2012-02-03 09:00:00'));
677         
678         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
679         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($persistentEvent, $exceptions, $from, $until);
680         
681         $recurSet[5]->attendee->addRecord(new Calendar_Model_Attender(array(
682             'user_type'   => Calendar_Model_Attender::USERTYPE_USER,
683             'user_id'     => $this->_getPersonasContacts('pwulf')->getId()
684         )));
685         
686         $updatedPersistentEvent = $this->_controller->createRecurException($recurSet[5], FALSE, TRUE);
687         
688         $this->assertEquals(3, count($updatedPersistentEvent->attendee));
689     }
690     
691     /**
692      * create daily recur series
693      * 
694      * @param Tinebase_DateTime $dtstart
695      * @return Calendar_Model_Event
696      */
697     protected function _getDailyEvent(Tinebase_DateTime $dtstart)
698     {
699         $event = new Calendar_Model_Event(array(
700             'summary'       => 'Some Daily Event',
701             'dtstart'       => $dtstart->toString(),
702             'dtend'         => $dtstart->addHour(1)->toString(),
703             'rrule'         => 'FREQ=DAILY;INTERVAL=1',
704             'container_id'  => $this->_getTestCalendar()->getId(),
705             'attendee'      => $this->_getAttendee(),
706         ));
707         return $this->_controller->create($event);
708     }
709     
710    /**
711     * @see #5806: thisandfuture range updates with count part fail
712     */
713     public function testCreateRecurExceptionAllFollowingWithCount()
714     {
715         $from = new Tinebase_DateTime('2012-02-20 00:00:00');
716         $until = new Tinebase_DateTime('2012-02-26 23:59:59');
717         
718         $event = new Calendar_Model_Event(array(
719             'uid'           => Tinebase_Record_Abstract::generateUID(),
720             'summary'       => 'Abendessen',
721             'dtstart'       => '2012-02-21 14:00:00',
722             'dtend'         => '2012-02-21 15:30:00',
723             'originator_tz' => 'Europe/Berlin',
724             'rrule'         => 'FREQ=DAILY;COUNT=5',
725             'container_id'  => $this->_getTestCalendar()->getId(),
726         ));
727         
728         $persistentEvent = $this->_controller->create($event);
729         
730         // create exception
731         $weekviewEvents = $this->_controller->search(new Calendar_Model_EventFilter(array(
732             array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_getTestCalendar()->getId()),
733         )));
734         Calendar_Model_Rrule::mergeRecurrenceSet($weekviewEvents, $from, $until);
735         $weekviewEvents[2]->dtstart->subHour(5);
736         $weekviewEvents[2]->dtend->subHour(5);
737         $this->_controller->createRecurException($weekviewEvents[2], FALSE, TRUE);
738         
739         // load events
740         $weekviewEvents = $this->_controller->search(new Calendar_Model_EventFilter(array(
741             array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_getTestCalendar()->getId()),
742         )));
743         Calendar_Model_Rrule::mergeRecurrenceSet($weekviewEvents, $from, $until);
744         $weekviewEvents->sort('dtstart', 'ASC');
745         
746         $this->assertEquals(2, count($weekviewEvents->filter('uid', $weekviewEvents[0]->uid)), 'shorten failed');
747         $this->assertEquals(5, count($weekviewEvents), 'wrong total count');
748     }
749     
750     /**
751      * testMoveRecurException
752      * 
753      * @see 0008704: moving a recur exception twice creates concurrency exception
754      * - this has been a client problem, server did everything right
755      */
756     public function testMoveRecurException()
757     {
758         $from = new Tinebase_DateTime('2012-02-01 00:00:00');
759         $until = new Tinebase_DateTime('2012-02-29 23:59:59');
760         
761         $persistentEvent1 = $this->_getDailyEvent(new Tinebase_DateTime('2012-02-03 09:00:00'));
762         $persistentEvent2 = $this->_getDailyEvent(new Tinebase_DateTime('2012-02-03 13:00:00'));
763         
764         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
765         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($persistentEvent1, $exceptions, $from, $until);
766         
767         $recurSet[5]->dtstart = new Tinebase_DateTime('2012-02-09 13:00:00');
768         $recurSet[5]->dtend = new Tinebase_DateTime('2012-02-09 14:00:00');
769         
770         $updatedPersistentEvent = $this->_controller->createRecurException($recurSet[5]);
771         
772         $updatedPersistentEvent->dtstart = new Tinebase_DateTime('2012-03-09 13:00:00');
773         $updatedPersistentEvent->dtend = new Tinebase_DateTime('2012-03-09 14:00:00');
774         
775         $this->setExpectedException('Tinebase_Timemachine_Exception_ConcurrencyConflict');
776         $updatedPersistentEvent = $this->_controller->createRecurException($updatedPersistentEvent);
777     }
778     
779     /**
780      * returns a simple recure event
781      *
782      * @return Calendar_Model_Event
783      */
784     protected function _getRecurEvent()
785     {
786         return new Calendar_Model_Event(array(
787             'summary'     => 'Breakfast',
788             'dtstart'     => '2010-05-20 06:00:00',
789             'dtend'       => '2010-05-20 06:15:00',
790             'description' => 'Breakfast',
791             'rrule'       => 'FREQ=DAILY;INTERVAL=1',    
792             'container_id' => $this->_getTestCalendar()->getId(),
793             Tinebase_Model_Grants::GRANT_EDIT    => true,
794         ));
795     }
796 }