Merge branch '2013.10' into 2014.11
authorPhilipp Schüle <p.schuele@metaways.de>
Wed, 20 Jan 2016 14:29:37 +0000 (15:29 +0100)
committerPhilipp Schüle <p.schuele@metaways.de>
Wed, 20 Jan 2016 14:29:37 +0000 (15:29 +0100)
Conflicts:
tests/tine20/Calendar/Controller/RecurTest.php
tine20/Calendar/Setup/setup.xml

Change-Id: I2ba9af2c9b5d45b7df8814b4afd87ce26462b839

1  2 
tests/tine20/Calendar/Controller/RecurTest.php
tests/tine20/Calendar/Frontend/ActiveSyncTest.php
tine20/ActiveSync/Controller.php
tine20/Calendar/Controller/Event.php
tine20/Calendar/Frontend/ActiveSync.php
tine20/Calendar/Setup/Update/Release8.php
tine20/Calendar/Setup/setup.xml

@@@ -35,7 -35,7 +35,7 @@@ class Calendar_Controller_RecurTest ext
              'dtend'         => '2012-06-01 18:30:00',
              'originator_tz' => 'Europe/Berlin',
              'rrule'         => 'FREQ=DAILY;INTERVAL=1;UNTIL=2011-05-31 17:30:00',
 -            'container_id'  => $this->_testCalendar->getId(),
 +            'container_id'  => $this->_getTestCalendar()->getId(),
          ));
          
          $this->setExpectedException('Tinebase_Exception_Record_Validation');
@@@ -85,7 -85,7 +85,7 @@@
              'dtend'         => '2011-04-20 15:30:00',
              'originator_tz' => 'Europe/Berlin',
              'rrule'         => 'FREQ=WEEKLY;INTERVAL=3;WKST=SU;BYDAY=TU,TH',
 -            'container_id'  => $this->_testCalendar->getId(),
 +            'container_id'  => $this->_getTestCalendar()->getId(),
              Tinebase_Model_Grants::GRANT_EDIT     => true,
          ));
          
@@@ -98,7 -98,7 +98,7 @@@
          $persistentEventException = $this->_controller->createRecurException($eventException);
          
          $weekviewEvents = $this->_controller->search(new Calendar_Model_EventFilter(array(
 -            array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_testCalendar->getId()),
 +            array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_getTestCalendar()->getId()),
          )));
          
          Calendar_Model_Rrule::mergeRecurrenceSet($weekviewEvents, $from, $until);
          $firstInstanceException->location = $location;
      
          $result = $this->_controller->update($firstInstanceException, FALSE, Calendar_Model_Event::RANGE_THISANDFUTURE);
 -        $this->assertEquals($result->location, $location);
 +        $this->assertEquals($location, $result->location);
      }
  
      /**
                  'dtend'         => '2012-02-22 15:30:00',
                  'originator_tz' => 'Europe/Berlin',
                  'rrule'         => 'FREQ=DAILY;COUNT=3',
 -                'container_id'  => $this->_testCalendar->getId(),
 +                'container_id'  => $this->_getTestCalendar()->getId(),
          ));
          
          $persistentEvent = $this->_controller->create($event);
          
          // create exception
          $weekviewEvents = $this->_controller->search(new Calendar_Model_EventFilter(array(
 -            array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_testCalendar->getId()),
 +            array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_getTestCalendar()->getId()),
          )));
          Calendar_Model_Rrule::mergeRecurrenceSet($weekviewEvents, $from, $until);
          $weekviewEvents[2]->dtstart->subHour(5);
          
          // load series
          $weekviewEvents = $this->_controller->search(new Calendar_Model_EventFilter(array(
 -            array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_testCalendar->getId()),
 +            array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_getTestCalendar()->getId()),
          )));
          Calendar_Model_Rrule::mergeRecurrenceSet($weekviewEvents, $from, $until);
          $weekviewEvents->sort('dtstart', 'ASC');
              'is_all_day_event'  => true,
              'originator_tz' => 'Europe/Berlin',
              'rrule'         => 'FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU,WE,TH',
 -            'container_id'  => $this->_testCalendar->getId(),
 +            'container_id'  => $this->_getTestCalendar()->getId(),
              Tinebase_Model_Grants::GRANT_EDIT     => true,
          ));
          
          $persistentEvent = $this->_controller->create($event);
          
          $weekviewEvents = $this->_controller->search(new Calendar_Model_EventFilter(array(
 -            array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_testCalendar->getId()),
 +            array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_getTestCalendar()->getId()),
          )));
          
          Calendar_Model_Rrule::mergeRecurrenceSet($weekviewEvents, $from, $until);
          $persistentEventException = $this->_controller->createRecurException($exception, TRUE);
          
          $weekviewEvents = $this->_controller->search(new Calendar_Model_EventFilter(array(
 -            array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_testCalendar->getId()),
 +            array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_getTestCalendar()->getId()),
          )));
          
          Calendar_Model_Rrule::mergeRecurrenceSet($weekviewEvents, $from, $until);
              'originator_tz' => 'Europe/Berlin',
              'rrule'         => 'FREQ=DAILY;INTERVAL=1;UNTIL=2009-03-31 17:30:00',
              'exdate'        => '2009-03-27 18:00:00,2009-03-29 17:00:00',
 -            'container_id'  => $this->_testCalendar->getId(),
 +            'container_id'  => $this->_getTestCalendar()->getId(),
              Tinebase_Model_Grants::GRANT_EDIT     => true,
          ));
          $event->attendee = $this->_getAttendee();
              'dtend'         => '2011-04-20 15:30:00',
              'originator_tz' => 'Europe/Berlin',
              'rrule'         => 'FREQ=WEEKLY;INTERVAL=3;WKST=SU;BYDAY=TU,TH',
 -            'container_id'  => $this->_testCalendar->getId(),
 +            'container_id'  => $this->_getTestCalendar()->getId(),
              Tinebase_Model_Grants::GRANT_EDIT     => true,
          ));
          $event->attendee = $this->_getAttendee();
          $this->_controller->attenderStatusCreateRecurException(clone $persistentEvent, $attendee, $attendee->status_authkey);
          
          $weekviewEvents = $this->_controller->search(new Calendar_Model_EventFilter(array(
 -            array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_testCalendar->getId()),
 +            array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_getTestCalendar()->getId()),
          )));
          
          Calendar_Model_Rrule::mergeRecurrenceSet($weekviewEvents, $from, $until);
          $event->dtstart = '2010-05-20 06:00:00';
          $event->dtend = '2010-05-20 06:15:00';
          $event->attendee = new Tinebase_Record_RecordSet('Calendar_Model_Attender', array(
 -            array('user_type' => Calendar_Model_Attender::USERTYPE_USER, 'user_id' => $this->_personasContacts['sclever']->getId()),
 -            array('user_type' => Calendar_Model_Attender::USERTYPE_USER, 'user_id' => $this->_personasContacts['pwulf']->getId())
 +            array('user_type' => Calendar_Model_Attender::USERTYPE_USER, 'user_id' => $this->_getPersonasContacts('sclever')->getId()),
 +            array('user_type' => Calendar_Model_Attender::USERTYPE_USER, 'user_id' => $this->_getPersonasContacts('pwulf')->getId())
          ));
          $this->_controller->create($event);
  
          $event1 = $this->_getRecurEvent();
          $event1->attendee = new Tinebase_Record_RecordSet('Calendar_Model_Attender', array(
 -            array('user_type' => Calendar_Model_Attender::USERTYPE_USER, 'user_id' => $this->_personasContacts['sclever']->getId()),
 -            array('user_type' => Calendar_Model_Attender::USERTYPE_USER, 'user_id' => $this->_personasContacts['pwulf']->getId())
 +            array('user_type' => Calendar_Model_Attender::USERTYPE_USER, 'user_id' => $this->_getPersonasContacts('sclever')->getId()),
 +            array('user_type' => Calendar_Model_Attender::USERTYPE_USER, 'user_id' => $this->_getPersonasContacts('pwulf')->getId())
          ));
          
          $this->setExpectedException('Calendar_Exception_AttendeeBusy');
          $event->dtstart = '2010-05-20 06:00:00';
          $event->dtend = '2010-05-20 06:15:00';
          $event->attendee = new Tinebase_Record_RecordSet('Calendar_Model_Attender', array(
 -            array('user_type' => Calendar_Model_Attender::USERTYPE_USER, 'user_id' => $this->_personasContacts['sclever']->getId()),
 -            array('user_type' => Calendar_Model_Attender::USERTYPE_USER, 'user_id' => $this->_personasContacts['pwulf']->getId())
 +            array('user_type' => Calendar_Model_Attender::USERTYPE_USER, 'user_id' => $this->_getPersonasContacts('sclever')->getId()),
 +            array('user_type' => Calendar_Model_Attender::USERTYPE_USER, 'user_id' => $this->_getPersonasContacts('pwulf')->getId())
          ));
          $this->_controller->create($event);
  
          $event1 = $this->_getRecurEvent();
          $event1->attendee = new Tinebase_Record_RecordSet('Calendar_Model_Attender', array(
 -            array('user_type' => Calendar_Model_Attender::USERTYPE_USER, 'user_id' => $this->_personasContacts['sclever']->getId()),
 -            array('user_type' => Calendar_Model_Attender::USERTYPE_USER, 'user_id' => $this->_personasContacts['pwulf']->getId())
 +            array('user_type' => Calendar_Model_Attender::USERTYPE_USER, 'user_id' => $this->_getPersonasContacts('sclever')->getId()),
 +            array('user_type' => Calendar_Model_Attender::USERTYPE_USER, 'user_id' => $this->_getPersonasContacts('pwulf')->getId())
          ));
          
          $event1 = $this->_controller->create($event1);
          $event = $this->_getRecurEvent();
          $event->rrule = "FREQ=MONTHLY;INTERVAL=1;BYDAY=3TH";
          $event->attendee = new Tinebase_Record_RecordSet('Calendar_Model_Attender', array(
 -            array('user_type' => Calendar_Model_Attender::USERTYPE_USER, 'user_id' => $this->_personasContacts['sclever']->getId()),
 -            array('user_type' => Calendar_Model_Attender::USERTYPE_USER, 'user_id' => $this->_personasContacts['pwulf']->getId())
 +            array('user_type' => Calendar_Model_Attender::USERTYPE_USER, 'user_id' => $this->_getPersonasContacts('sclever')->getId()),
 +            array('user_type' => Calendar_Model_Attender::USERTYPE_USER, 'user_id' => $this->_getPersonasContacts('pwulf')->getId())
          ));
          
          $persistentRecurEvent = $this->_controller->create($event);
                  'dtstart'       => '2012-03-13 09:00:00',
                  'dtend'         => '2012-03-13 10:00:00',
                  'rrule'         => 'FREQ=DAILY;INTERVAL=1',
 -                'container_id'  => $this->_testCalendar->getId(),
 +                'container_id'  => $this->_getTestCalendar()->getId(),
                  'attendee'      => $this->_getAttendee(),
          ));
          
              'dtstart'       => '2011-04-21 10:00:00',
              'dtend'         => '2011-04-21 12:00:00',
              'originator_tz' => 'Europe/Berlin',
-             'rrule'         => 'FREQ=DAILY;INTERVAL=1;UNTIL=2011-04-27 21:59:59',
+             'rrule'         => 'FREQ=DAILY;INTERVAL=1;UNTIL=2011-04-28 21:59:59',
 -            'container_id'  => $this->_testCalendar->getId()
 +            'container_id'  => $this->_getTestCalendar()->getId()
          ));
          
          $persistentEvent = $this->_controller->create($event);
          $recurSet->summary = 'Limo bei Schweinske';
          $recurSet[5]->dtstart->addHour(2);
          $recurSet[5]->dtend->addHour(2);
+         $recurSet[6]->dtstart->subDay(6);
+         $recurSet[6]->dtend->subDay(6);
          
          $this->_controller->createRecurException($recurSet[1], TRUE);  // (23) delete instance
          
          $updatedBaseEvent = $this->_controller->getRecurBaseEvent($recurSet[5]);
          $recurSet[5]->last_modified_time = $updatedBaseEvent->last_modified_time;
          $this->_controller->createRecurException($recurSet[5], FALSE); // (27) move instance
+         $updatedBaseEvent = $this->_controller->getRecurBaseEvent($recurSet[6]);
+         $recurSet[6]->last_modified_time = $updatedBaseEvent->last_modified_time;
+         $this->_controller->createRecurException($recurSet[6], FALSE); // (28) move instance to 22
          
          // now test update allfollowing
          $recurSet[3]->summary = 'Spezi bei Schwinske';
          
          $updatedBaseEvent = $this->_controller->getRecurBaseEvent($recurSet[3]);
          $recurSet[3]->last_modified_time = $updatedBaseEvent->last_modified_time;
-         $newBaseEvent = $this->_controller->createRecurException($recurSet[3], FALSE, TRUE);
+         $newBaseEvent = $this->_controller->createRecurException($recurSet[3], FALSE, TRUE); // split at 25
          
          $events = $this->_controller->search(new Calendar_Model_EventFilter(array(
 -            array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_testCalendar->getId()),
 +            array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_getTestCalendar()->getId()),
              array('field' => 'period', 'operator' => 'within', 'value' => array('from' => $from, 'until' => $until),
          ))));
          
          Calendar_Model_Rrule::mergeRecurrenceSet($events, $from, $until);
-         $this->assertEquals(6, count($events), 'there should be exactly 6 events');
+         $this->assertEquals(7, count($events), 'there should be exactly 6 events');
          
          $oldSeries = $events->filter('uid', $persistentEvent->uid);
          $newSeries = $events->filter('uid', $newBaseEvent->uid);
          $this->assertEquals(3, count($oldSeries), 'there should be exactly 3 events with old uid');
-         $this->assertEquals(3, count($newSeries), 'there should be exactly 3 events with new uid');
+         $this->assertEquals(4, count($newSeries), 'there should be exactly 3 events with new uid');
          
          $this->assertEquals(1, count($oldSeries->filter('recurid', "/^$/", TRUE)), 'there should be exactly one old base event');
          $this->assertEquals(1, count($newSeries->filter('recurid', "/^$/", TRUE)), 'there should be exactly one new base event');
          
          $this->assertEquals(1, count($oldSeries->filter('recurid', "/^.+/", TRUE)->filter('rrule', '/^$/', TRUE)), 'there should be exactly one old persitent event exception');
-         $this->assertEquals(1, count($newSeries->filter('recurid', "/^.+/", TRUE)->filter('rrule', '/^$/', TRUE)), 'there should be exactly one new persitent event exception');
+         $this->assertEquals(2, count($newSeries->filter('recurid', "/^.+/", TRUE)->filter('rrule', '/^$/', TRUE)), 'there should be exactly one new persitent event exception');
          
          $this->assertEquals(1, count($oldSeries->filter('id', "/^fake.*/", TRUE)), 'there should be exactly one old fake event');
          $this->assertEquals(1, count($newSeries->filter('id', "/^fake.*/", TRUE)), 'there should be exactly one new fake event'); //26 (reset)
          
          $this->assertFalse(!!array_diff($newBaseEvent->exdate, array(
              new Tinebase_DateTime('2011-04-27 14:00:00'),
+             new Tinebase_DateTime('2011-04-28 14:00:00'),
          )), 'exdate of new series');
          
          $this->assertFalse(!!array_diff($oldSeries->dtstart, array(
              new Tinebase_DateTime('2011-04-22 10:00:00'),
              new Tinebase_DateTime('2011-04-24 10:00:00'),
          )), 'dtstart of old series');
-         
          $this->assertFalse(!!array_diff($newSeries->dtstart, array(
              new Tinebase_DateTime('2011-04-25 14:00:00'),
              new Tinebase_DateTime('2011-04-26 14:00:00'),
              new Tinebase_DateTime('2011-04-27 12:00:00'),
+             new Tinebase_DateTime('2011-04-22 10:00:00'),
          )), 'dtstart of new series');
      }
  
              'dtstart'       => '2012-02-03 09:00:00',
              'dtend'         => '2012-02-03 10:00:00',
              'rrule'         => 'FREQ=DAILY;INTERVAL=1',
 -            'container_id'  => $this->_testCalendar->getId(),
 +            'container_id'  => $this->_getTestCalendar()->getId(),
              'attendee'      => $this->_getAttendee(),
          ));
          
              'dtend'         => '2012-06-01 12:00:00',
              'originator_tz' => 'Europe/Berlin',
              'rrule'         => 'FREQ=WEEKLY;INTERVAL=1',
 -            'container_id'  => $this->_testCalendar->getId()
 +            'container_id'  => $this->_getTestCalendar()->getId()
          ));
          
          $persistentEvent = $this->_controller->create($event);
              'dtstart'       => '2012-02-03 09:00:00',
              'dtend'         => '2012-02-03 10:00:00',
              'rrule'         => 'FREQ=DAILY;INTERVAL=1',
 -            'container_id'  => $this->_testCalendar->getId(),
 +            'container_id'  => $this->_getTestCalendar()->getId(),
              'attendee'      => $this->_getAttendee(),
          ));
          
          $this->_controller->attenderStatusCreateRecurException($start, $sclever, $sclever->status_authkey, TRUE);
          
          $events = $this->_controller->search(new Calendar_Model_EventFilter(array(
 -            array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_testCalendar->getId())
 +            array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_getTestCalendar()->getId())
          )))->sort('dtstart', 'ASC');
          
          // assert two baseEvents
          $this->assertFalse($events[1]->rrule_until instanceof Tinebase_DateTime, 'rrule_until of second baseEvent must not be set');
          $this->assertEquals(Calendar_Model_Attender::STATUS_ACCEPTED, Calendar_Model_Attender::getAttendee($events[1]->attendee, $event->attendee[1])->status, 'second baseEvent status is not touched');
      }
 -    
 +
 +    public function testCreateRecurException()
 +    {
 +        $event = $this->_getEvent();
 +        $event->rrule = 'FREQ=DAILY;INTERVAL=1;UNTIL=2009-04-30 13:30:00';
 +        $persistentEvent = $this->_controller->create($event);
 +
 +        $exception = clone $persistentEvent;
 +        $exception->dtstart->addDay(3);
 +        $exception->dtend->addDay(3);
 +        $exception->summary = 'Abendbrot';
 +        $exception->recurid = $exception->uid . '-' . $exception->dtstart->get(Tinebase_Record_Abstract::ISO8601LONG);
 +        $persistentException = $this->_controller->createRecurException($exception);
 +
 +        $persistentEvent = $this->_controller->get($persistentEvent->getId());
 +        $this->assertEquals(1, count($persistentEvent->exdate));
 +
 +        $events = $this->_controller->search(new Calendar_Model_EventFilter(array(
 +            array('field' => 'uid',     'operator' => 'equals', 'value' => $persistentEvent->uid),
 +        )));
 +        $this->assertEquals(2, count($events));
 +
 +        return $persistentException;
 +    }
 +
 +    public function testGetRecurExceptions()
 +    {
 +        $persistentException = $this->testCreateRecurException();
 +
 +        $baseEvent = $this->_controller->getRecurBaseEvent($persistentException);
 +
 +        $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
 +        $nextOccurance = Calendar_Model_Rrule::computeNextOccurrence($baseEvent, $exceptions, $baseEvent->dtend);
 +        $this->_controller->createRecurException($nextOccurance, TRUE);
 +
 +        $exceptions = $this->_controller->getRecurExceptions($persistentException, TRUE);
 +        $dtstarts = $exceptions->dtstart;
 +
 +        $this->assertTrue(in_array($nextOccurance->dtstart, $dtstarts), 'deleted instance missing');
 +        $this->assertTrue(in_array($persistentException->dtstart, $dtstarts), 'exception instance missing');
 +    }
 +
 +    /**
 +     * testUpdateEventWithRruleAndRecurId
 +     *
 +     * @see 0008696: do not allow both rrule and recurId in event
 +     */
 +    public function testUpdateEventWithRruleAndRecurId()
 +    {
 +        $persistentRecurEvent = $this->testCreateRecurException();
 +        $persistentRecurEvent->rrule = 'FREQ=DAILY;INTERVAL=1';
 +
 +        $updatedEvent = $this->_controller->update($persistentRecurEvent);
 +
 +        $this->assertEquals(NULL, $updatedEvent->rrule);
 +    }
 +
     /**
      * @see {http://forge.tine20.org/mantisbt/view.php?id=5686}
      */
          
          $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
          $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($persistentEvent, $exceptions, $from, $until);
 +
 +        $pwulf = new Calendar_Model_Attender(array(
 +            'user_type'   => Calendar_Model_Attender::USERTYPE_USER,
 +            'user_id'     => $this->_getPersonasContacts('pwulf')->getId()
 +        ));
 +        $recurSet[5]->attendee->addRecord($pwulf);
 +        
 +        $updatedPersistentEvent = $this->_controller->createRecurException($recurSet[5], FALSE, TRUE);
 +        
 +        $this->assertEquals(3, count($updatedPersistentEvent->attendee));
 +
 +        $persistentPwulf = Calendar_Model_Attender::getAttendee($updatedPersistentEvent->attendee, $pwulf);
 +        $this->assertNotNull($persistentPwulf->displaycontainer_id);
 +    }
 +    
 +    /**
 +     * Events don't show up in attendees personal calendar
 +     */
 +    public function testCreateRecurExceptionAllFollowingAttendeeAdd2()
 +    {
 +        $from = new Tinebase_DateTime('2014-04-01 00:00:00');
 +        $until = new Tinebase_DateTime('2014-04-29 23:59:59');
 +        
 +        $persistentEvent = $this->_getDailyEvent(new Tinebase_DateTime('2014-04-03 09:00:00'));
 +        
 +        $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
 +        $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($persistentEvent, $exceptions, $from, $until);
          
          $recurSet[5]->attendee->addRecord(new Calendar_Model_Attender(array(
 -            'user_type'   => Calendar_Model_Attender::USERTYPE_USER,
 -            'user_id'     => $this->_personasContacts['pwulf']->getId()
 +                'user_type'   => Calendar_Model_Attender::USERTYPE_USER,
 +                'user_id'     => $this->_getPersonasContacts('pwulf')->getId()
          )));
          
          $updatedPersistentEvent = $this->_controller->createRecurException($recurSet[5], FALSE, TRUE);
 -        
          $this->assertEquals(3, count($updatedPersistentEvent->attendee));
 +        
 +        $filter = new Calendar_Model_EventFilter(array(
 +                array('field' => 'container_id',             'operator' => 'equals', 'value' => $this->_personasDefaultCals['pwulf']->id),
 +                array('field' => 'attender_status', 'operator' => 'not',    'value' => Calendar_Model_Attender::STATUS_DECLINED),
 +        ));
 +        
 +        $events = $this->_controller->search($filter);
 +        $this->assertEquals(1, count($events), 'event should be found, but is not');
      }
      
      /**
              'dtstart'       => $dtstart->toString(),
              'dtend'         => $dtstart->addHour(1)->toString(),
              'rrule'         => 'FREQ=DAILY;INTERVAL=1',
 -            'container_id'  => $this->_testCalendar->getId(),
 +            'container_id'  => $this->_getTestCalendar()->getId(),
              'attendee'      => $this->_getAttendee(),
          ));
          return $this->_controller->create($event);
              'dtend'         => '2012-02-21 15:30:00',
              'originator_tz' => 'Europe/Berlin',
              'rrule'         => 'FREQ=DAILY;COUNT=5',
 -            'container_id'  => $this->_testCalendar->getId(),
 +            'container_id'  => $this->_getTestCalendar()->getId(),
          ));
          
          $persistentEvent = $this->_controller->create($event);
          
          // create exception
          $weekviewEvents = $this->_controller->search(new Calendar_Model_EventFilter(array(
 -            array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_testCalendar->getId()),
 +            array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_getTestCalendar()->getId()),
          )));
          Calendar_Model_Rrule::mergeRecurrenceSet($weekviewEvents, $from, $until);
          $weekviewEvents[2]->dtstart->subHour(5);
          
          // load events
          $weekviewEvents = $this->_controller->search(new Calendar_Model_EventFilter(array(
 -            array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_testCalendar->getId()),
 +            array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_getTestCalendar()->getId()),
          )));
          Calendar_Model_Rrule::mergeRecurrenceSet($weekviewEvents, $from, $until);
          $weekviewEvents->sort('dtstart', 'ASC');
          $this->assertEquals(2, count($weekviewEvents->filter('uid', $weekviewEvents[0]->uid)), 'shorten failed');
          $this->assertEquals(5, count($weekviewEvents), 'wrong total count');
      }
 -    
 +
 +    public function testCreateRecurExceptionAllFollowingContainerMove()
 +    {
 +        $this->markTestSkipped('exdate container move not yet forbidden');
 +        $exception = $this->testCreateRecurException();
 +        $baseEvent = $this->_controller->getRecurBaseEvent($exception);
 +
 +        $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
 +        $from = $baseEvent->dtstart;
 +        $until = $baseEvent->dtstart->getClone()->addDay(1);
 +        $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($baseEvent, $exceptions, $from, $until);
 +
 +        $recurSet->getFirstRecord()->container_id = $this->_getTestContainer('Calendar')->getId();
 +        $newSeries = $this->_controller->createRecurException($recurSet->getFirstRecord(), false, true);
 +        $newExceptions = $this->_controller->getRecurExceptions($newSeries);
 +
 +//        print_r($newSeries->toArray());
 +//        print_r($newExceptions->toArray());
 +    }
 +
      /**
       * testMoveRecurException
       * 
          $this->setExpectedException('Tinebase_Timemachine_Exception_ConcurrencyConflict');
          $updatedPersistentEvent = $this->_controller->createRecurException($updatedPersistentEvent);
      }
 -    
 +
 +    public function testExdateContainerMoveCreateException()
 +    {
 +        $this->markTestSkipped('exdate container move not yet forbidden');
 +        $event = $this->_getDailyEvent(new Tinebase_DateTime('2014-02-03 09:00:00'));
 +
 +        $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
 +        
 +        $from = new Tinebase_DateTime('2014-02-01 00:00:00');
 +        $until = new Tinebase_DateTime('2014-02-29 23:59:59');
 +        
 +        $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($event, $exceptions, $from, $until);
 +
 +        $this->setExpectedException('Calendar_Exception_ExdateContainer');
 +
 +        $recurSet[2]->container_id = $this->_getTestContainer('Calendar')->getId();
 +        $this->_controller->createRecurException($recurSet[2]);
 +    }
 +
 +    public function testExdateContainerMoveUpdateException()
 +    {
 +        $this->markTestSkipped('exdate container move not yet forbidden');
 +        $event = $this->_getDailyEvent(new Tinebase_DateTime('2014-02-03 09:00:00'));
 +
 +        $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
 +
 +        $from = new Tinebase_DateTime('2014-02-01 00:00:00');
 +        $until = new Tinebase_DateTime('2014-02-29 23:59:59');
 +
 +        $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($event, $exceptions, $from, $until);
 +
 +        $recurSet[2]->summary = 'exdate';
 +
 +        $updatedPersistentEvent = $this->_controller->createRecurException($recurSet[2]);
 +
 +        $this->setExpectedException('Calendar_Exception_ExdateContainer');
 +
 +        $updatedPersistentEvent->container_id = $this->_getTestContainer('Calendar')->getId();
 +        $this->_controller->update($updatedPersistentEvent);
 +
 +    }
 +
 +    /**
 +     * test get free busy info with recurring event and dst
 +     *
 +     * @see 0009558: sometimes free/busy conflicts are not detected
 +     */
 +    public function testFreeBusyWithRecurSeriesAndRessourceInDST()
 +    {
 +        $event = $this->_getEvent();
 +        $resource = Calendar_Controller_Resource::getInstance()->create($this->_getResource());
 +        $event->attendee = new Tinebase_Record_RecordSet('Calendar_Model_Attender', array(array(
 +            'user_id'   => $resource->getId(),
 +            'user_type' => Calendar_Model_Attender::USERTYPE_RESOURCE,
 +        )));
 +        $event->dtstart = new Tinebase_DateTime('2013-10-14 10:30:00'); // this is UTC
 +        $event->dtend = new Tinebase_DateTime('2013-10-14 11:45:00');
 +        $event->rrule = 'FREQ=WEEKLY;INTERVAL=1;WKST=SU;BYDAY=MO';
 +        $persistentEvent = Calendar_Controller_Event::getInstance()->create($event);
 +
 +        // check free busy in DST
 +        $newEvent = $this->_getEvent();
 +        $newEvent->attendee = new Tinebase_Record_RecordSet('Calendar_Model_Attender', array(array(
 +            'user_id'   => $resource->getId(),
 +            'user_type' => Calendar_Model_Attender::USERTYPE_RESOURCE,
 +        )));
 +        $newEvent->dtstart = new Tinebase_DateTime('2014-01-20 12:30:00');
 +        $newEvent->dtend = new Tinebase_DateTime('2014-01-20 13:30:00');
 +
 +        $this->setExpectedException('Calendar_Exception_AttendeeBusy');
 +        $savedEvent = Calendar_Controller_Event::getInstance()->create($newEvent, /* $checkBusyConflicts = */ true);
 +    }
 +
      /**
       * returns a simple recure event
       *
              'dtend'       => '2010-05-20 06:15:00',
              'description' => 'Breakfast',
              'rrule'       => 'FREQ=DAILY;INTERVAL=1',    
 -            'container_id' => $this->_testCalendar->getId(),
 +            'container_id' => $this->_getTestCalendar()->getId(),
              Tinebase_Model_Grants::GRANT_EDIT    => true,
          ));
      }
@@@ -2,18 -2,18 +2,18 @@@
  /**
   * Tine 2.0 - http://www.tine20.org
   * 
 - * @package     ActiveSync
 + * @package     Calendar
   * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
   * @copyright   Copyright (c) 2010-2014 Metaways Infosystems GmbH (http://www.metaways.de)
   * @author      Cornelius Weiss <c.weiss@metaways.de>
   */
  
  /**
 - * Test class for Calendar_Controller_Event
 + * Test class for Calendar_Frontend_ActiveSync
   * 
 - * @package     ActiveSync
 + * @package     Calendar
   */
 -class ActiveSync_Controller_CalendarTests extends ActiveSync_Controller_ControllerTest
 +class Calendar_Frontend_ActiveSyncTest extends ActiveSync_Controller_ControllerTest
  {
      /**
       * name of the application
       */
      protected $_applicationName = 'Calendar';
      
 -    protected $_controllerName = 'ActiveSync_Controller_Calendar';
 +    protected $_controllerName = 'Calendar_Frontend_ActiveSync';
      
      protected $_class = Syncroton_Data_Factory::CLASS_CALENDAR;
      
 -    protected $_prefs;
 -    
 -    protected $_defaultCalendar;
 -    
 -    protected $_defaultHasChanged = false;
 -    
      /**
       * @var array test objects
       */
@@@ -51,7 -57,7 +51,7 @@@
                              <Calendar:BusyStatus>2</Calendar:BusyStatus>
                              <Calendar:DtStamp>20121125T150537Z</Calendar:DtStamp>
                              <Calendar:EndTime>20121123T160000Z</Calendar:EndTime>
 -                            <Calendar:Sensitivity>0</Calendar:Sensitivity>
 +                            <Calendar:Sensitivity>2</Calendar:Sensitivity>
                              <Calendar:Subject>Repeat</Calendar:Subject>
                              <Calendar:StartTime>20121123T130000Z</Calendar:StartTime>
                              <Calendar:UID>6de7cb687964dc6eea109cd81750177979362217</Calendar:UID>
@@@ -503,8 -509,6 +503,8 @@@ Zeile 3</AirSyncBase:Data
      {
          parent::setUp();
          
 +        Calendar_Controller_Event::getInstance()->doContainerACLChecks(true);
 +        
          // replace email to make current user organizer and attendee
          $testConfig = Zend_Registry::get('testConfig');
          $email = ($testConfig->email) ? $testConfig->email : Tinebase_Core::getUser()->accountEmailAddress;
              'lars@kneschke.de',
              'unittest@tine20.org',
          ), $email, $this->_testXMLInput);
 -        
 -        $this->_prefs = $prefs = new Calendar_Preference();
 -        $this->_defaultCalendar = Tinebase_Core::getPreference('Calendar')->{Calendar_Preference::DEFAULTCALENDAR};
 -    }
 -    
 -    protected  function tearDown()
 -    {
 -        parent::tearDown();
 -        
 -        if ($this->_defaultHasChanged) {
 -            $this->_prefs->setValue(Calendar_Preference::DEFAULTCALENDAR, $this->_defaultCalendar);
 -        }
      }
      
      /**
          $this->assertEquals(2,         $syncrotonEvent->busyStatus);
          $this->assertEquals('Repeat',  $syncrotonEvent->subject);
          $this->assertEquals(15,        $syncrotonEvent->reminder);
 +        $this->assertEquals(2,         $syncrotonEvent->sensitivity);
          $this->assertTrue($syncrotonEvent->endTime instanceof DateTime);
          $this->assertTrue($syncrotonEvent->startTime instanceof DateTime);
          $this->assertEquals($thisYear . '1123T160000Z', $syncrotonEvent->endTime->format('Ymd\THis\Z'));
          #echo '----------------' . PHP_EOL; foreach ($syncrotonEvent as $key => $value) {echo "$key => "; var_dump($value);}
          
          $this->assertEquals(0, $syncrotonEvent->allDayEvent);
 +        $this->assertEquals(0, $syncrotonEvent->sensitivity);
          $this->assertTrue($syncrotonEvent->endTime instanceof DateTime);
          $this->assertTrue($syncrotonEvent->startTime instanceof DateTime);
          $this->assertEquals('20101220T100000Z', $syncrotonEvent->endTime->format('Ymd\THis\Z'));
          list($serverId, $syncrotonEvent) = $this->testCreateEntry($syncrotonFolder);
          
          // transfer event to other user
 -        $rwright = array_value('rwright', $this->_personas = Zend_Registry::get('personas'));
 +        $rwright = Tinebase_Helper::array_value('rwright', $this->_personas = Zend_Registry::get('personas'));
          $eventBackend = new Calendar_Backend_Sql();
          $eventBackend->updateMultiple($eventBackend->getMultipleByProperty($syncrotonEvent->uID, 'uid')->id, array(
              'container_id'  => Tinebase_Core::getPreference('Calendar')->getValueForUser(Calendar_Preference::DEFAULTCALENDAR, $rwright->getId()),
      
          $folderA = $this->testCreateFolder(); // personal of test user
          
 -        $sclever = array_value('sclever', Zend_Registry::get('personas'));
 +        $sclever = Tinebase_Helper::array_value('sclever', Zend_Registry::get('personas'));
          $folderB = Tinebase_Core::getPreference('Calendar')->getValueForUser(Calendar_Preference::DEFAULTCALENDAR, $sclever->getId());
  
          // have syncGerant for sclever
@@@ -1189,7 -1203,8 +1189,7 @@@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
          $syncrotonFolder2 = $this->testCreateFolder();
          
          //make $syncrotonFolder2 the default
 -        $this->_prefs->setValue(Calendar_Preference::DEFAULTCALENDAR, $syncrotonFolder2->serverId);
 -        $this->_defaultHasChanged = true;
 +        Tinebase_Core::getPreference('Calendar')->setValue(Calendar_Preference::DEFAULTCALENDAR, $syncrotonFolder2->serverId);
          
          $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), Tinebase_DateTime::now());
          
      {
          // create event
          $syncrotonFolder = $this->testCreateFolder();
 +        $syncrotonFolder2 = $this->testCreateFolder();
 +        
 +        //make $syncrotonFolder2 the default
 +        Tinebase_Core::getPreference('Calendar')->setValue(Calendar_Preference::DEFAULTCALENDAR, $syncrotonFolder2->serverId);
 +        
          $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), Tinebase_DateTime::now());
          list($serverId, $syncrotonEvent) = $this->testCreateEntry($syncrotonFolder);
  
  
          // assert attendee are preserved
          $updatedEvent = Calendar_Controller_MSEventFacade::getInstance()->get($serverId);
-         $this->assertNotEmpty($updatedEvent->attendee, 'attendee must be preserved during update');
+         $this->assertNotEmpty($updatedEvent, 'attendee must be preserved during update');
      }
  
      /**
       *
       * for iOS devices we need to suppress attendee during sync for non default folder (see foldertype in foldersync)
       * iOS can only have one default folder
-      *
-      * NOTE: our implementation in not correct at the moment as we only append attendee if the event is in the default folder.
-      *       correct implementation would be to append attendee when default folder is synced
       */
      public function testUpdateEntriesIPhoneNonDefaultFolder()
      {
          $this->assertEquals($syncrotonEventtoUpdate->exceptions[0]->startTime->toString(), $updatedSyncrotonEvent->exceptions[0]->startTime->toString());
  
          // assert attendee are preserved
-         $updatedEvent = Calendar_Controller_MSEventFacade::getInstance()->get($serverId);
-         $this->assertNotEmpty($updatedEvent->attendee, 'attendee must be preserved during update');
+         $this->assertNotEmpty($updatedSyncrotonEvent->attendees, 'attendee must be preserved during update');
      }
  }
@@@ -55,20 -55,27 +55,27 @@@ class ActiveSync_Controller extends Tin
      /**
       * reset sync for user
       *
-      * @param string $username
+      * @param mixed $user
       * @param array $classesToReset
       * @return boolean
       */
-     public function resetSyncForUser($username, $classesToReset)
+     public function resetSyncForUser($user, $classesToReset)
      {
+         if (! $user instanceof Tinebase_Model_User) {
+             try {
+                 $user = Tinebase_User::getInstance()->getFullUserById($user);
+             } catch (Tinebase_Exception_NotFound $tenf) {
+                 $user = Tinebase_User::getInstance()->getUserByPropertyFromSqlBackend('accountLoginName', $user);
+             }
+         }
          if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
-                 . ' Resetting sync for user ' . $username . ' collections: ' . print_r($classesToReset, true));
-         
-         $user = Tinebase_User::getInstance()->getUserByPropertyFromSqlBackend('accountLoginName', $username);
-         
+             . ' Resetting sync for user ' . $user->accountDisplayName . ' collections: ' . print_r($classesToReset, true));
          self::initSyncrotonRegistry();
          
-         $devices = $this->_getDevicesForUser($user);
+         $devices = $this->_getDevicesForUser($user->getId());
          
          foreach ($devices as $device) {
              if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
      /**
       * fetch devices for user
       * 
-      * @param Tinebase_Model_FullUser $user
+      * @param string $userId
       */
-     protected function _getDevicesForUser($user)
+     protected function _getDevicesForUser($userId)
      {
          $deviceBackend = new ActiveSync_Backend_Device();
          $deviceFilter = new ActiveSync_Model_DeviceFilter(array(
-             array('field' => 'owner_id', 'operator' => 'equals', 'value' => $user->getId())
+             array('field' => 'owner_id', 'operator' => 'equals', 'value' => $userId)
          ));
          $devices = $deviceBackend->search($deviceFilter);
          return $devices;
          Syncroton_Registry::set(Syncroton_Registry::SYNCSTATEBACKEND,    new Syncroton_Backend_SyncState(Tinebase_Core::getDb(), SQL_TABLE_PREFIX . 'acsync_'));
          Syncroton_Registry::set(Syncroton_Registry::CONTENTSTATEBACKEND, new Syncroton_Backend_Content(Tinebase_Core::getDb(), SQL_TABLE_PREFIX . 'acsync_'));
          Syncroton_Registry::set(Syncroton_Registry::POLICYBACKEND,       new Syncroton_Backend_Policy(Tinebase_Core::getDb(), SQL_TABLE_PREFIX . 'acsync_'));
 -        Syncroton_Registry::set('loggerBackend',       Tinebase_Core::getLogger());
 +        Syncroton_Registry::set(Syncroton_Registry::LOGGERBACKEND,       Tinebase_Core::getLogger());
 +        Syncroton_Registry::set(Syncroton_Registry::SESSION_VALIDATOR,   function() {
 +            return ! Tinebase_Core::inMaintenanceMode();
 +        });
      }
  }
@@@ -159,6 -159,7 +159,6 @@@ class Calendar_Controller_Event extend
      {
          $ignoreUIDs = !empty($_event->uid) ? array($_event->uid) : array();
          
 -        // 
          if ($_event->transp == Calendar_Model_Event::TRANSP_TRANSP || count($_event->attendee) < 1) {
              if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
                  . " Skipping free/busy check because event is transparent");
              $busyException = new Calendar_Exception_AttendeeBusy();
              $busyException->setFreeBusyInfo($fbInfo);
              
 -            Calendar_Model_Attender::resolveAttendee($_event->attendee, FALSE);
 +            Calendar_Model_Attender::resolveAttendee($_event->attendee, /* resolve_display_containers = */ false);
              $busyException->setEvent($_event);
              
              throw $busyException;
          $filter = new Calendar_Model_EventFilter($filterData);
          
          if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . ' ' . __LINE__
 -            . ' free/busy fitler: ' . print_r($filter->toArray(), true));
 +            . ' free/busy filter: ' . print_r($filter->toArray(), true));
          
          $events = $this->search($filter, new Tinebase_Model_Pagination(), FALSE, FALSE);
          
          
          // create a typemap
          $typeMap = array();
 -        foreach($attendee as $attender) {
 +        foreach ($attendee as $attender) {
              if (! (isset($typeMap[$attender['user_type']]) || array_key_exists($attender['user_type'], $typeMap))) {
                  $typeMap[$attender['user_type']] = array();
              }
              $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction($db);
              
              $sendNotifications = $this->sendNotifications(FALSE);
 -            
 +
              $event = $this->get($_record->getId());
              if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
                      .' Going to update the following event. rawdata: ' . print_r($event->toArray(), true));
 -            
 +
              //NOTE we check via get(full rights) here whereas _updateACLCheck later checks limited rights from search
              if ($this->_doContainerACLChecks === FALSE || $event->hasGrant(Tinebase_Model_Grants::GRANT_EDIT)) {
                  Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " updating event: {$_record->id} (range: {$range})");
                      }
                  }
              }
 -            
 +
              if ($_record->isRecurException() && in_array($range, array(Calendar_Model_Event::RANGE_ALL, Calendar_Model_Event::RANGE_THISANDFUTURE))) {
                  $this->_updateExdateRange($_record, $range, $event);
              }
 -            
 +
              Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
          } catch (Exception $e) {
              if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Rolling back because: ' . $e);
       */
      protected function _applyExdateDiffToRecordSet($exdate, $diff, $events)
      {
 +        // make sure baseEvent gets updated first to circumvent concurrency conflicts
 +        $events->sort('recurdid', 'ASC');
 +
          foreach ($events as $event) {
              if ($event->getId() === $exdate->getId()) {
                  // skip the exdate
                      }
                      foreach ($attendeeDiff['removed'] as $attenderToRemove) {
                          $attenderInCurrentSet = Calendar_Model_Attender::getAttendee($event->attendee, $attenderToRemove);
 -                        $event->attendee->removeRecord($attenderInCurrentSet);
 +                        if ($attenderInCurrentSet) {
 +                            $event->attendee->removeRecord($attenderInCurrentSet);
 +                        }
                      }
                  } else {
                      // remove ids of new attendee
                  . " It is not allowed to create recur instance if it is clone of base event");
              throw new Tinebase_Timemachine_Exception_ConcurrencyConflict('concurrency conflict!');
          }
 -        
 +
 +//        // Maybe Later
 +//        // exdates needs to stay in baseEvents container
 +//        if ($_event->container_id != $baseEvent->container_id) {
 +//            throw new Calendar_Exception_ExdateContainer();
 +//        }
 +
          // check if this is an exception to the first occurence
          if ($baseEvent->getId() == $_event->getId()) {
              if ($_allFollowing) {
                  throw new Exception('please edit or delete complete series!');
              }
              // NOTE: if the baseEvent gets a time change, we can't compute the recurdid w.o. knowing the original dtstart
 -            $recurid = $baseEvent->setRecurId();
 +            $recurid = $baseEvent->setRecurId($baseEvent->getId());
              unset($baseEvent->recurid);
              $_event->recurid = $recurid;
          }
                      . " Creating persistent exception for: '{$_event->recurid}'");
                  if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ 
                      . " Recur exception: " . print_r($_event->toArray(), TRUE));
 -                
 +
 +                $_event->base_event_id = $baseEvent->getId();
                  $_event->setId(NULL);
                  unset($_event->rrule);
                  unset($_event->exdate);
              $pastPersistentExceptionEvents = new Tinebase_Record_RecordSet('Calendar_Model_Event');
              $futurePersistentExceptionEvents = new Tinebase_Record_RecordSet('Calendar_Model_Event');
              foreach ($persistentExceptionEvents as $persistentExceptionEvent) {
-                 $persistentExceptionEvent->dtstart->isLater($_event->dtstart) ? $futurePersistentExceptionEvents->addRecord($persistentExceptionEvent) : $pastPersistentExceptionEvents->addRecord($persistentExceptionEvent);
+                 $persistentExceptionEvent->getOriginalDtStart()->isLater($_event->dtstart) ?
+                     $futurePersistentExceptionEvents->addRecord($persistentExceptionEvent) :
+                     $pastPersistentExceptionEvents->addRecord($persistentExceptionEvent);
              }
              
              // update baseEvent
              }
              $baseEvent->rrule = (string) $rrule;
              $baseEvent->exdate = $pastExdates;
 -            
 +
              // NOTE: we don't want implicit attendee updates
              //$updatedBaseEvent = $this->update($baseEvent, FALSE);
              $this->_inspectEvent($baseEvent);
                      $_event->rrule = (string) $rrule;
                  }
                  
 -                $_event->setId(NULL);
 +                $_event->setId(Tinebase_Record_Abstract::generateUID());
                  $_event->uid = $futurePersistentExceptionEvents->uid = Tinebase_Record_Abstract::generateUID();
 -                $futurePersistentExceptionEvents->setRecurId();
 +                $_event->setId(Tinebase_Record_Abstract::generateUID());
 +                $futurePersistentExceptionEvents->setRecurId($_event->getId());
                  unset($_event->recurid);
 +                unset($_event->base_event_id);
                  foreach(array('attendee', 'notes', 'alarms') as $prop) {
                      if ($_event->{$prop} instanceof Tinebase_Record_RecordSet) {
                          $_event->{$prop}->setId(NULL);
                      }
                  }
                  $_event->exdate = $futureExdates;
 -                $futurePersistentExceptionEvents->setRecurId();
 -                
 +
                  $attendees = $_event->attendee; unset($_event->attendee);
                  $note = $_event->notes; unset($_event->notes);
                  $persistentExceptionEvent = $this->create($_event, $_checkBusyConflicts && $dtStartHasDiff);
                  
 -                // we save attendee seperatly to preserve their attributes
 +                // we save attendee separately to preserve their attributes
                  if ($attendees instanceof Tinebase_Record_RecordSet) {
 -                    $attendees->cal_event_id = $persistentExceptionEvent->getId();
 -                    
                      foreach($attendees as $attendee) {
 -                        if (! $attendee->status_authkey) {
 -                            // new invitations
 -                            $attendee->status_authkey = Tinebase_Record_Abstract::generateUID();
 -                        }
 -                        $this->_backend->createAttendee($attendee);
 -                        $this->_increaseDisplayContainerContentSequence($attendee, $persistentExceptionEvent, Tinebase_Model_ContainerContent::ACTION_CREATE);
 +                        $this->_createAttender($attendee, $persistentExceptionEvent, true);
                      }
                  }
                  
       */
      public function getRecurBaseEvent($_event)
      {
 -        $possibleBaseEventIds = $this->_backend->search(new Calendar_Model_EventFilter(array(
 -            array('field' => 'uid',     'operator' => 'equals', 'value' => $_event->uid),
 -            array('field' => 'recurid', 'operator' => 'isnull', 'value' => NULL)
 -        )), NULL, TRUE);
 -        
 -        if (count($possibleBaseEventIds) > 1) {
 -            if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ .
 -                ' Got multiple possible base events: ' . print_r($possibleBaseEventIds, true));
 -        }
 -        $baseEventId = array_value(0, $possibleBaseEventIds);
 -        
 +        $baseEventId = $_event->base_event_id ?: $_event->id;
 +
          if (! $baseEventId) {
              throw new Tinebase_Exception_NotFound('base event of a recurring series not found');
          }
 -        
 +
          // make sure we have a 'fully featured' event
          return $this->get($baseEventId);
      }
  
 -   /**
 -    * lookup existing event by uid
 -    *
 -    * @param  Calendar_Model_Event $_event
 -    * @return Calendar_Model_Event|NULL
 -    * 
 -    * @todo also add more criteria for lookup (recurid, ...)
 -    * @todo sophisticated reccurring event handling
 -    */
 -    public function lookupExistingEvent($_event)
 -    {
 -        $events = $this->_backend->search(new Calendar_Model_EventFilter(array(
 -            array('field' => 'uid',     'operator' => 'equals', 'value' => $_event->uid),
 -            //array('field' => 'recurid', 'operator' => 'isnull', 'value' => NULL)
 -        )));
 -        
 -        $event = $events->filter(Tinebase_Model_Grants::GRANT_READ, TRUE)->getFirstRecord();
 -    
 -        // make sure we have a 'fully featured' event
 -        return ($event !== NULL) ? $this->get($event->getId()) : NULL;
 -    }
 -    
      /**
       * returns all persistent recur exceptions of recur series identified by uid of given event
       * 
       */
      public function getRecurExceptions($_event, $_fakeDeletedInstances = FALSE, $_eventFilter = NULL)
      {
 +        $baseEventId = $_event->base_event_id ?: $_event->id;
 +
          $exceptionFilter = new Calendar_Model_EventFilter(array(
 -            array('field' => 'uid',     'operator' => 'equals',  'value' => $_event->uid),
 -            array('field' => 'recurid', 'operator' => 'notnull', 'value' => NULL)
 +            array('field' => 'base_event_id', 'operator' => 'equals',  'value' => $baseEventId),
          ));
          
          if ($_eventFilter instanceof Calendar_Model_EventFilter) {
              $fakeEvent->dtend = clone $deletedInstanceDtStart;
              $fakeEvent->dtend->add($eventLength);
              $fakeEvent->is_deleted = TRUE;
 -            $fakeEvent->setRecurId();
 +            $fakeEvent->setRecurId($baseEvent->getId());
              $fakeEvent->rrule = null;
  
              $exceptions->addRecord($fakeEvent);
          // if dtstart of an event changes, we update the originator_tz, alarm times
          if (! $_oldRecord->dtstart->equals($_record->dtstart)) {
              if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' dtstart changed -> adopting organizer_tz');
 -            $_record->originator_tz = Tinebase_Core::get(Tinebase_Core::USERTIMEZONE);
 +            $_record->originator_tz = Tinebase_Core::getUserTimezone();
              if (! empty($_record->rrule)) {
                  $diff = $_oldRecord->dtstart->diff($_record->dtstart);
                  $this->_updateRecurIdOfExdates($_record, $diff);
              Calendar_Model_Rrule::addUTCDateDstFix($exception->recurid, $diff, $_record->originator_tz);
              $exdates[] = $exception->recurid;
              
 -            $exception->setRecurId();
 +            $exception->setRecurId($_record->getId());
              $this->_backend->update($exception);
          }
          
          if ($_record->rrule instanceof Calendar_Model_Rrule) {
              $_record->rrule->normalize($_record);
          }
 -        
 -        if ($_record->isRecurException() && $_record->rrule !== NULL) {
 -            if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
 -                . ' Removing invalid rrule from recur exception: ' . $_record->rrule);
 -            $_record->rrule = NULL;
 +
 +        if ($_record->isRecurException()) {
 +            $baseEvent = $this->getRecurBaseEvent($_record);
 +
 +            // remove invalid rrules
 +            if ($_record->rrule !== NULL) {
 +                if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
 +                    . ' Removing invalid rrule from recur exception: ' . $_record->rrule);
 +                $_record->rrule = NULL;
 +            }
 +
 +//            // Maybe Later
 +//            // exdates needs to stay in baseEvents container
 +//            if($_record->container_id != $baseEvent->container_id) {
 +//                throw new Calendar_Exception_ExdateContainer();
 +//            }
          }
      }
  
       */
      protected function _inspectOriginatorTZ($record)
      {
 -        $record->originator_tz = $record->originator_tz ? $record->originator_tz : Tinebase_Core::get(Tinebase_Core::USERTIMEZONE);
 +        $record->originator_tz = $record->originator_tz ? $record->originator_tz : Tinebase_Core::getUserTimezone();
  
          try {
              new DateTimeZone($record->originator_tz);
              
              if ($baseEvent->getId() == $_recurInstance->getId()) {
                  // exception to the first occurence
 -                $_recurInstance->setRecurId();
 +                $_recurInstance->setRecurId($baseEvent->getId());
              }
              
              // NOTE: recurid is computed by rrule recur computations and therefore is already part of the event.
                  $baseEvent->dtstart = new Tinebase_DateTime(substr($_recurInstance->recurid, -19), 'UTC');
                  $baseEvent->dtend   = clone $baseEvent->dtstart;
                  $baseEvent->dtend->add($diff);
 -                
 +
 +                $baseEvent->base_event_id = $baseEvent->id;
                  $baseEvent->id = $_recurInstance->id;
                  $baseEvent->recurid = $_recurInstance->recurid;
 -                
 +
                  $attendee = $baseEvent->attendee;
                  unset($baseEvent->attendee);
                  
      {
          // apply defaults
          $attender->user_type         = isset($attender->user_type) ? $attender->user_type : Calendar_Model_Attender::USERTYPE_USER;
 +        $attender->cal_event_id      =  $event->getId();
          $calendar = ($calendar) ? $calendar : Tinebase_Container::getInstance()->getContainerById($event->container_id);
          
          $userAccountId = $attender->getUserAccountId();
          $this->_increaseDisplayContainerContentSequence($attender, $event, Tinebase_Model_ContainerContent::ACTION_CREATE);
      }
      
 +        
 +    /**
 +     * returns the default calendar
 +     * 
 +     * @return Tinebase_Model_Container
 +     */
 +    public function getDefaultCalendar()
 +    {
 +        return Tinebase_Container::getInstance()->getDefaultContainer($this->_applicationName, NULL, Calendar_Preference::DEFAULTCALENDAR);
 +    }
 +    
      /**
       * returns default displayContainer id of given attendee
       *
          if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " About to send alarm " . print_r($_alarm->toArray(), TRUE));
          
          $doContainerACLChecks = $this->doContainerACLChecks(FALSE);
 -        
 -        $event = $this->get($_alarm->record_id);
 -        $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array($_alarm));
 -        $this->_inspectAlarmGet($event);
 -        
 +
 +        try {
 +            $event = $this->get($_alarm->record_id);
 +            $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array($_alarm));
 +            $this->_inspectAlarmGet($event);
 +        } catch (Exception $e) {
 +            $this->doContainerACLChecks($doContainerACLChecks);
 +            throw($e);
 +        }
 +
          $this->doContainerACLChecks($doContainerACLChecks);
 -        
 +
          if ($event->rrule) {
              $recurid = $_alarm->getOption('recurid');
              
@@@ -2,20 -2,20 +2,20 @@@
  /**
   * Tine 2.0
   *
 - * @package     ActiveSync
 - * @subpackage  Controller
 + * @package     Calendar
 + * @subpackage  Frontend
   * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 - * @copyright   Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
 + * @copyright   Copyright (c) 2009-2014 Metaways Infosystems GmbH (http://www.metaways.de)
   * @author      Cornelius Weiss <c.weiss@metaways.de>
   */
  
  /**
 - * controller events class
 + * ActiveSync frontend class
   * 
 - * @package     ActiveSync
 - * @subpackage  Controller
 + * @package     Calendar
 + * @subpackage  Frontend
   */
 -class ActiveSync_Controller_Calendar extends ActiveSync_Controller_Abstract implements Syncroton_Data_IDataCalendar
 +class Calendar_Frontend_ActiveSync extends ActiveSync_Frontend_Abstract implements Syncroton_Data_IDataCalendar
  {
      /**
       * available filters
          'endTime'           => 'dtend',
          'location'          => 'location',
          'reminder'          => 'alarms',
 -        //'Sensitivity'       => 'class',
 +        'sensitivity'       => 'class',
          'subject'           => 'summary',
          'body'              => 'description',
          'startTime'         => 'dtstart',
          'samsunggtn7000', // Samsung Galaxy Note 
          'samsunggti9300', // Samsung Galaxy S-3
      );
-     
+     /**
+      * folder id which is currenttly synced
+      *
+      * @var string
+      */
+     protected $_syncFolderId = null;
      /**
       * (non-PHPdoc)
 -     * @see ActiveSync_Controller_Abstract::__construct()
 +     * @see ActiveSync_Frontend_Abstract::__construct()
       */
      public function __construct(Syncroton_Model_IDevice $_device, DateTime $_syncTimeStamp)
      {
      
      /**
       * (non-PHPdoc)
 -     * @see ActiveSync_Controller_Abstract::toSyncrotonModel()
 +     * @see ActiveSync_Frontend_Abstract::toSyncrotonModel()
       * @todo handle BusyStatus
       */
      public function toSyncrotonModel($entry, array $options = array())
                      
                  case 'attendee':
                      if ($this->_device->devicetype === Syncroton_Model_Device::TYPE_IPHONE &&
-                         $entry->container_id       !== $this->_getDefaultContainerId()) {
+                         $this->_syncFolderId       !== $this->_getDefaultContainerId()) {
                          
                          continue;
                      }
                      
                      break;
                      
 +                case 'class':
 +                    $syncrotonEvent->$syncrotonProperty = $entry->$tine20Property == Calendar_Model_Event::CLASS_PRIVATE ? 2 : 0;
 +                    
 +                    break;
 +                    
                  case 'description':
                      $syncrotonEvent->$syncrotonProperty = new Syncroton_Model_EmailBody(array(
                          'type' => Syncroton_Model_EmailBody::TYPE_PLAINTEXT,
                          'data' => $entry->$tine20Property
                      ));
 -                
 +                    
                      break;
 -                
 +                    
                  case 'dtend':
                      if($entry->$tine20Property instanceof DateTime) {
                          if ($entry->is_all_day_event == true) {
              Tinebase_Core::get(Tinebase_Core::CACHE)
          );
          
 -        $syncrotonEvent->timezone = $timeZoneConverter->encodeTimezone(Tinebase_Core::get(Tinebase_Core::USERTIMEZONE));
 +        $syncrotonEvent->timezone = $timeZoneConverter->encodeTimezone(Tinebase_Core::getUserTimezone());
          
          $syncrotonEvent->meetingStatus = 1;
 -        $syncrotonEvent->sensitivity = 0;
          $syncrotonEvent->dtStamp = $entry->creation_time;
          $syncrotonEvent->uID = $entry->uid;
          
      
      /**
       * (non-PHPdoc)
 -     * @see ActiveSync_Controller_Abstract::toTineModel()
 +     * @see ActiveSync_Frontend_Abstract::toTineModel()
       */
      public function toTineModel(Syncroton_Model_IEntry $data, $entry = null)
      {
                  } else {
                      if ($tine20Property === 'attendee' && $entry &&
                          $this->_device->devicetype === Syncroton_Model_Device::TYPE_IPHONE &&
-                         $entry->container_id       !== $this->_getDefaultContainerId()) {
+                         $this->_syncFolderId       !== $this->_getDefaultContainerId()) {
                              // keep attendees as the are / they were not sent to the device before
                      } else {
                          if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(
                  case 'attendee':
                      if ($entry && 
                          $this->_device->devicetype === Syncroton_Model_Device::TYPE_IPHONE &&
-                         $entry->container_id       !== $this->_getDefaultContainerId()) {
+                         $this->_syncFolderId       !== $this->_getDefaultContainerId()) {
                              // keep attendees as the are / they were not sent to the device before
                              continue;
                      }
                      Calendar_Model_Attender::emailsToAttendee($event, $newAttendees);
                      
                      break;
 -
 +                    
 +                case 'class':
 +                    $event->$tine20Property = $data->$syncrotonProperty == 2 ? Calendar_Model_Event::CLASS_PRIVATE : Calendar_Model_Event::CLASS_PUBLIC;
 +                    
 +                    break;
 +                    
                  case 'exdate':
                      // handle exceptions from recurrence
                      $exdates = new Tinebase_Record_RecordSet('Calendar_Model_Event');
              try {
                  $timezone = $timeZoneConverter->getTimezone(
                      $data->timezone,
 -                    Tinebase_Core::get(Tinebase_Core::USERTIMEZONE)
 +                    Tinebase_Core::getUserTimezone()
                  );
                  $event->originator_tz = $timezone;
              } catch (ActiveSync_TimezoneNotFoundException $e) {
                  Tinebase_Core::getLogger()->crit(__METHOD__ . '::' . __LINE__ . " timezone data not found " . $data->timezone);
 -                $event->originator_tz = Tinebase_Core::get(Tinebase_Core::USERTIMEZONE);
 +                $event->originator_tz = Tinebase_Core::getUserTimezone();
              }
          
              if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
              $containerId = Tinebase_Core::getPreference('ActiveSync')->{$this->_defaultFolder};
              $container = Tinebase_Container::getInstance()->getContainerById($containerId);
          }
+         $this->_syncFolderId = $container->getId();
          Calendar_Controller_MSEventFacade::getInstance()->assertEventFacadeParams($container, false);
      }
  }
@@@ -171,283 -171,86 +171,301 @@@ class Calendar_Setup_Update_Release8 ex
       */
      public function update_3()
      {
 -        $declaration = new Setup_Backend_Schema_Field_Xml('
 -            <field>
 -                <name>etag</name>
 -                <type>text</type>
 -                <length>60</length>
 -            </field>');
 -        $this->_backend->addCol('cal_events', $declaration);
 -        
 -        $declaration = new Setup_Backend_Schema_Index_Xml('
 -            <index>
 -                <name>etag</name>
 +        if (! $this->_backend->columnExists('etag', 'cal_events')) {
 +            $declaration = new Setup_Backend_Schema_Field_Xml('
                  <field>
                      <name>etag</name>
 -                </field>
 -            </index>');
 -        $this->_backend->addIndex('cal_events', $declaration);
 +                    <type>text</type>
 +                    <length>60</length>
 +                </field>');
 +            $this->_backend->addCol('cal_events', $declaration);
 +
 +            $declaration = new Setup_Backend_Schema_Index_Xml('
 +                <index>
 +                    <name>etag</name>
 +                    <field>
 +                        <name>etag</name>
 +                    </field>
 +                </index>');
 +            $this->_backend->addIndex('cal_events', $declaration);
 +        }
 +        
          $this->setTableVersion('cal_events', 7);
          $this->setApplicationVersion('Calendar', '8.4');
      }
      
      /**
 +     * - update import / export
 +     */
 +    public function update_4()
 +    {
 +        Setup_Controller::getInstance()->createImportExportDefinitions(Tinebase_Application::getInstance()->getApplicationByName('Calendar'));
 +        $this->setTableVersion('cal_events', 7);
 +        $this->setApplicationVersion('Calendar', '8.5');
 +    }
 +    
 +    /**
       * adds external_seq col
       * 
       * @see 0009890: improve external event invitation support
       */
 -    public function update_4()
 +    public function update_5()
      {
 -        $seqCol = '<field>
 -            <name>external_seq</name>
 -            <type>integer</type>
 -            <notnull>true</notnull>
 -            <default>0</default>
 -        </field>';
 -        
 -        $declaration = new Setup_Backend_Schema_Field_Xml($seqCol);
 -        $this->_backend->addCol('cal_events', $declaration);
 +        if (! $this->_backend->columnExists('external_seq', 'cal_events')) {
 +            $seqCol = '<field>
 +                <name>external_seq</name>
 +                <type>integer</type>
 +                <notnull>true</notnull>
 +                <default>0</default>
 +            </field>';
 +            
 +            $declaration = new Setup_Backend_Schema_Field_Xml($seqCol);
 +            $this->_backend->addCol('cal_events', $declaration);
 +        }
          
 -        $this->setTableVersion('cal_events', 8);
 -        $this->setApplicationVersion('Calendar', '8.5');
 +        $this->setTableVersion('cal_events', '8');
 +        $this->setApplicationVersion('Calendar', '8.6');
      }
      
      /**
       * add rrule index
       * 
       * @see 0010214: improve calendar performance / yearly base events
 +     *
 +     * TODO re-enable this when it is fixed for postgresql
 +     * @see 0011194: only drop index if it exists
       */
 -    public function update_5()
 +    public function update_6()
 +    {
 +//        $declaration = new Setup_Backend_Schema_Index_Xml('
 +//            <index>
 +//                <name>rrule</name>
 +//                <field>
 +//                    <name>rrule</name>
 +//                </field>
 +//            </index>');
 +//        try {
 +//            $this->_backend->addIndex('cal_events', $declaration);
 +//        } catch (Zend_Db_Statement_Exception $e) {
 +//            Tinebase_Exception::log($e);
 +//        }
 +        
 +        $this->setTableVersion('cal_events', '9');
 +        $this->setApplicationVersion('Calendar', '8.7');
 +    }
 +
 +    /**
 +     * repair missing displaycontainer_id
 +     */
 +    public function update_7()
      {
 +        $allUser = $this->_db->query(
 +            "SELECT " . $this->_db->quoteIdentifier('id') . "," . $this->_db->quoteIdentifier('contact_id') .
 +            " FROM " . $this->_db->quoteIdentifier(SQL_TABLE_PREFIX . "accounts") .
 +            " WHERE " . $this->_db->quoteIdentifier("contact_id") . " IS NOT NULL"
 +        )->fetchAll(Zend_Db::FETCH_ASSOC);
 +
 +        $contactUserMap = array();
 +        foreach ($allUser as $id => $user) {
 +            $contactUserMap[$user['contact_id']] = $user['id'];
 +        }
 +
 +        // find all user/groupmember attendees with missing displaycontainer
 +        $attendees = $this->_db->query(
 +            "SELECT DISTINCT" . $this->_db->quoteIdentifier('user_type') . "," . $this->_db->quoteIdentifier('user_id') .
 +            " FROM " . $this->_db->quoteIdentifier(SQL_TABLE_PREFIX . "cal_attendee") .
 +            " WHERE " . $this->_db->quoteIdentifier("displaycontainer_id") . " IS  NULL" .
 +            "  AND " . $this->_db->quoteIdentifier("user_type") . $this->_db->quoteInto(" IN (?)", array('user', 'groupmemeber')) .
 +            "  AND " . $this->_db->quoteIdentifier("user_id") . $this->_db->quoteInto(" IN (?)", array_keys($contactUserMap))
 +        )->fetchAll(Zend_Db::FETCH_ASSOC);
 +
 +        // find all user/groupmember attendees with missing displaycontainer
 +        $attendees = array_merge($attendees, $this->_db->query(
 +            "SELECT DISTINCT" . $this->_db->quoteIdentifier('user_type') . "," . $this->_db->quoteIdentifier('user_id') .
 +            " FROM " . $this->_db->quoteIdentifier(SQL_TABLE_PREFIX . "cal_attendee") .
 +            " WHERE " . $this->_db->quoteIdentifier("displaycontainer_id") . " IS  NULL" .
 +            "  AND " . $this->_db->quoteIdentifier("user_type") . $this->_db->quoteInto(" IN (?)", array('resource'))
 +        )->fetchAll(Zend_Db::FETCH_ASSOC));
 +
 +        $resources = $this->_db->query(
 +            "SELECT " . $this->_db->quoteIdentifier('id') . "," . $this->_db->quoteIdentifier('container_id') .
 +            " FROM " . $this->_db->quoteIdentifier(SQL_TABLE_PREFIX . "cal_resources")
 +        )->fetchAll(Zend_Db::FETCH_ASSOC);
 +
 +        $resourceContainerMap = array();
 +        foreach ($resources as $resource) {
 +            $resourceContainerMap[$resource['id']] = $resource['container_id'];
 +        }
 +
 +        foreach ($attendees as $attendee) {
 +            //find out displaycontainer
 +            if ($attendee['user_type'] != 'resource') {
 +                $userAccountId = $contactUserMap[$attendee['user_id']];
 +                try {
 +                    $attendee['displaycontainerId'] = Calendar_Controller_Event::getDefaultDisplayContainerId($userAccountId);
 +                } catch (Tinebase_Exception_NotFound $tenf) {
 +                    Setup_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . " Could not find user with id " . $attendee['user_id']);
 +                    continue;
 +                }
 +            } else {
 +                $attendee['displaycontainerId'] = $resourceContainerMap[$attendee['user_id']];
 +            }
 +
 +            // update displaycontainer
 +            $this->_db->query(
 +                "UPDATE" . $this->_db->quoteIdentifier(SQL_TABLE_PREFIX . "cal_attendee") .
 +                " SET " . $this->_db->quoteIdentifier("displaycontainer_id") . " = " . $this->_db->quote($attendee['displaycontainerId']) .
 +                " WHERE " . $this->_db->quoteIdentifier("user_type") . " = " . $this->_db->quote($attendee['user_type']) .
 +                "  AND " . $this->_db->quoteIdentifier("user_id") . " = " . $this->_db->quote($attendee['user_id'])
 +            );
 +        }
 +
 +        $this->setApplicationVersion('Calendar', '8.8');
 +    }
 +
 +    /**
 +     * identify base event via new base_event_id field instead of UID
 +     */
 +    public function update_8()
 +    {
 +        /* find possibly broken events
 +         SELECT group_concat(id), uid, count(id) as cnt from tine20_cal_events
 +             WHERE rrule IS NOT NULL
 +             GROUP BY uid
 +             HAVING cnt > 1;
 +         */
 +
 +        $declaration = new Setup_Backend_Schema_Field_Xml('
 +            <field>
 +                <name>base_event_id</name>
 +                <type>text</type>
 +                <length>40</length>
 +            </field>');
 +        $this->_backend->addCol('cal_events', $declaration);
 +
          $declaration = new Setup_Backend_Schema_Index_Xml('
              <index>
 -                <name>rrule</name>
 +                <name>base_event_id</name>
                  <field>
 -                    <name>rrule</name>
 +                    <name>base_event_id</name>
                  </field>
              </index>');
 -        try {
 -            $this->_backend->addIndex('cal_events', $declaration);
 -        } catch (Zend_Db_Statement_Exception $e) {
 -            Tinebase_Exception::log($e);
 +        $this->_backend->addIndex('cal_events', $declaration);
 +
 +        // find all events with rrule
 +        $events = $this->_db->query(
 +            "SELECT " . $this->_db->quoteIdentifier('id') .
 +                 ', ' . $this->_db->quoteIdentifier('uid') .
 +                 ', ' . $this->_db->quoteIdentifier('container_id') .
 +                 ', ' . $this->_db->quoteIdentifier('created_by') .
 +            " FROM " . $this->_db->quoteIdentifier(SQL_TABLE_PREFIX . "cal_events") .
 +            " WHERE " . $this->_db->quoteIdentifier("rrule") . " IS NOT NULL" .
 +              " AND " . $this->_db->quoteIdentifier("is_deleted") . " = " . $this->_db->quote(0, Zend_Db::INT_TYPE)
 +        )->fetchAll(Zend_Db::FETCH_ASSOC);
 +
 +        // update all exdates in same container
 +        foreach($events as $event) {
 +            $this->_db->query(
 +                "UPDATE " . $this->_db->quoteIdentifier(SQL_TABLE_PREFIX . "cal_events") .
 +                  " SET " . $this->_db->quoteIdentifier('base_event_id') . ' = ' . $this->_db->quote($event['id']) .
 +                " WHERE " . $this->_db->quoteIdentifier('uid') . ' = ' . $this->_db->quote($event['uid']) .
 +                  " AND " . $this->_db->quoteIdentifier("container_id") . ' = ' . $this->_db->quote($event['container_id']) .
 +                  " AND " . $this->_db->quoteIdentifier("recurid") . " IS NOT NULL" .
 +                  " AND " . $this->_db->quoteIdentifier("is_deleted") . " = " . $this->_db->quote(0, Zend_Db::INT_TYPE)
 +            );
          }
 -        
 -        $this->setTableVersion('cal_events', '9');
 -        $this->setApplicationVersion('Calendar', '8.6');
 +
 +        // find all container move exdates
 +        $danglingExdates = $this->_db->query(
 +            "SELECT " . $this->_db->quoteIdentifier('uid') .
 +                ', ' . $this->_db->quoteIdentifier('id') .
 +                ', ' . $this->_db->quoteIdentifier('created_by') .
 +            " FROM " . $this->_db->quoteIdentifier(SQL_TABLE_PREFIX . "cal_events") .
 +            " WHERE " . $this->_db->quoteIdentifier("recurid") . " IS NOT NULL" .
 +              " AND " . $this->_db->quoteIdentifier("base_event_id") . " IS NULL" .
 +              " AND " . $this->_db->quoteIdentifier("is_deleted") . " = " . $this->_db->quote(0, Zend_Db::INT_TYPE)
 +        )->fetchAll(Zend_Db::FETCH_ASSOC);
 +
 +        // try to match by creator
 +        foreach ($danglingExdates as $exdate) {
 +            $possibleBaseEvents = array();
 +            $matches = array_filter($events, function ($event) use ($exdate, $possibleBaseEvents) {
 +                if ($event['uid'] == $exdate['uid']) {
 +                    $possibleBaseEvents[] = $event;
 +                    return $event['created_by'] == $exdate['created_by'];
 +                }
 +                return false;
 +            });
 +
 +            switch(count($matches)) {
 +                case 0:
 +                    // no match :-(
 +                    if (count($possibleBaseEvents) == 0) {
 +                        // garbage? exdate without any base event
 +                        Setup_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . " dangling exdate with id {$exdate['id']}");
 +                        continue 2;
 +                    }
 +                    Setup_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . " no match for exdate with id {$exdate['id']}");
 +                    $baseEvent = current($possibleBaseEvents);
 +                    break;
 +                case 1:
 +                    // exact match :-)
 +                    $baseEvent = current($matches);
 +                    break;
 +                default:
 +                    // to much matches :-(
 +                    Setup_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . " multiple matches for exdate with id {$exdate['id']}");
 +                    $baseEvent = current($matches);
 +            }
 +
 +            $this->_db->query(
 +                "UPDATE " . $this->_db->quoteIdentifier(SQL_TABLE_PREFIX . "cal_events") .
 +                " SET " . $this->_db->quoteIdentifier('base_event_id') . ' = ' . $this->_db->quote($baseEvent['id']) .
 +                " WHERE " . $this->_db->quoteIdentifier('id') . ' = ' . $this->_db->quote($exdate['id'])
 +            );
 +        }
 +
 +        $this->setTableVersion('cal_events', '10');
 +        $this->setApplicationVersion('Calendar', '8.9');
 +    }
 +
 +    /**
 +     * @see 0011266: increase size of event fields summary and location
 +     */
 +    public function update_9()
 +    {
 +        $fieldsToChange = array('location', 'summary');
 +
 +        foreach ($fieldsToChange as $name) {
 +            $seqCol = '<field>
 +                <name>' . $name . '</name>
 +                <type>text</type>
 +                <length>1024</length>
 +            </field>';
 +
 +            $declaration = new Setup_Backend_Schema_Field_Xml($seqCol);
 +            $this->_backend->alterCol('cal_events', $declaration);
 +        }
 +
 +        $this->setTableVersion('cal_events', 11);
 +        $this->setApplicationVersion('Calendar', '8.10');
      }
 -    public function update_6()
+     /**
+      * force activesync calendar resync for iOS devices
+      */
 -        $this->setApplicationVersion('Calendar', '8.7');
++    public function update_10()
+     {
+         $deviceBackend = new ActiveSync_Backend_Device();
+         $usersWithiPhones = $deviceBackend->search(new ActiveSync_Model_DeviceFilter(array(
+             'devicetype' => 'iphone'
+         )), NULL, 'owner_id');
+         $activeSyncController = ActiveSync_Controller::getInstance();
+         foreach($usersWithiPhones as $userId) {
+             $activeSyncController->resetSyncForUser($userId, 'Calendar');
+         }
++        $this->setApplicationVersion('Calendar', '8.11');
+     }
  }
@@@ -2,14 -2,14 +2,14 @@@
  <application>
      <name>Calendar</name>
      <!-- gettext('Calendar') -->   
-     <version>8.10</version>
 -    <version>8.7</version>
++    <version>8.11</version>
      <order>15</order>
      <status>enabled</status>
      <tables>
          <!-- events -->
          <table>
              <name>cal_events</name>
 -            <version>8</version>
 +            <version>11</version>
              <declaration>
                  <field>
                      <name>id</name>
@@@ -23,8 -23,8 +23,8 @@@
                  </field>
                  <field>
                      <name>created_by</name>
 -                      <type>text</type>
 -                      <length>40</length>
 +                    <type>text</type>
 +                    <length>40</length>
                  </field>
                  <field>
                      <name>creation_time</name>
                  <field>
                      <name>location</name>
                      <type>text</type>
 -                    <length>255</length>
 +                    <length>1024</length>
                  </field>
                      <field>
                      <name>organizer</name>
 -                      <type>text</type>
 -                      <length>40</length>
 +                    <type>text</type>
 +                    <length>40</length>
                  </field>
                  <field>
                      <name>priority</name>
                  <field>
                      <name>summary</name>
                      <type>text</type>
 -                    <length>255</length>
 +                    <length>1024</length>
                      <notnull>false</notnull>
                  </field>
                  <field>
                      <length>255</length>
                  </field>
                  <field>
 +                    <name>base_event_id</name>
 +                    <type>text</type>
 +                    <length>40</length>
 +                </field>
 +                <field>
                      <name>rrule</name>
                      <type>text</type>
                      <length>255</length>
                      </field>
                  </index>
                  <index>
 +                    <name>base_event_id</name>
 +                    <field>
 +                        <name>base_event_id</name>
 +                    </field>
 +                </index>
 +                <index>
                      <name>etag</name>
                      <field>
                          <name>etag</name>
                  </index>
              </declaration>
          </table>
 -        
 -        
      </tables>
  </application>