Merge remote-tracking branch 'refs/remotes/gerrit/pu/cal2012' into master
authorCornelius Weiß <mail@corneliusweiss.de>
Wed, 12 Sep 2012 07:59:17 +0000 (09:59 +0200)
committerCornelius Weiß <mail@corneliusweiss.de>
Wed, 12 Sep 2012 07:59:17 +0000 (09:59 +0200)
Conflicts:
tests/tine20/ActiveSync/Controller/CalendarTests.php
tine20/ActiveSync/Controller/Calendar.php

1  2 
tests/tine20/ActiveSync/Controller/CalendarTests.php
tests/tine20/Calendar/Controller/EventTests.php
tests/tine20/Calendar/Frontend/WebDAV/EventTest.php
tine20/ActiveSync/Controller/Calendar.php
tine20/Calendar/Controller/Event.php
tine20/Calendar/js/GridView.js
tine20/Tinebase/Model/Filter/FilterGroup.php

@@@ -226,165 -71,422 +226,188 @@@ Zeile 3</AirSyncBase:Data
          
          // replace email to make current user organizer and attendee
          $this->_testXMLInput = str_replace('lars@kneschke.de', Tinebase_Core::getUser()->accountEmailAddress, $this->_testXMLInput);
 -        
 -        $event = new Calendar_Model_Event(array(
 -            'uid'           => Tinebase_Record_Abstract::generateUID(),
 -            'summary'       => 'SyncTest',
 -            'dtstart'       => Tinebase_DateTime::now()->addMonth(1)->toString(Tinebase_Record_Abstract::ISO8601LONG), //'2009-04-25 18:00:00',
 -            'dtend'         => Tinebase_DateTime::now()->addMonth(1)->addHour(1)->toString(Tinebase_Record_Abstract::ISO8601LONG), //'2009-04-25 18:30:00',
 -            'originator_tz' => 'Europe/Berlin',
 -            'container_id'  => $this->_getContainerWithSyncGrant()->getId(),
 -            Tinebase_Model_Grants::GRANT_EDIT     => true,
 -            'attendee'      => new Tinebase_Record_RecordSet('Calendar_Model_Attender', array(
 -                array(
 -                    'user_id' => Tinebase_Core::getUser()->contact_id,
 -                    'user_type' => Calendar_Model_Attender::USERTYPE_USER,
 -                    'status' => Calendar_Model_Attender::STATUS_ACCEPTED
 -                )
 -            ))
 -        ));
 -        
 -        $event = Calendar_Controller_Event::getInstance()->create($event);
 -        
 -        $this->objects['event'] = $event;
 -        
 -        $event2MonthsBack = new Calendar_Model_Event(array(
 -            'uid'           => Tinebase_Record_Abstract::generateUID(),
 -            'summary'       => 'SyncTest',
 -            'dtstart'       => Tinebase_DateTime::now()->subMonth(2)->toString(Tinebase_Record_Abstract::ISO8601LONG), //'2009-04-25 18:00:00',
 -            'dtend'         => Tinebase_DateTime::now()->subMonth(2)->addHour(1)->toString(Tinebase_Record_Abstract::ISO8601LONG), //'2009-04-25 18:30:00',
 -            'originator_tz' => 'Europe/Berlin',
 -            'container_id'  => $this->_getContainerWithSyncGrant()->getId(),
 -            Tinebase_Model_Grants::GRANT_EDIT     => true,
 -        ));
 -        
 -        $event = Calendar_Controller_Event::getInstance()->create($event2MonthsBack);
 -        
 -        $this->objects['event2MonthsBack'] = $event;
 -        
 -        $eventDaily = new Calendar_Model_Event(array(
 -            'uid'           => Tinebase_Record_Abstract::generateUID(),
 -            'summary'       => 'SyncTest',
 -            'dtstart'       => Tinebase_DateTime::now()->addMonth(1)->toString(Tinebase_Record_Abstract::ISO8601LONG), //'2009-05-25 18:00:00',
 -            'dtend'         => Tinebase_DateTime::now()->addMonth(1)->addHour(1)->toString(Tinebase_Record_Abstract::ISO8601LONG), //'2009-05-25 19:00:00',
 -            'originator_tz' => 'Europe/Berlin',
 -            'rrule'         => 'FREQ=DAILY;INTERVAL=1;UNTIL=' . Tinebase_DateTime::now()->addMonth(1)->addDay(6)->setHour(22)->setMinute(59)->setSecond(59)->toString(Tinebase_Record_Abstract::ISO8601LONG), //2009-05-31 22:59:59',
 -            'container_id'  => $this->_getContainerWithSyncGrant()->getId(),
 -            Tinebase_Model_Grants::GRANT_EDIT     => true,
 -            'attendee'      => new Tinebase_Record_RecordSet('Calendar_Model_Attender', array(
 -                array(
 -                    'user_id' => Tinebase_Core::getUser()->contact_id,
 -                    'user_type' => Calendar_Model_Attender::USERTYPE_USER,
 -                    'status' => Calendar_Model_Attender::STATUS_ACCEPTED
 -                )
 -            ))
 -        ));
 -                
 -        $eventDaily = Calendar_Controller_Event::getInstance()->create($eventDaily);
 -        
 -        // compute recurset
 -        $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($eventDaily, new Tinebase_Record_RecordSet('Calendar_Model_Event'), $eventDaily->dtstart, $eventDaily->rrule_until);
 -        
 -        // first deleted instance
 -        $updatedBaseEvent = Calendar_Controller_Event::getInstance()->getRecurBaseEvent($recurSet[0]);
 -        $recurSet[0]->last_modified_time = $updatedBaseEvent->last_modified_time;
 -        Calendar_Controller_Event::getInstance()->createRecurException($recurSet[0], true);
 -        
 -        // second deleted instance
 -        $updatedBaseEvent = Calendar_Controller_Event::getInstance()->getRecurBaseEvent($recurSet[1]);
 -        $recurSet[1]->last_modified_time = $updatedBaseEvent->last_modified_time;
 -        Calendar_Controller_Event::getInstance()->createRecurException($recurSet[1], true);
 -        
 -        // first exception instance
 -        $recurSet[2]->dtstart->addHour(2);
 -        $recurSet[2]->dtend->addHour(2);
 -        $recurSet[2]->summary = 'Test Exception 1';
 -        $updatedBaseEvent = Calendar_Controller_Event::getInstance()->getRecurBaseEvent($recurSet[2]);
 -        $recurSet[2]->last_modified_time = $updatedBaseEvent->last_modified_time;
 -        Calendar_Controller_Event::getInstance()->createRecurException($recurSet[2]);
 -        
 -        // first exception instance
 -        $recurSet[3]->dtstart->addHour(3);
 -        $recurSet[3]->dtend->addHour(3);
 -        $recurSet[3]->summary = 'Test Exception 2';
 -        $updatedBaseEvent = Calendar_Controller_Event::getInstance()->getRecurBaseEvent($recurSet[3]);
 -        $recurSet[3]->last_modified_time = $updatedBaseEvent->last_modified_time;
 -        Calendar_Controller_Event::getInstance()->createRecurException($recurSet[3]);
 -        
 -        // reread event from database again
 -        $eventDaily = Calendar_Controller_Event::getInstance()->get($eventDaily);
 -        #var_dump($eventDaily->toArray());
 -        
 -        $this->objects['eventDaily'] = $eventDaily;
 -        
 -        Tinebase_Core::getPreference('ActiveSync')->setValue(ActiveSync_Preference::DEFAULTCALENDAR, $this->_getContainerWithSyncGrant()->getId());
 -        
 -        ########### define test filter
 -        $filterBackend = new Tinebase_PersistentFilter_Backend_Sql();
 -        
 -        ########### define test devices
 -        $palm = ActiveSync_Backend_DeviceTests::getTestDevice(Syncope_Model_Device::TYPE_WEBOS);
 -        $palm->owner_id     = $this->_testUser->getId();
 -        $this->objects['deviceWebOS']   = ActiveSync_Controller_Device::getInstance()->create($palm);
 -        
 -        $iphone = ActiveSync_Backend_DeviceTests::getTestDevice(Syncope_Model_Device::TYPE_IPHONE);
 -        $iphone->owner_id   = $this->_testUser->getId();
 -        $this->objects['deviceIPhone'] = ActiveSync_Controller_Device::getInstance()->create($iphone);
 -    }
 -
 -    /**
 -     * test xml generation for IPhone
 -     */
 -    public function testAppendXml()
 -    {
 -        $imp                   = new DOMImplementation();
 -        
 -        $dtd                   = $imp->createDocumentType('AirSync', "-//AIRSYNC//DTD AirSync//EN", "http://www.microsoft.com/");
 -        $testDom               = $imp->createDocument('uri:AirSync', 'Sync', $dtd);
 -        $testDom->formatOutput = true;
 -        $testDom->encoding     = 'utf-8';
 -        
 -        $collections    = $testDom->documentElement->appendChild($testDom->createElementNS('uri:AirSync', 'Collections'));
 -        $collection     = $collections->appendChild($testDom->createElementNS('uri:AirSync', 'Collection'));
 -        $commands       = $collection->appendChild($testDom->createElementNS('uri:AirSync', 'Commands'));
 -        $add            = $commands->appendChild($testDom->createElementNS('uri:AirSync', 'Add'));
 -        $appData        = $add->appendChild($testDom->createElementNS('uri:AirSync', 'ApplicationData'));
 -        
 -        
 -        $controller = new ActiveSync_Controller_Calendar($this->objects['deviceIPhone'], new Tinebase_DateTime(null, null, 'de_DE'));
 -        
 -        $controller->appendXML($appData, null, $this->objects['event']->getId(), array());
 -        
 -        // namespace === uri:Calendar
 -        $endTime = $this->objects['event']->dtend->format("Ymd\THis") . 'Z';
 -        $this->assertEquals($endTime, @$testDom->getElementsByTagNameNS('uri:Calendar', 'EndTime')->item(0)->nodeValue, $testDom->saveXML());
 -        $this->assertEquals($this->objects['event']->uid, @$testDom->getElementsByTagNameNS('uri:Calendar', 'UID')->item(0)->nodeValue, $testDom->saveXML());
 -        
 -        // try to encode XML until we have wbxml tests
 -        $outputStream = fopen("php://temp", 'r+');
 -        $encoder = new Wbxml_Encoder($outputStream, 'UTF-8', 3);
 -        $encoder->encode($testDom);
      }
      
 -    /**
 -     * testAppendXml_allDayEvent
 -     */
 -    public function testAppendXml_allDayEvent()
 +    public function testCreateEntry($syncrotonFolder = null)
      {
 -        $startDate = Tinebase_DateTime::now()->setTime(0,0,0)->addMonth(1);
 -        $endDate   = Tinebase_DateTime::now()->setTime(23,59,59)->addMonth(1)->addDay(3);
 -        
 -        $allDayEvent = new Calendar_Model_Event(array(
 -            'summary'       => 'Allday SyncTest',
 -            'dtstart'       => $startDate->toString(Tinebase_Record_Abstract::ISO8601LONG), //'2009-04-25 18:00:00'
 -            'dtend'         => $endDate->toString(Tinebase_Record_Abstract::ISO8601LONG),   //'2009-04-25 23:59:59'
 -            'is_all_day_event' => true,
 -            'originator_tz' => 'Europe/Berlin',
 -            'rrule'         => 'FREQ=DAILY;INTERVAL=1;UNTIL=' . Tinebase_DateTime::now()->addMonth(1)->addDay(6)->setHour(22)->setMinute(59)->setSecond(59)->toString(Tinebase_Record_Abstract::ISO8601LONG),
 -            'container_id'  => $this->_getContainerWithSyncGrant()->getId(),
 -            Tinebase_Model_Grants::GRANT_EDIT     => true,
 -        ));
 -        $allDayEvent = Calendar_Controller_Event::getInstance()->create($allDayEvent);
 -        $this->objects['events']['allDayEvent'] = $allDayEvent;
 -        
 -        $dom     = $this->_getOutputDOMDocument();
 -        $appData = $dom->getElementsByTagNameNS('uri:AirSync', 'ApplicationData')->item(0);
 -
 -        $controller = $this->_getController($this->_getDevice(Syncope_Model_Device::TYPE_WEBOS));
 -        
 -        $controller->appendXML($appData, null, $allDayEvent->getId(), array());
 -        
 -        #$dom->formatOutput = true; echo $dom->saveXML(); $dom->formatOutput = false;
 -        
 -        # ;'20110106T000000Z'
 -        $this->assertEquals($endDate->addSecond(1)->format('Ymd\THis\Z'), @$dom->getElementsByTagNameNS('uri:Calendar', 'EndTime')->item(0)->nodeValue, $dom->saveXML());
 -        // check that no Exceptions tag is set
 -        $this->assertEquals(0,                                            $dom->getElementsByTagNameNS('uri:Calendar', 'Exceptions')->length);
 -    }
 +        if ($syncrotonFolder === null) {
 +            $syncrotonFolder = $this->testCreateFolder();
 +        }
      
 -    /**
 -     * test xml generation for IPhone
 -     * 
 -     * FIXME fix this test! -> seems to fail depending on the current time / date 
 -     */
 -    public function testAppendXml_dailyEvent()
 -    {
 -        $imp                   = new DOMImplementation();
 -        
 -        $dtd                   = $imp->createDocumentType('AirSync', "-//AIRSYNC//DTD AirSync//EN", "http://www.microsoft.com/");
 -        $testDom               = $imp->createDocument('uri:AirSync', 'Sync', $dtd);
 -        $testDom->formatOutput = true;
 -        $testDom->encoding     = 'utf-8';
 -        $testDom->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/' ,'xmlns:Calendar', 'uri:Calendar');
 -        
 -        $collections    = $testDom->documentElement->appendChild($testDom->createElementNS('uri:AirSync', 'Collections'));
 -        $collection     = $collections->appendChild($testDom->createElementNS('uri:AirSync', 'Collection'));
 -        $commands       = $collection->appendChild($testDom->createElementNS('uri:AirSync', 'Commands'));
 -        $add            = $commands->appendChild($testDom->createElementNS('uri:AirSync', 'Add'));
 -        $appData        = $add->appendChild($testDom->createElementNS('uri:AirSync', 'ApplicationData'));
 -        
 -        
 -        $controller = new ActiveSync_Controller_Calendar($this->objects['deviceIPhone'], new Tinebase_DateTime());
 -        
 -        $controller->appendXML($appData, null, $this->objects['eventDaily']->getId(), array());
 -        
 -        // namespace === uri:Calendar
 -        $this->assertEquals(ActiveSync_Controller_Calendar::RECUR_TYPE_DAILY, @$testDom->getElementsByTagNameNS('uri:Calendar', 'Type')->item(0)->nodeValue, $testDom->saveXML());
 -        $this->assertEquals(4, @$testDom->getElementsByTagNameNS('uri:Calendar', 'Exception')->length, $testDom->saveXML());
 -        $this->assertEquals(4, @$testDom->getElementsByTagNameNS('uri:Calendar', 'ExceptionStartTime')->length, $testDom->saveXML());
 -        $this->assertEquals(3, @$testDom->getElementsByTagNameNS('uri:Calendar', 'Subject')->length, $testDom->saveXML());
 -        
 -        $endTime = $this->objects['eventDaily']->dtend->format("Ymd\THis") . 'Z';
 -        $this->assertEquals($endTime, @$testDom->getElementsByTagNameNS('uri:Calendar', 'EndTime')->item(0)->nodeValue, $testDom->saveXML());
 -        
 -        $untilTime = Calendar_Model_Rrule::getRruleFromString($this->objects['eventDaily']->rrule)->until->format("Ymd\THis") . 'Z';
 -        $this->assertEquals($untilTime, @$testDom->getElementsByTagNameNS('uri:Calendar', 'Until')->item(0)->nodeValue, $testDom->saveXML());
 -        
 -    }
 +        $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_WEBOS), new Tinebase_DateTime(null, null, 'de_DE'));
      
 -    public function testRecurEventExceptionFilters()
 -    {
 -        // decline one exception, remove attendee from other exception
 -        $persistentExceptions = Calendar_Controller_Event::getInstance()->getRecurExceptions($this->objects['eventDaily']);
 -        $persistentExceptions[0]->attendee[0]->status = Calendar_Model_Attender::STATUS_DECLINED;
 -        $persistentExceptions[1]->attendee = new Tinebase_Record_RecordSet('Calendar_Model_Attender');
 -        
 -        $updatedBaseEvent = Calendar_Controller_Event::getInstance()->update($persistentExceptions[0]);
 -        Calendar_Controller_Event::getInstance()->update($persistentExceptions[1]);
 -        
 -        $imp                   = new DOMImplementation();
 -        
 -        $dtd                   = $imp->createDocumentType('AirSync', "-//AIRSYNC//DTD AirSync//EN", "http://www.microsoft.com/");
 -        $testDom               = $imp->createDocument('uri:AirSync', 'Sync', $dtd);
 -        $testDom->formatOutput = true;
 -        $testDom->encoding     = 'utf-8';
 -        $testDom->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/' ,'xmlns:Calendar', 'uri:Calendar');
 -        
 -        $collections    = $testDom->documentElement->appendChild($testDom->createElementNS('uri:AirSync', 'Collections'));
 -        $collection     = $collections->appendChild($testDom->createElementNS('uri:AirSync', 'Collection'));
 -        $commands       = $collection->appendChild($testDom->createElementNS('uri:AirSync', 'Commands'));
 -        $add            = $commands->appendChild($testDom->createElementNS('uri:AirSync', 'Add'));
 -        $appData        = $add->appendChild($testDom->createElementNS('uri:AirSync', 'ApplicationData'));
 -        
 -        
 -        $controller = new ActiveSync_Controller_Calendar($this->objects['deviceIPhone'], new Tinebase_DateTime());
 -        
 -        $controller->appendXML($appData, null, $this->objects['eventDaily']->getId(), array());
 -        
 -        // all exceptions fall out (2 implicit fallouts by filter)
 -        $this->assertEquals(ActiveSync_Controller_Calendar::RECUR_TYPE_DAILY, @$testDom->getElementsByTagNameNS('uri:Calendar', 'Type')->item(0)->nodeValue, $testDom->saveXML());
 -        $this->assertEquals(4, @$testDom->getElementsByTagNameNS('uri:Calendar', 'Exception')->length, $testDom->saveXML());
 -        $this->assertEquals(4, @$testDom->getElementsByTagNameNS('uri:Calendar', 'ExceptionStartTime')->length, $testDom->saveXML());
 -        $this->assertEquals(1, @$testDom->getElementsByTagNameNS('uri:Calendar', 'Subject')->length, $testDom->saveXML());
 -        
 -        //@TODO test way back -> no deltes but how?
 -//         $event = $controller->toTineModel(simplexml_import_dom($testDom), $this->objects['eventDaily']);
 +        $xml = new SimpleXMLElement($this->_testXMLInput);
 +        $syncrotonEvent = new Syncroton_Model_Event($xml->Collections->Collection->Commands->Change[0]->ApplicationData);
 +    
 +        $serverId = $controller->createEntry($syncrotonFolder->serverId, $syncrotonEvent);
 +    
 +        $syncrotonEvent = $controller->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => $syncrotonFolder->serverId)), $serverId);
 +    
 +        #echo '----------------' . PHP_EOL; foreach ($syncrotonEvent as $key => $value) {echo "$key => "; var_dump($value);}
 +        
 +        $this->assertEquals(0,         $syncrotonEvent->allDayEvent);
 +        $this->assertEquals(2,         $syncrotonEvent->busyStatus);
 +        $this->assertEquals('Repeat',  $syncrotonEvent->subject);
 +        $this->assertEquals(15,        $syncrotonEvent->reminder);
 +        $this->assertTrue($syncrotonEvent->endTime instanceof DateTime);
 +        $this->assertTrue($syncrotonEvent->startTime instanceof DateTime);
 +        $this->assertEquals('20121123T160000Z', $syncrotonEvent->endTime->format('Ymd\THis\Z'));
 +        $this->assertEquals('20121123T130000Z', $syncrotonEvent->startTime->format('Ymd\THis\Z'));
 +        $this->assertEquals(1, count($syncrotonEvent->attendees), 'event: ' . var_export($syncrotonEvent->attendees, TRUE));
 +        $this->assertEquals(Tinebase_Core::getUser()->accountEmailAddress, $syncrotonEvent->attendees[0]->email, 'event: ' . var_export($syncrotonEvent, TRUE));
 +        
 +        //Body
 +        $this->assertTrue($syncrotonEvent->body instanceof Syncroton_Model_EmailBody);
 +        $this->assertEquals('Hello', $syncrotonEvent->body->data);
 +        
 +        // Recurrence
 +        $this->assertTrue($syncrotonEvent->recurrence instanceof Syncroton_Model_EventRecurrence);
 +        $this->assertEquals(Syncroton_Model_EventRecurrence::TYPE_DAILY, $syncrotonEvent->recurrence->type);
 +        $this->assertEquals(1, $syncrotonEvent->recurrence->interval);
 +        $this->assertTrue($syncrotonEvent->recurrence->until instanceof DateTime);
 +        $this->assertEquals('20121128T225959Z', $syncrotonEvent->recurrence->until->format('Ymd\THis\Z'));
 +        
 +        // Exceptions
 +        $this->assertEquals(2, count($syncrotonEvent->exceptions));
 +        $this->assertTrue($syncrotonEvent->exceptions[0] instanceof Syncroton_Model_EventException);
 +        $this->assertEquals(0, $syncrotonEvent->exceptions[0]->deleted);
 +        $this->assertEquals('Repeat mal anders', $syncrotonEvent->exceptions[0]->subject);
 +        $this->assertEquals('20121125T130000Z', $syncrotonEvent->exceptions[0]->exceptionStartTime->format('Ymd\THis\Z'));
 +        $this->assertEquals('20121125T170000Z', $syncrotonEvent->exceptions[0]->endTime->format('Ymd\THis\Z'));
 +        $this->assertEquals('20121125T140000Z', $syncrotonEvent->exceptions[0]->startTime->format('Ymd\THis\Z'));
 +        
 +        $this->assertEquals(1, $syncrotonEvent->exceptions[1]->deleted);
 +        $this->assertEquals('20121124T130000Z', $syncrotonEvent->exceptions[1]->exceptionStartTime->format('Ymd\THis\Z'));
 +        
 +        return array($serverId, $syncrotonEvent);
      }
      
 -    /**
 -     * test update of record
 -     * 
 -     * @return Tinebase_Record_Abstract
 -     */
 -    public function testChangeEntryInBackend()
 +    public function testCreateEntryPalmPreV12($syncrotonFolder = null)
      {
 -        $recordId = $this->testAddEntryToBackend();
 -        
 -        $controller = $this->_getController($this->_getDevice(Syncope_Model_Device::TYPE_WEBOS));
 +        if ($syncrotonFolder === null) {
 +            $syncrotonFolder = $this->testCreateFolder();
 +        }
      
 -        $xml = simplexml_import_dom($this->_getInputDOMDocument());
 -        $recordId = $controller->change($this->_getContainerWithSyncGrant()->getId(), $recordId, $xml->Collections->Collection->Commands->Change[0]->ApplicationData);
 +        $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), new Tinebase_DateTime(null, null, 'de_DE'));
      
 -        $this->assertTrue(!empty($recordId));
 +        $xml = new SimpleXMLElement($this->_testXMLInput_palmPreV12);
 +        $syncrotonEvent = new Syncroton_Model_Event($xml->Collections->Collection->Commands->Change[0]->ApplicationData);
      
 -        return $recordId;
 -    }
 -        
 -    /**
 -     * test get list all record ids not older than 2 weeks back
 -     */
 -    public function testGetServerEntries2WeeksBack()
 -    {
 -        $controller = $this->_getController($this->_getDevice(Syncope_Model_Device::TYPE_WEBOS));
 +        $serverId = $controller->createEntry($syncrotonFolder->serverId, $syncrotonEvent);
      
 -        $records = $controller->getServerEntries($this->_specialFolderName, Syncope_Command_Sync::FILTER_2_WEEKS_BACK);
 -
 -        $this->assertNotContains($this->objects['event2MonthsBack']->getId(), $records, 'found event 2 months back');
 -    }
 +        $syncrotonEvent = $controller->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => $syncrotonFolder->serverId)), $serverId);
      
 -    /**
 -     * (non-PHPdoc)
 -     * @see ActiveSync/ActiveSync_TestCase::_validateGetServerEntries()
 -     */
 -    protected function _validateGetServerEntries($_recordId)
 -    {
 -        $controller = $this->_getController($this->_getDevice(Syncope_Model_Device::TYPE_WEBOS));
 -        $records = $controller->getServerEntries($this->_specialFolderName, Syncope_Command_Sync::FILTER_NOTHING);
 +        #echo '----------------' . PHP_EOL; foreach ($syncrotonEvent as $key => $value) {echo "$key => "; var_dump($value);}
          
 -        $this->assertContains($_recordId, $records, 'event not found');
 -    }
 +        $this->assertEquals(1,         $syncrotonEvent->allDayEvent);
 +        $this->assertTrue($syncrotonEvent->endTime instanceof DateTime);
 +        $this->assertTrue($syncrotonEvent->startTime instanceof DateTime);
 +        $this->assertEquals('20101106T230000Z', $syncrotonEvent->endTime->format('Ymd\THis\Z'));
 +        $this->assertEquals('20101103T230000Z', $syncrotonEvent->startTime->format('Ymd\THis\Z'));
      
 -    /**
 -     * test xml generation for IPhone
 -     * 
 -     * @see (attender check) 0006086: if new attender is added own attender is deleted / https://forge.tine20.org/mantisbt/view.php?id=6086
 -     */
 -    public function testConvertToTine20Model()
 -    {
 -        $event = $this->_createEvent($this->_testXMLInput);
 -        $this->assertEquals('2010-11-23 12:45:00', $event->alarms[0]->alarm_time->format(Tinebase_Record_Abstract::ISO8601LONG));
 -        $this->assertTrue(Calendar_Model_Attender::getOwnAttender($event->attendee) instanceof Calendar_Model_Attender);
 +        return array($serverId, $syncrotonEvent);
      }
      
 -    /**
 -     * create tine event from xml string
 -     * 
 -     * @param string $testXMLInput
 -     */
 -    protected function _createEvent($testXMLInput)
 +    public function testCreateEntryDailyRepeating($syncrotonFolder = null)
      {
 -        if (empty(Tinebase_Core::getUser()->accountEmailAddress)) {
 -            $this->markTestSkipped('current user has no email address');
 +        if ($syncrotonFolder === null) {
 +            $syncrotonFolder = $this->testCreateFolder();
          }
 -        
 -        $xml = simplexml_import_dom($this->_getInputDOMDocument($testXMLInput));
 -        
 -        $controller = $this->_getController($this->_getDevice(Syncope_Model_Device::TYPE_IPHONE));
 -        
 -        $event = $controller->toTineModel($xml->Collections->Collection->Commands->Change[0]->ApplicationData);
 -        
 -        $this->assertEquals('Repeat'             , $event->summary);
 -        $this->assertEquals(2                    , count($event->exdate));
 -        
 -        return $event;
 -    }
 -
 -    /**
 -    * test xml generation for IPhone (WithoutReminder)
 -    * 
 -    * @see 0006072: removing of event reminder does not work / https://forge.tine20.org/mantisbt/view.php?id=6072
 -    */
 -    public function testConvertToTine20ModelWithoutReminder()
 -    {
 -        $input = str_replace('<Calendar:Reminder>15</Calendar:Reminder>', '', $this->_testXMLInput);
 -        $event = $this->_createEvent($input);
      
 -        $this->assertEquals(0, count($event->alarms));
 -    }
 +        $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), new Tinebase_DateTime(null, null, 'de_DE'));
      
 -    /**
 -     * testConvertToTine20Model_fromPalm
 -     */
 -    public function testConvertToTine20Model_fromPalm()
 -    {
 -        $xml = simplexml_import_dom($this->_getInputDOMDocument($this->_testXMLInput_palmPreV12));
 -        
 -        $controller = $this->_getController($this->_getDevice(Syncope_Model_Device::TYPE_WEBOS));
 -        
 -        $event = $controller->toTineModel($xml->Collections->Collection->Commands->Change[0]->ApplicationData);
 +        $xml = new SimpleXMLElement($this->_testXMLInput_DailyRepeatingEvent);
 +        $syncrotonEvent = new Syncroton_Model_Event($xml->Collections->Collection->Commands->Add[0]->ApplicationData);
 +    
 +        $serverId = $controller->createEntry($syncrotonFolder->serverId, $syncrotonEvent);
 +    
 +        $syncrotonEvent = $controller->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => $syncrotonFolder->serverId)), $serverId);
 +    
 +        #echo '----------------' . PHP_EOL; foreach ($syncrotonEvent as $key => $value) {echo "$key => "; var_dump($value);}
          
 -        $this->assertEquals('2010-11-03 23:00:00', $event->dtstart->format(Tinebase_Record_Abstract::ISO8601LONG));
 -        $this->assertEquals('2010-11-06 22:59:59', $event->dtend->format(Tinebase_Record_Abstract::ISO8601LONG));
 -        $this->assertTrue(!!$event->is_all_day_event);
 -        $this->assertEquals("test beschreibung zeile 1\r\nZeile 2\r\nZeile 3", $event->description);
 +        $this->assertEquals(0, $syncrotonEvent->allDayEvent);
 +        $this->assertTrue($syncrotonEvent->endTime instanceof DateTime);
 +        $this->assertTrue($syncrotonEvent->startTime instanceof DateTime);
 +        $this->assertEquals('20101220T100000Z', $syncrotonEvent->endTime->format('Ymd\THis\Z'));
 +        $this->assertEquals('20101220T090000Z', $syncrotonEvent->startTime->format('Ymd\THis\Z'));
 +    
 +        // Recurrence
 +        $this->assertTrue($syncrotonEvent->recurrence instanceof Syncroton_Model_EventRecurrence);
 +        $this->assertEquals(Syncroton_Model_EventRecurrence::TYPE_DAILY, $syncrotonEvent->recurrence->type);
 +        $this->assertEquals(1, $syncrotonEvent->recurrence->interval);
 +        $this->assertTrue($syncrotonEvent->recurrence->until instanceof DateTime);
 +        $this->assertEquals('20101223T225959Z', $syncrotonEvent->recurrence->until->format('Ymd\THis\Z'));
 +        
 +        return array($serverId, $syncrotonEvent);
      }
      
 -    /**
 -     * testConvertToTine20Model_dailyRepeatingEvent
 -     */
 -    public function testConvertToTine20Model_dailyRepeatingEvent()
 +    public function testCreateEntrySamsungGalaxyStatus($syncrotonFolder = null)
      {
 -        $xml = simplexml_import_dom($this->_getInputDOMDocument($this->_testXMLInput_DailyRepeatingEvent));
 -        
 -        $controller = $this->_getController($this->_getDevice(Syncope_Model_Device::TYPE_WEBOS));
 +        if ($syncrotonFolder === null) {
 +            $syncrotonFolder = $this->testCreateFolder();
 +        }
 +    
 +        $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_SMASUNGGALAXYS2), new Tinebase_DateTime(null, null, 'de_DE'));
 +    
 +        $xml = new SimpleXMLElement($this->_testXMLInput_SamsungGalaxyStatus);
 +        $syncrotonEvent = new Syncroton_Model_Event($xml->Collections->Collection->Commands->Change[0]->ApplicationData);
 +    
 +        $serverId = $controller->createEntry($syncrotonFolder->serverId, $syncrotonEvent);
 +    
 +        $syncrotonEvent = $controller->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => $syncrotonFolder->serverId)), $serverId);
 +    
 +        #echo '----------------' . PHP_EOL; foreach ($syncrotonEvent as $key => $value) {echo "$key => "; var_dump($value);}
          
 -        $event = $controller->toTineModel($xml->Collections->Collection->Commands->Add[0]->ApplicationData);
 +        $this->assertEquals(Syncroton_Model_Event::BUSY_STATUS_BUSY, $syncrotonEvent->busyStatus);
          
 -        $this->assertEquals('2010-12-20 09:00:00', $event->dtstart->format(Tinebase_Record_Abstract::ISO8601LONG));
 -        $this->assertEquals('2010-12-20 10:00:00', $event->dtend->format(Tinebase_Record_Abstract::ISO8601LONG));
 -        $this->assertEquals('2010-12-23 22:59:59', $event->rrule->until->format(Tinebase_Record_Abstract::ISO8601LONG));
 +        return array($serverId, $syncrotonEvent);
      }
      
 -    /**
 -     * testConvertToTine20Model_samsungGalaxy
 -     * 
 -     * @see 0005896: Calendar sync problems with Galaxy-S2 / http://forge.tine20.org/mantisbt/view.php?id=5896
 -     */
 -    public function testConvertToTine20Model_samsungGalaxy()
 +    public function testUpdateEntry($syncrotonFolder = null)
      {
 -        $xml = simplexml_import_dom($this->_getInputDOMDocument($this->_testXMLInput_SamsungGalaxyStatus));
 -        $controller = $this->_getController($this->_getDevice('SamsungGalaxyS2'));
 -        $event = $controller->toTineModel($xml->Collections->Collection->Commands->Change->ApplicationData);
 -        
 -        $ownAttender = Calendar_Model_Attender::getOwnAttender($event->attendee);
 -        $this->assertEquals(Calendar_Model_Attender::STATUS_ACCEPTED, $ownAttender->status);
 +        if ($syncrotonFolder === null) {
 +            $syncrotonFolder = $this->testCreateFolder();
 +        }
 +    
 +        $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), new Tinebase_DateTime(null, null, 'de_DE'));
 +    
 +        list($serverId, $syncrotonEvent) = $this->testCreateEntry($syncrotonFolder);
 +    
 +        unset($syncrotonEvent->recurrence);
 +        unset($syncrotonEvent->exceptions);
 +    
 +        $serverId = $controller->updateEntry($syncrotonFolder->serverId, $serverId, $syncrotonEvent);
 +    
 +        $syncrotonEvent = $controller->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => $syncrotonFolder->serverId)), $serverId);
 +    
 +        $this->assertFalse($syncrotonEvent->recurrence instanceof Syncroton_Model_EventRecurrence);
 +    
 +        return array($serverId, $syncrotonEvent);
      }
      
 -    /**
 -     * test search events
 -     * 
 -     * @todo implement
 -     */
 -    public function testSearch()
++    public function testRecurEventExceptionFilters($syncrotonFolder = null)
+     {
 -        $this->markTestSkipped();
++        if ($syncrotonFolder === null) {
++            $syncrotonFolder = $this->testCreateFolder();
++        }
+         
 -        $controller = $this->_getController($this->_getDevice(Syncope_Model_Device::TYPE_WEBOS));
 -
 -        $xml = simplexml_import_dom($this->_getInputDOMDocument());
++        $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), new Tinebase_DateTime(null, null, 'de_DE'));
++        
++        list($serverId, $syncrotonEvent) = $this->testCreateEntry($syncrotonFolder);
+         
 -        $record = $controller->createEntry($this->_getContainerWithSyncGrant()->getId(), $xml->Collections->Collection->Commands->Change[0]->ApplicationData);
 -        $this->objects['events'][] = $record;
++        // remove testuser as attendee
++        $eventBackend = new Calendar_Backend_Sql();
++        $exception = $eventBackend->getByProperty($syncrotonEvent->uID . '-' . $syncrotonEvent->exceptions[0]->exceptionStartTime->format(Tinebase_Record_Abstract::ISO8601LONG), 'recurid');
++        $ownAttendee = Calendar_Model_Attender::getOwnAttender($exception->attendee);
++        $eventBackend->deleteAttendee(array($ownAttendee->getId()));
+         
 -        $event = $controller->search($this->_specialFolderName, $xml->Collections->Collection->Commands->Change[0]->ApplicationData);
++        $syncrotonEvent = $controller->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => $syncrotonFolder->serverId)), $serverId);
+         
 -        $this->assertEquals(1       , count($event));
 -        $this->assertEquals('Repeat', $event[0]->summary);
++        // assert fallout by filter
++        $this->assertTrue((bool) $syncrotonEvent->exceptions[0]->deleted);
++        $this->assertTrue((bool) $syncrotonEvent->exceptions[1]->deleted);
+     }
+     
      /**
       * test search events (unsyncable)
       * 
@@@ -58,6 -58,6 +58,9 @@@ class Calendar_Frontend_WebDAV_EventTes
              'application_id'    => Tinebase_Application::getInstance()->getApplicationByName('Calendar')->getId(),
          )));
          
++        $prefs = new Calendar_Preference();
++        $prefs->setValue(Calendar_Preference::DEFAULTCALENDAR, $this->objects['initialContainer']->getId());
++        
          $_SERVER['REQUEST_URI'] = 'lars';
      }
  
@@@ -186,208 -231,228 +186,219 @@@ class ActiveSync_Controller_Calendar ex
      );
      
      /**
 -     * the constructor
 -     *
 -     * @param Tinebase_DateTime $_syncTimeStamp
 +     * (non-PHPdoc)
++     * @see ActiveSync_Controller_Abstract::__construct()
+      */
 -    public function __construct(Syncope_Model_IDevice $_device, DateTime $_syncTimeStamp)
++    public function __construct(Syncroton_Model_IDevice $_device, DateTime $_syncTimeStamp)
+     {
+         parent::__construct($_device, $_syncTimeStamp);
+         
+         $this->_contentController->setEventFilter($this->_getContentFilter(0));
+     }
+     
+     /**
 -     * append event data to xml element
 -     *
++     * (non-PHPdoc)
 +     * @see ActiveSync_Controller_Abstract::toSyncrotonModel()
       * @todo handle BusyStatus
 -     * @todo handle TimeZone data
 -     * 
 -     * @param DOMElement  $_domParrent   the parrent xml node
 -     * @param string      $_folderId  the local folder id
 -     * @param boolean     $_withBody  retrieve body of entry
       */
 -    public function appendXML(DOMElement $_domParrent, $_collectionData, $_serverId)
 +    public function toSyncrotonModel($entry, array $options = array())
      {
 -        $data = $_serverId instanceof Tinebase_Record_Abstract ? $_serverId : $this->_contentController->get($_serverId);
 -        
 -        if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) 
 -            Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . " calendar data " . print_r($data->toArray(), true));
 +        if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(
 +            __METHOD__ . '::' . __LINE__ . " calendar data " . print_r($entry->toArray(), true));
          
 -        // add calendar namespace
 -        $_domParrent->ownerDocument->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/' ,'xmlns:Calendar', 'uri:Calendar');
 +        $syncrotonEvent = new Syncroton_Model_Event();
          
 -        foreach ($this->_mapping as $key => $value) {
 -            if(!empty($data->$value) || $data->$value == '0') {
 -                $nodeContent = null;
 -                
 -                switch($value) {
 -                    case 'dtend':
 -                        if($data->$value instanceof DateTime) {
 -                            if ($data->is_all_day_event == true) {
 -                                // whole day events ends at 23:59:59 in Tine 2.0 but 00:00 the next day in AS
 -                                $dtend = clone $data->dtend;
 -                                $dtend->addSecond($dtend->get('s') == 59 ? 1 : 0);
 -                                $dtend->addMinute($dtend->get('i') == 59 ? 1 : 0);
 -    
 -                                $nodeContent = $dtend->format('Ymd\THis') . 'Z';
 -                            } else {
 -                                $nodeContent = $data->dtend->format('Ymd\THis') . 'Z';
 -                            }
 -                        }
 -                        break;
 -                    case 'dtstart':
 -                        if($data->$value instanceof DateTime) {
 -                            $nodeContent = $data->$value->format('Ymd\THis') . 'Z';
 -                        }
 -                        break;
 -                    default:
 -                        $nodeContent = $data->$value;
 -                        
 -                        break;
 -                }
 -                
 -                // skip empty elements
 -                if($nodeContent === null || $nodeContent == '') {
 -                    Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " Value for $key is empty. Skip element.");
 -                    continue;
 -                }
 -                
 -                // strip off any non printable control characters
 -                if (!ctype_print($nodeContent)) {
 -                    $nodeContent = $this->removeControlChars($nodeContent);
 -                }
 -                
 -                $node = $_domParrent->ownerDocument->createElementNS('uri:Calendar', $key);
 -                $node->appendChild($_domParrent->ownerDocument->createTextNode($nodeContent));
 -                
 -                $_domParrent->appendChild($node);
 +        foreach ($this->_mapping as $syncrotonProperty => $tine20Property) {
 +            if (empty($entry->$tine20Property) && $entry->$tine20Property != '0' || count($entry->$tine20Property) === 0) {
 +                continue;
              }
 -        }
 -        
 -        if(!empty($data->description)) {
 -            if (version_compare($this->_device->acsversion, '12.0', '>=') === true) {
 -                $body = $_domParrent->appendChild(new DOMElement('Body', null, 'uri:AirSyncBase'));
 +            
 +            switch($tine20Property) {
 +                case 'alarms':
 +                    $entry->$tine20Property->sort('alarm_time');
 +                    $alarm = $entry->alarms->getFirstRecord();
 +                    
 +                    if($alarm instanceof Tinebase_Model_Alarm) {
 +                        // NOTE: option minutes_before is always calculated by Calendar_Controller_Event::_inspectAlarmSet
 +                        $minutesBefore = (int) $alarm->getOption('minutes_before');
 +                        
 +                        // avoid negative alarms which may break phones
 +                        if ($minutesBefore >= 0) {
 +                            $syncrotonEvent->$syncrotonProperty = $minutesBefore;
 +                        }
 +                    }
 +                    
 +                    break;
 +                    
 +                case 'attendee':
 +                    // fill attendee cache
 +                    Calendar_Model_Attender::resolveAttendee($entry->$tine20Property, FALSE);
 +                    
 +                    $attendees = array();
                  
 -                $body->appendChild(new DOMElement('Type', 1, 'uri:AirSyncBase'));
 +                    foreach($entry->$tine20Property as $attenderObject) {
 +                        $attendee = new Syncroton_Model_EventAttendee();
 +                        $attendee->name = $attenderObject->getName();
 +                        $attendee->email = $attenderObject->getEmail();
 +                        
 +                        $acsType = array_search($attenderObject->role, $this->_attendeeTypeMapping);
 +                        $attendee->attendeeType = $acsType ? $acsType : Syncroton_Model_EventAttendee::ATTENDEE_TYPE_REQUIRED;
 +            
 +                        $acsStatus = array_search($attenderObject->status, $this->_attendeeStatusMapping);
 +                        $attendee->attendeeStatus = $acsStatus ? $acsStatus : Syncroton_Model_EventAttendee::ATTENDEE_STATUS_UNKNOWN;
 +                        
 +                        $attendees[] = $attendee;
 +                    }
 +                    
 +                    $syncrotonEvent->$syncrotonProperty = $attendees;
 +                    
 +                    // set own status
 +                    if (($ownAttendee = Calendar_Model_Attender::getOwnAttender($entry->attendee)) !== null && ($busyType = array_search($ownAttendee->status, $this->_busyStatusMapping)) !== false) {
 +                        $syncrotonEvent->busyStatus = $busyType;
 +                    }
 +                    
 +                    break;
 +                    
 +                case 'description':
 +                    $syncrotonEvent->$syncrotonProperty = new Syncroton_Model_EmailBody(array(
 +                        'type' => Syncroton_Model_EmailBody::TYPE_PLAINTEXT,
 +                        'data' => $entry->$tine20Property
 +                    ));
                  
 -                // create a new DOMElement ...
 -                $dataTag = new DOMElement('Data', null, 'uri:AirSyncBase');
 -
 -                // ... append it to parent node aka append it to the document ...
 -                $body->appendChild($dataTag);
 +                    break;
                  
 -                // ... and now add the content (DomText takes care of special chars)
 -                $dataTag->appendChild(new DOMText($data->description));
 -            } else {
 -                // create a new DOMElement ...
 -                $node = new DOMElement('Body', null, 'uri:Calendar');
 +                case 'dtend':
 +                    if($entry->$tine20Property instanceof DateTime) {
 +                        if ($entry->is_all_day_event == true) {
 +                            // whole day events ends at 23:59:59 in Tine 2.0 but 00:00 the next day in AS
 +                            $dtend = clone $entry->$tine20Property;
 +                            $dtend->addSecond($dtend->get('s') == 59 ? 1 : 0);
 +                            $dtend->addMinute($dtend->get('i') == 59 ? 1 : 0);
  
 -                // ... append it to parent node aka append it to the document ...
 -                $_domParrent->appendChild($node);
 -                
 -                // ... and now add the content (DomText takes care of special chars)
 -                $node->appendChild(new DOMText($data->description));
 -                
 -            }
 -        }
 -           
 -        if(!empty($data->alarms)) {
 -            $data->alarms->sort('alarm_time');
 -            $alarm = $data->alarms->getFirstRecord();
 -            if($alarm instanceof Tinebase_Model_Alarm) {
 -                // NOTE: option minutes_before is always calculated by Calendar_Controller_Event::_inspectAlarmSet
 -                $minutesBefore = (int) $alarm->getOption('minutes_before');
 -                if ($minutesBefore >= 0) {
 -                    $_domParrent->appendChild(new DOMElement('Reminder', $minutesBefore, 'uri:Calendar'));
 -                }
 -            }
 -        }
 -                
 -        
 -        if(!empty($data->rrule)) {
 -            if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " calendar rrule " . $data->rrule);
 -            $rrule = Calendar_Model_Rrule::getRruleFromString($data->rrule);
 -            
 -            $recurrence = $_domParrent->appendChild(new DOMElement('Recurrence', null, 'uri:Calendar'));
 -            // required fields
 -            switch($rrule->freq) {
 -                case Calendar_Model_Rrule::FREQ_DAILY:
 -                    $recurrence->appendChild(new DOMElement('Type', self::RECUR_TYPE_DAILY, 'uri:Calendar'));
 +                            $syncrotonEvent->$syncrotonProperty = $dtend;
 +                        } else {
 +                            $syncrotonEvent->$syncrotonProperty = $entry->$tine20Property;
 +                        }
 +                    }
 +                    
                      break;
                      
 -                case Calendar_Model_Rrule::FREQ_WEEKLY:
 -                    $recurrence->appendChild(new DOMElement('Type',      self::RECUR_TYPE_WEEKLY,                    'uri:Calendar'));
 -                    $recurrence->appendChild(new DOMElement('DayOfWeek', $this->_convertDayToBitMask($rrule->byday), 'uri:Calendar'));
 +                case 'dtstart':
 +                    if($entry->$tine20Property instanceof DateTime) {
 +                        $syncrotonEvent->$syncrotonProperty = $entry->$tine20Property;
 +                    }
 +                    
                      break;
                      
 -                case Calendar_Model_Rrule::FREQ_MONTHLY:
 -                    if(!empty($rrule->bymonthday)) {
 -                        $recurrence->appendChild(new DOMElement('Type',       self::RECUR_TYPE_MONTHLY, 'uri:Calendar'));
 -                        $recurrence->appendChild(new DOMElement('DayOfMonth', $rrule->bymonthday,       'uri:Calendar'));
 -                    } else {
 -                        $weekOfMonth = (int) substr($rrule->byday, 0, -2);
 -                        $weekOfMonth = ($weekOfMonth == -1) ? 5 : $weekOfMonth;
 -                        $dayOfWeek   = substr($rrule->byday, -2);
 +                case 'exdate':
 +                    // handle exceptions of repeating events
 +                    if($entry->$tine20Property instanceof Tinebase_Record_RecordSet && $entry->$tine20Property->count() > 0) {
 +                        $exceptions = array();
 +                    
 +                        foreach ($entry->exdate as $exdate) {
 +                            $exception = new Syncroton_Model_EventException();
 +                    
 +                            $exception->deleted            = (int)$exdate->is_deleted;
 +                            $exception->exceptionStartTime = $exdate->getOriginalDtStart();
 +                    
 +                            if ((int)$exdate->is_deleted === 0) {
 +                                $exceptionSyncrotonEvent = $this->toSyncrotonModel($exdate);
 +                                foreach ($exception->getProperties() as $property) {
 +                                    if (isset($exceptionSyncrotonEvent->$property)) {
 +                                        $exception->$property = $exceptionSyncrotonEvent->$property;
 +                                    }
 +                                }
 +                                unset($exceptionSyncrotonEvent);
 +                            }
 +                            
 +                            $exceptions[] = $exception;
 +                        }
                          
 -                        $recurrence->appendChild(new DOMElement('Type',        self::RECUR_TYPE_MONTHLY_DAYN,           'uri:Calendar'));
 -                        $recurrence->appendChild(new DOMElement('WeekOfMonth', $weekOfMonth,                            'uri:Calendar'));
 -                        $recurrence->appendChild(new DOMElement('DayOfWeek',   $this->_convertDayToBitMask($dayOfWeek), 'uri:Calendar'));
 +                        $syncrotonEvent->$syncrotonProperty = $exceptions;
                      }
 +                    
                      break;
                      
 -                case Calendar_Model_Rrule::FREQ_YEARLY:
 -                    if(!empty($rrule->bymonthday)) {
 -                        $recurrence->appendChild(new DOMElement('Type',        self::RECUR_TYPE_YEARLY, 'uri:Calendar'));
 -                        $recurrence->appendChild(new DOMElement('DayOfMonth',  $rrule->bymonthday,      'uri:Calendar'));
 -                        $recurrence->appendChild(new DOMElement('MonthOfYear', $rrule->bymonth,         'uri:Calendar'));
 -                    } else {
 -                        $weekOfMonth = (int) substr($rrule->byday, 0, -2);
 -                        $weekOfMonth = ($weekOfMonth == -1) ? 5 : $weekOfMonth;
 -                        $dayOfWeek   = substr($rrule->byday, -2);
 +                case 'rrule':
 +                    if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
 +                        __METHOD__ . '::' . __LINE__ . " calendar rrule " . $entry->$tine20Property);
                          
 -                        $recurrence->appendChild(new DOMElement('Type',        self::RECUR_TYPE_YEARLY_DAYN, 'uri:Calendar'));
 -                        $recurrence->appendChild(new DOMElement('WeekOfMonth', $weekOfMonth,                 'uri:Calendar'));
 -                        $recurrence->appendChild(new DOMElement('DayOfWeek',   $this->_convertDayToBitMask($dayOfWeek), 'uri:Calendar'));
 -                        $recurrence->appendChild(new DOMElement('MonthOfYear', $rrule->bymonth,              'uri:Calendar'));
 +                    $rrule = Calendar_Model_Rrule::getRruleFromString($entry->$tine20Property);
 +                    
 +                    $recurrence = new Syncroton_Model_EventRecurrence();
 +                    
 +                    // required fields
 +                    switch($rrule->freq) {
 +                        case Calendar_Model_Rrule::FREQ_DAILY:
 +                            $recurrence->type = Syncroton_Model_EventRecurrence::TYPE_DAILY;
 +                            
 +                            break;
 +                    
 +                        case Calendar_Model_Rrule::FREQ_WEEKLY:
 +                            $recurrence->type      = Syncroton_Model_EventRecurrence::TYPE_WEEKLY;
 +                            $recurrence->dayOfWeek = $this->_convertDayToBitMask($rrule->byday);
 +                            
 +                            break;
 +                    
 +                        case Calendar_Model_Rrule::FREQ_MONTHLY:
 +                            if(!empty($rrule->bymonthday)) {
 +                                $recurrence->type       = Syncroton_Model_EventRecurrence::TYPE_MONTHLY;
 +                                $recurrence->dayOfMonth = $rrule->bymonthday;
 +                            } else {
 +                                $weekOfMonth = (int) substr($rrule->byday, 0, -2);
 +                                $weekOfMonth = ($weekOfMonth == -1) ? 5 : $weekOfMonth;
 +                                $dayOfWeek   = substr($rrule->byday, -2);
 +                    
 +                                $recurrence->type        = Syncroton_Model_EventRecurrence::TYPE_MONTHLY_DAYN;
 +                                $recurrence->weekOfMonth = $weekOfMonth;
 +                                $recurrence->dayOfWeek   = $this->_convertDayToBitMask($dayOfWeek);
 +                            }
 +                            
 +                            break;
 +                    
 +                        case Calendar_Model_Rrule::FREQ_YEARLY:
 +                            if(!empty($rrule->bymonthday)) {
 +                                $recurrence->type        = Syncroton_Model_EventRecurrence::TYPE_YEARLY;
 +                                $recurrence->dayOfMonth  = $rrule->bymonthday;
 +                                $recurrence->monthOfYear = $rrule->bymonth;
 +                            } else {
 +                                $weekOfMonth = (int) substr($rrule->byday, 0, -2);
 +                                $weekOfMonth = ($weekOfMonth == -1) ? 5 : $weekOfMonth;
 +                                $dayOfWeek   = substr($rrule->byday, -2);
 +                    
 +                                $recurrence->type        = Syncroton_Model_EventRecurrence::TYPE_YEARLY_DAYN;
 +                                $recurrence->weekOfMonth = $weekOfMonth;
 +                                $recurrence->dayOfWeek   = $this->_convertDayToBitMask($dayOfWeek);
 +                                $recurrence->monthOfYear = $rrule->bymonth;
 +                            }
 +                            
 +                            break;
                      }
 -                    break;
 -            }
 -            
 -            // required field
 -            $interval = $rrule->interval ? $rrule->interval : 1;
 -            $recurrence->appendChild(new DOMElement('Interval', $interval, 'uri:Calendar'));
 -            
 -            if($rrule->until instanceof DateTime) {
 -                $recurrence->appendChild(new DOMElement('Until', $rrule->until->format('Ymd\THis') . 'Z', 'uri:Calendar'));
 -            }
 -            
 -            // handle exceptions of repeating events
 -            if($data->exdate instanceof Tinebase_Record_RecordSet && $data->exdate->count() > 0) {
 -                $exceptionsTag = $_domParrent->appendChild(new DOMElement('Exceptions', null, 'uri:Calendar'));
 -                
 -                foreach ($data->exdate as $exception) {
 -                    $exceptionTag = $exceptionsTag->appendChild(new DOMElement('Exception', null, 'uri:Calendar'));
                      
 -                    $exceptionTag->appendChild(new DOMElement('Deleted', (int)$exception->is_deleted, 'uri:Calendar'));
 -                    $exceptionTag->appendChild(new DOMElement('ExceptionStartTime', $exception->getOriginalDtStart()->format('Ymd\THis') . 'Z', 'uri:Calendar'));
 +                    // required field
 +                    $recurrence->interval = $rrule->interval ? $rrule->interval : 1;
                      
 -                    if ((int)$exception->is_deleted === 0) {
 -                        $this->appendXML($exceptionTag, $_collectionData, $exception);
 +                    if($rrule->count) {
 +                        $recurrence->occurrences = $rrule->count;
 +                    } else if($rrule->until instanceof DateTime) {
 +                        $recurrence->until = $rrule->until;
                      }
 -                }
 -            }
 -            
 -        }
 -
 -        if(count($data->attendee) > 0) {
 -            // fill attendee cache
 -            Calendar_Model_Attender::resolveAttendee($data->attendee, FALSE);
 -            
 -            $attendees = $_domParrent->ownerDocument->createElementNS('uri:Calendar', 'Attendees');
 -            
 -            foreach($data->attendee as $attenderObject) {
 -                $attendee = $attendees->appendChild(new DOMElement('Attendee', null, 'uri:Calendar'));
 -                $attendee->appendChild(new DOMElement('Name', $attenderObject->getName(), 'uri:Calendar'));
 -                $attendee->appendChild(new DOMElement('Email', $attenderObject->getEmail(), 'uri:Calendar'));
 -                if(version_compare($this->_device->acsversion, '12.0', '>=') === true) {
 -                    $acsType = array_search($attenderObject->role, $this->_attendeeTypeMapping);
 -                    $attendee->appendChild(new DOMElement('AttendeeType', $acsType ? $acsType : self::ATTENDEE_TYPE_REQUIRED , 'uri:Calendar'));
 -                    
 -                    $acsStatus = array_search($attenderObject->status, $this->_attendeeStatusMapping);
 -                    $attendee->appendChild(new DOMElement('AttendeeStatus', $acsStatus ? $acsStatus : self::ATTENDEE_STATUS_UNKNOWN, 'uri:Calendar'));
 -                }
 -            }
 -            
 -            if ($attendees->hasChildNodes()) {
 -                $_domParrent->appendChild($attendees);
 -            }
 -            
 -            // set own status
 -            if (($ownAttendee = Calendar_Model_Attender::getOwnAttender($data->attendee)) !== null && ($busyType = array_search($ownAttendee->status, $this->_busyStatusMapping)) !== false) {
 -                $_domParrent->appendChild(new DOMElement('BusyStatus', $busyType, 'uri:Calendar'));
 +                    
 +                    $syncrotonEvent->$syncrotonProperty = $recurrence;
 +                    
 +                    break;
 +                    
 +                // @todo validate tags are working
 +                case 'tags':
 +                    $syncrotonEvent->$syncrotonProperty = (array) $entry->$tine20Property;
 +                    
 +                    break;
 +                    
 +                default:
 +                    $syncrotonEvent->$syncrotonProperty = $entry->$tine20Property;
 +                    
 +                    break;
              }
 -        
          }
          
          $timeZoneConverter = ActiveSync_TimezoneConverter::getInstance(
Simple merge
Simple merge