generalize VEVENT/VTODO handling
authorPhilipp Schüle <p.schuele@metaways.de>
Thu, 21 Aug 2014 10:32:57 +0000 (12:32 +0200)
committerPhilipp Schüle <p.schuele@metaways.de>
Thu, 4 Sep 2014 09:26:44 +0000 (11:26 +0200)
* generalize VALARMs
* removes lots of obsolete code
* adds apple valarm test (VTODO)

Change-Id: I4c944b51f94ee7b201b86c3e9a7453071c5fe721
Reviewed-on: http://gerrit.tine20.com/customers/1031
Tested-by: Jenkins CI (http://ci.tine20.com/)
Reviewed-by: Philipp Schüle <p.schuele@metaways.de>
tests/tine20/Tasks/Convert/Task/VCalendar/MacOSXTest.php
tests/tine20/Tasks/Import/files/apple_valarm.ics [new file with mode: 0644]
tine20/Calendar/Convert/Event/VCalendar/Abstract.php
tine20/Tasks/Convert/Task/VCalendar/Abstract.php
tine20/Tinebase/Convert/VCalendar/Abstract.php [new file with mode: 0644]

index 212a51c..3d816a4 100644 (file)
@@ -4,71 +4,28 @@
  * 
  * @package     Calendar
  * @license     http://www.gnu.org/licenses/agpl.html
- * @copyright   Copyright (c) 2011-2011 Metaways Infosystems GmbH (http://www.metaways.de)
- * @author      Lars Kneschke <l.kneschke@metaways.de>
+ * @copyright   Copyright (c) 2014 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Philipp Schüle <p.schuele@metaways.de>
  */
 
 /**
- * Test helper
- */
-require_once dirname(dirname(dirname(dirname(dirname(__FILE__))))) . DIRECTORY_SEPARATOR . 'TestHelper.php';
-
-if (!defined('PHPUnit_MAIN_METHOD')) {
-    define('PHPUnit_MAIN_METHOD', 'Tasks_Convert_Task_VCalendar_MacOSXTest::main');
-}
-
-/**
  * Test class for Tasks_Convert_Task_VCalendar_MacOSX
  */
-class Tasks_Convert_Task_VCalendar_MacOSXTest extends PHPUnit_Framework_TestCase
+class Tasks_Convert_Task_VCalendar_MacOSXTest extends TestCase
 {
     /**
-     * @var array test objects
-     */
-    protected $objects = array();
-    
-    /**
-     * Runs the test methods of this class.
-     *
-     * @access public
-     * @static
-     */
-    public static function main()
-    {
-        $suite  = new PHPUnit_Framework_TestSuite('Tine 2.0 Calendar WebDAV MacOSX Event Tests');
-        PHPUnit_TextUI_TestRunner::run($suite);
-    }
-
-    /**
-     * Sets up the fixture.
-     * This method is called before a test is executed.
-     *
-     * @access protected
-     */
-    protected function setUp()
-    {
-        $this->markTestIncomplete('tests not yet implemented');
-    }
-
-    /**
-     * Tears down the fixture
-     * This method is called after a test is executed.
-     *
-     * @access protected
-     */
-    protected function tearDown()
-    {
-    }
-    
-    /**
-     * test converting vcard from sogo connector to Calendar_Model_Event 
+     * test converting vtodo without trigger valarm
      */
     public function testConvertToTine20Model()
     {
-        $vcalendarStream = fopen(dirname(__FILE__) . '/../../../Import/files/lightning.ics', 'r');
-        
-        $converter = Tasks_Convert_Task_VCalendar_Factory::factory(Tasks_Convert_Task_VCalendar_Factory::CLIENT_GENERIC);
+        $vcalendarStream = fopen(dirname(__FILE__) . '/../../../Import/files/apple_valarm.ics', 'r');
+    
+        $converter = Tasks_Convert_Task_VCalendar_Factory::factory(Tasks_Convert_Task_VCalendar_Factory::CLIENT_MACOSX);
+    
+        $task = $converter->toTine20Model($vcalendarStream);
         
-        $event = $converter->toTine20Model($vcalendarStream);
-    }            
+        $this->assertEquals('Stundenaufstellung Heinz Walter', $task->summary, print_r($task->toArray(), true));
+        $this->assertEquals(1, count($task->alarms), print_r($task->toArray(), true));
+        $this->assertEquals('2014-09-12 06:00:00', $task->alarms->getFirstRecord()->alarm_time->toString(), print_r($task->toArray(), true));
+    }
 }
diff --git a/tests/tine20/Tasks/Import/files/apple_valarm.ics b/tests/tine20/Tasks/Import/files/apple_valarm.ics
new file mode 100644 (file)
index 0000000..71737d4
--- /dev/null
@@ -0,0 +1,24 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//Mac OS X 10.9.4//EN
+CALSCALE:GREGORIAN
+BEGIN:VTODO
+STATUS:NEEDS-ACTION
+CREATED:20131112T111120Z
+UID:79C3E044-658D-4B88-A0E8-2BCEFFAEF5A5
+DUE;VALUE=DATE:20140912
+X-APPLE-SORT-ORDER:430306542
+SUMMARY:Stundenaufstellung Heinz Walter
+PRIORITY:1
+DTSTAMP:20140813T103551Z
+SEQUENCE:15
+DESCRIPTION:geringfügig Beschäftigte
+BEGIN:VALARM
+X-WR-ALARMUID:F84333D4-63F7-4430-94D0-B7039A407901
+UID:F84333D4-63F7-4430-94D0-B7039A407901
+TRIGGER:PT8H
+ATTACH;VALUE=URI:Basso
+ACTION:AUDIO
+END:VALARM
+END:VTODO
+END:VCALENDAR
index adf3a79..76182c1 100644 (file)
  * @package     Calendar
  * @subpackage  Convert
  */
-class Calendar_Convert_Event_VCalendar_Abstract implements Tinebase_Convert_Interface
+class Calendar_Convert_Event_VCalendar_Abstract extends Tinebase_Convert_VCalendar_Abstract implements Tinebase_Convert_Interface
 {
-    /**
-     * use servers modlogProperties instead of given DTSTAMP & SEQUENCE
-     * use this if the concurrency checks are done differently like in CalDAV
-     * where the etag is checked
-     */
-    const OPTION_USE_SERVER_MODLOG = 'useServerModlog';
-    
     public static $cutypeMap = array(
         Calendar_Model_Attender::USERTYPE_USER          => 'INDIVIDUAL',
         Calendar_Model_Attender::USERTYPE_GROUPMEMBER   => 'INDIVIDUAL',
@@ -32,10 +25,8 @@ class Calendar_Convert_Event_VCalendar_Abstract implements Tinebase_Convert_Inte
         Calendar_Model_Attender::USERTYPE_RESOURCE      => 'RESOURCE',
     );
     
-    protected $_supportedFields = array();
-    
-    protected $_version;
-    
+    protected $_modelName = 'Calendar_Model_Event';
+
     /**
      * value of METHOD property
      * @var string
@@ -43,14 +34,6 @@ class Calendar_Convert_Event_VCalendar_Abstract implements Tinebase_Convert_Inte
     protected $_method;
     
     /**
-     * @param  string  $version  the version of the client
-     */
-    public function __construct($version = null)
-    {
-        $this->_version = $version;
-    }
-    
-    /**
      * convert Tinebase_Record_RecordSet to Sabre\VObject\Component
      *
      * @param  Tinebase_Record_RecordSet  $_records
@@ -348,16 +331,7 @@ class Calendar_Convert_Event_VCalendar_Abstract implements Tinebase_Convert_Inte
             $vevent->add('ATTENDEE', (strpos($attendeeEmail, '@') !== false ? 'mailto:' : 'urn:uuid:') . $attendeeEmail, $parameters);
         }
     }
-    
-    /**
-     * can be overwriten in extended class to modify/cleanup $_vcalendar
-     * 
-     * @param \Sabre\VObject\Component\VCalendar $vcalendar
-     */
-    protected function _afterFromTine20Model(\Sabre\VObject\Component\VCalendar $vcalendar)
-    {
-    }
-    
+
     /**
      * set the METHOD for the generated VCALENDAR
      *
@@ -556,95 +530,6 @@ class Calendar_Convert_Event_VCalendar_Abstract implements Tinebase_Convert_Inte
     }
     
     /**
-     * returns VObject of input data
-     * 
-     * @param   mixed  $blob
-     * @return  \Sabre\VObject\Component\VCalendar
-     */
-    public static function getVObject($blob)
-    {
-        if ($blob instanceof \Sabre\VObject\Component\VCalendar) {
-            return $blob;
-        }
-        
-        if (is_resource($blob)) {
-            $blob = stream_get_contents($blob);
-        }
-        
-        $blob = Tinebase_Core::filterInputForDatabase($blob);
-        
-        $vcalendar = self::readVCalBlob($blob);
-        
-        return $vcalendar;
-    }
-    
-    /**
-     * reads vcal blob and tries to repair some parsing problems that Sabre has
-     * 
-     * @param string $blob
-     * @param integer $failcount
-     * @param integer $spacecount
-     * @param integer $lastBrokenLineNumber
-     * @param array $lastLines
-     * @throws Sabre\VObject\ParseException
-     * @return Sabre\VObject\Component\VCalendar
-     * 
-     * @see 0006110: handle iMIP messages from outlook
-     * 
-     * @todo maybe we can remove this when #7438 is resolved
-     */
-    public static function readVCalBlob($blob, $failcount = 0, $spacecount = 0, $lastBrokenLineNumber = 0, $lastLines = array())
-    {
-        // convert to utf-8
-        $blob = mbConvertTo($blob);
-        
-        if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ .
-            ' ' . $blob);
-        
-        try {
-            $vcalendar = \Sabre\VObject\Reader::read($blob);
-        } catch (Sabre\VObject\ParseException $svpe) {
-            // NOTE: we try to repair\Sabre\VObject\Reader as it fails to detect followup lines that do not begin with a space or tab
-            if ($failcount < 10 && preg_match(
-                '/Invalid VObject, line ([0-9]+) did not follow the icalendar\/vcard format/', $svpe->getMessage(), $matches
-            )) {
-                if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
-                    ' ' . $svpe->getMessage() .
-                    ' lastBrokenLineNumber: ' . $lastBrokenLineNumber);
-                
-                $brokenLineNumber = $matches[1] - 1 + $spacecount;
-                
-                if ($lastBrokenLineNumber === $brokenLineNumber) {
-                    if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
-                        ' Try again: concat this line to previous line.');
-                    $lines = $lastLines;
-                    $brokenLineNumber--;
-                    // increase spacecount because one line got removed
-                    $spacecount++;
-                } else {
-                    $lines = preg_split('/[\r\n]*\n/', $blob);
-                    if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
-                        ' Concat next line to this one.');
-                    $lastLines = $lines; // for retry
-                }
-                $lines[$brokenLineNumber] .= $lines[$brokenLineNumber + 1];
-                unset($lines[$brokenLineNumber + 1]);
-                
-                if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ .
-                    ' failcount: ' . $failcount .
-                    ' brokenLineNumber: ' . $brokenLineNumber .
-                    ' spacecount: ' . $spacecount);
-                
-                $vcalendar = self::readVCalBlob(implode("\n", $lines), $failcount + 1, $spacecount, $brokenLineNumber, $lastLines);
-            } else {
-                throw $svpe;
-            }
-        }
-        
-        return $vcalendar;
-    }
-    
-    /**
      * get METHOD of current VCALENDAR or supplied blob
      * 
      * @param  string  $blob
@@ -1128,119 +1013,4 @@ class Calendar_Convert_Event_VCalendar_Abstract implements Tinebase_Convert_Inte
         // convert all datetime fields to UTC
         $event->setTimezone('UTC');
     }
-    
-    /**
-     * parse valarm properties
-     * 
-     * @param Calendar_Model_Event $event
-     * @param unknown $valarms
-     * @param \Sabre\VObject\Component\VEvent $vevent
-     */
-    protected function _parseAlarm(Calendar_Model_Event $event, $valarms, \Sabre\VObject\Component\VEvent $vevent)
-    {
-        foreach ($valarms as $valarm) {
-            
-            if ($valarm->ACTION == 'NONE') {
-                if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
-                        . ' We can\'t cope with action NONE: iCal 6.0 sends default alarms in the year 1976 with action NONE. Skipping alarm.');
-                continue;
-            }
-            
-            if (! is_object($valarm->TRIGGER)) {
-                if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
-                . ' Alarm has no TRIGGER value. Skipping it.');
-                continue;
-            }
-            
-            # TRIGGER:-PT15M
-            if (is_string($valarm->TRIGGER->getValue()) && $valarm->TRIGGER instanceof Sabre\VObject\Property\ICalendar\Duration) {
-                if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
-                . ' Adding DURATION trigger value for ' . $valarm->TRIGGER->getValue());
-                $valarm->TRIGGER->add('VALUE', 'DURATION');
-            }
-            
-            $trigger = is_object($valarm->TRIGGER['VALUE']) ? $valarm->TRIGGER['VALUE'] : (is_object($valarm->TRIGGER['RELATED']) ? $valarm->TRIGGER['RELATED'] : NULL);
-            
-            if ($trigger === NULL) {
-                // added Trigger/Related for eM Client alarms
-                // 2014-01-03 - Bullshit, why don't we have testdata for emclient alarms?
-                        //              this alarm handling should be refactored, the logic is scrambled
-                // @see 0006110: handle iMIP messages from outlook
-                // @todo fix 0007446: handle broken alarm in outlook invitation message
-                if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
-                . ' Alarm has no TRIGGER value. Skipping it.');
-                continue;
-            }
-            
-            switch (strtoupper($trigger->getValue())) {
-                # TRIGGER;VALUE=DATE-TIME:20111031T130000Z
-                case 'DATE-TIME':
-                    $alarmTime = new Tinebase_DateTime($valarm->TRIGGER->getValue());
-                    $alarmTime->setTimezone('UTC');
-                    
-                    $alarm = new Tinebase_Model_Alarm(array(
-                        'alarm_time'        => $alarmTime,
-                        'minutes_before'    => 'custom',
-                        'model'             => 'Calendar_Model_Event'
-                    ));
-                    
-                    break;
-                
-                # TRIGGER;VALUE=DURATION:-PT1H15M
-                case 'DURATION':
-                default:
-                    $alarmTime = $this->_convertToTinebaseDateTime($vevent->DTSTART);
-                    $alarmTime->setTimezone('UTC');
-                    
-                    preg_match('/(?P<invert>[+-]?)(?P<spec>P.*)/', $valarm->TRIGGER->getValue(), $matches);
-                    $duration = new DateInterval($matches['spec']);
-                    $duration->invert = !!($matches['invert'] === '-');
-                    
-                    $alarm = new Tinebase_Model_Alarm(array(
-                        'alarm_time'        => $alarmTime->add($duration),
-                        'minutes_before'    => ($duration->format('%d') * 60 * 24) + ($duration->format('%h') * 60) + ($duration->format('%i')),
-                        'model'             => 'Calendar_Model_Event'
-                    ));
-                    if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
-                        . ' Adding DURATION alarm ' . print_r($alarm->toArray(), true));
-            }
-            
-            if ($valarm->ACKNOWLEDGED) {
-                $dtack = $valarm->ACKNOWLEDGED->getDateTime();
-                Calendar_Controller_Alarm::setAcknowledgeTime($alarm, $dtack);
-            }
-            
-            $event->alarms->addRecord($alarm);
-        }
-    }
-    
-    /**
-     * get datetime from sabredav datetime property (user TZ is fallback)
-     * 
-     * @param  Sabre\VObject\Property  $dateTimeProperty
-     * @param  boolean                 $_useUserTZ
-     * @return Tinebase_DateTime
-     * 
-     * @todo try to guess some common timezones
-     */
-    protected function _convertToTinebaseDateTime(\Sabre\VObject\Property $dateTimeProperty, $_useUserTZ = FALSE)
-    {
-        $defaultTimezone = date_default_timezone_get();
-        date_default_timezone_set((string) Tinebase_Core::get(Tinebase_Core::USERTIMEZONE));
-        
-        if ($dateTimeProperty instanceof Sabre\VObject\Property\ICalendar\DateTime) {
-            $dateTime = $dateTimeProperty->getDateTime();
-            $tz = ($_useUserTZ || (isset($dateTimeProperty['VALUE']) && strtoupper($dateTimeProperty['VALUE']) == 'DATE')) ? 
-                (string) Tinebase_Core::get(Tinebase_Core::USERTIMEZONE) : 
-                $dateTime->getTimezone();
-            
-            $result = new Tinebase_DateTime($dateTime->format(Tinebase_Record_Abstract::ISO8601LONG), $tz);
-        } else {
-            $result = new Tinebase_DateTime($dateTimeProperty->getValue());
-        }
-        
-        date_default_timezone_set($defaultTimezone);
-        
-        return $result;
-    }
 }
index 01c84c0..4785b1f 100644 (file)
  *
  * @package     Tasks
  * @subpackage  Convert
+ * 
+ * @todo find a way to generalize VCARD/VEVENT/VTODO parsing
  */
-class Tasks_Convert_Task_VCalendar_Abstract implements Tinebase_Convert_Interface
+class Tasks_Convert_Task_VCalendar_Abstract extends Tinebase_Convert_VCalendar_Abstract implements Tinebase_Convert_Interface
 {
-    /**
-     * use servers modlogProperties instead of given DTSTAMP & SEQUENCE
-     * use this if the concurrency checks are done differntly like in CalDAV
-     * where the etag is checked
-     */
-    const OPTION_USE_SERVER_MODLOG = 'useServerModlog';
-    
-    public static $cutypeMap = array(
-        //Tasks_Model_Attender::USERTYPE_USER          => 'INDIVIDUAL',
-        //Tasks_Model_Attender::USERTYPE_GROUPMEMBER   => 'INDIVIDUAL',
-        //Tasks_Model_Attender::USERTYPE_GROUP         => 'GROUP',
-        //Tasks_Model_Attender::USERTYPE_RESOURCE      => 'RESOURCE',
-    );
-    
-    protected $_supportedFields = array(
-    );
-    
-    protected $_version;
-    
-    /**
-     * value of METHOD property
-     * @var string
-     */
-    protected $_method;
+    protected $_modelName = 'Tasks_Model_Task';
     
     /**
-     * @param  string  $_version  the version of the client
-     */
-    public function __construct($_version = null)
-    {
-        $this->_version = $_version;
-    }
-        
-    /**
      * convert Tasks_Model_Task to \Sabre\VObject\Component
      *
      * @param  Tasks_Model_Task  $_record
@@ -83,24 +54,6 @@ class Tasks_Convert_Task_VCalendar_Abstract implements Tinebase_Convert_Interfac
         
         $this->_convertTasksModelTask($vcalendar, $_record);
         
-        # Tasks application does not yet support repeating tasks
-        #
-        #if ($_record->exdate instanceof Tinebase_Record_RecordSet) {
-        #    $eventExceptions = $_record->exdate->filter('is_deleted', false);
-        #    
-        #    foreach($eventExceptions as $eventException) {
-        #        // set timefields
-        #        // @todo move to MS event facade
-        #        $eventException->creation_time = $_record->creation_time;
-        #        if (isset($_record->last_modified_time)) {
-        #            $eventException->last_modified_time = $_record->last_modified_time;
-        #        }
-        #        $vevent = $this->_convertTasksModelTask($eventException, $_record);
-        #        $vcalendar->add($vevent);
-        #    }
-        #
-        #}
-        
         $this->_afterFromTine20Model($vcalendar);
         
         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(
@@ -205,51 +158,7 @@ class Tasks_Convert_Task_VCalendar_Abstract implements Tinebase_Convert_Interfac
             $vtodo->add('CATEGORIES', (array) $task->tags->name);
         }
         
-        // repeating event properties
-        /*if ($event->rrule) {
-            if ($event->is_all_day_event == true) {
-                $vtodo->add(new Sabre_VObject_Property_Recure('RRULE', preg_replace_callback('/UNTIL=([\d :-]{19})(?=;?)/', function($matches) {
-                    $dtUntil = new Tinebase_DateTime($matches[1]);
-                    $dtUntil->setTimezone((string) Tinebase_Core::get(Tinebase_Core::USERTIMEZONE));
-                    
-                    return 'UNTIL=' . $dtUntil->format('Ymd');
-                }, $event->rrule)));
-            } else {
-                $vtodo->add(new Sabre_VObject_Property_Recure('RRULE', preg_replace('/(UNTIL=)(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/', '$1$2$3$4T$5$6$7Z', $event->rrule)));
-            }
-            if ($event->exdate instanceof Tinebase_Record_RecordSet) {
-                $deletedEvents = $event->exdate->filter('is_deleted', true);
-                
-                foreach($deletedEvents as $deletedEvent) {
-                    $exdate = new Sabre_VObject_Element_DateTime('EXDATE');
-                    $dateTime = $deletedEvent->getOriginalDtStart();
-                    
-                    if ($event->is_all_day_event == true) {
-                        $dateTime->setTimezone($event->originator_tz);
-                        $exdate->setDateTime($dateTime, Sabre_VObject_Element_DateTime::DATE);
-                    } else {
-                        $exdate->setDateTime($dateTime, Sabre_VObject_Element_DateTime::UTC);
-                    }
-                    $vtodo->add($exdate);
-                }
-            }
-        }
-        */
-        // add alarms only to vcalendar if current user attends to this event
         if ($task->alarms) {
-            //$ownAttendee = Calendar_Model_Attender::getOwnAttender($task->attendee);
-            
-            //if ($ownAttendee && $ownAttendee->alarm_ack_time instanceof Tinebase_DateTime) {
-            //    $xMozLastAck = new Sabre_VObject_Element_DateTime('X-MOZ-LASTACK');
-            //    $xMozLastAck->setDateTime($ownAttendee->alarm_ack_time, Sabre_VObject_Element_DateTime::UTC);
-            //    $vtodo->add($xMozLastAck);
-            //}
-            
-            //if ($ownAttendee && $ownAttendee->alarm_snooze_time instanceof Tinebase_DateTime) {
-            //    $xMozSnoozeTime = new Sabre_VObject_Element_DateTime('X-MOZ-SNOOZE-TIME');
-            //    $xMozSnoozeTime->setDateTime($ownAttendee->alarm_snooze_time, Sabre_VObject_Element_DateTime::UTC);
-            //    $vtodo->add($xMozSnoozeTime);
-            //}
             
             // fake X-MOZ-LASTACK
             $vtodo->add('X-MOZ-LASTACK', $task->creation_time->getClone()->setTimezone('UTC'), array('VALUE' => 'DATE-TIME'));
@@ -292,15 +201,6 @@ class Tasks_Convert_Task_VCalendar_Abstract implements Tinebase_Convert_Interfac
     }
     
     /**
-     * to be overwriten in extended classes to modify/cleanup $_vcalendar
-     * 
-     * @param \Sabre\VObject\Component\VCalendar $vcalendar
-     */
-    protected function _afterFromTine20Model(\Sabre\VObject\Component\VCalendar $vcalendar)
-    {
-    }
-    
-    /**
      * converts vcalendar to Tasks_Model_Task
      * 
      * @param  mixed                 $_blob   the vcalendar to parse
@@ -312,7 +212,7 @@ class Tasks_Convert_Task_VCalendar_Abstract implements Tinebase_Convert_Interfac
     {
         $vcalendar = self::getVObject($_blob);
         
-        // contains the VCALENDAR any VEVENTS
+        // contains the VCALENDAR any VTODOS
         if (!isset($vcalendar->VTODO)) {
             throw new Tinebase_Exception_UnexpectedValue('no vevents found');
         }
@@ -327,180 +227,28 @@ class Tasks_Convert_Task_VCalendar_Abstract implements Tinebase_Convert_Interfac
         // bypass filters until end of this funtion
         $task->bypassFilters = true;
         
-        // keep current exdate's (only the not deleted ones)
-        /* if ($task->exdate instanceof Tinebase_Record_RecordSet) {
-            $oldExdates = $task->exdate->filter('is_deleted', false);
-        } else {
-            $oldExdates = new Tinebase_Record_RecordSet('Calendar_Model_Events');
-        }
-        */
-        if (!isset($vcalendar->METHOD)) {
-            $this->_method = $vcalendar->METHOD;
-        }
-        
         // find the main event - the main event has no RECURRENCE-ID
-        foreach($vcalendar->VTODO as $vtodo) {
-            if(!isset($vtodo->{"RECURRENCE-ID"})) {
+        foreach ($vcalendar->VTODO as $vtodo) {
+            if (!isset($vtodo->{"RECURRENCE-ID"})) {
                 $this->_convertVtodo($vtodo, $task, $options);
                 
                 break;
             }
         }
 
-        // if we have found no VEVENT component something went wrong, lets stop here
+        // if we have found no VTODO component something went wrong, lets stop here
         if (!isset($task)) {
-            throw new Tinebase_Exception_UnexpectedValue('no main VEVENT component found in VCALENDAR');
+            throw new Tinebase_Exception_UnexpectedValue('no main TODO component found in VCALENDAR');
         }
         
-        #// parse the event exceptions
-        #foreach($vcalendar->VTODO as $vtodo) {
-        #    if(isset($vtodo->{"RECURRENCE-ID"}) && $task->id == $vtodo->UID) {
-        #        $recurException = $this->_getRecurException($oldExdates, $vtodo);
-        #        
-        #        // initialize attendee with attendee from base events for new exceptions
-        #        // this way we can keep attendee extra values like groupmember type
-        #        // attendees which do not attend to the new exception will be removed in _convertVtodo
-        #        /*if (! $recurException->attendee instanceof Tinebase_Record_RecordSet) {
-        #            $recurException->attendee = new Tinebase_Record_RecordSet('Calendar_Model_Attender');
-        #            foreach ($task->attendee as $attendee) {
-        #                $recurException->attendee->addRecord(new Calendar_Model_Attender(array(
-        #                    'user_id'   => $attendee->user_id,
-        #                    'user_type' => $attendee->user_type,
-        #                    'role'      => $attendee->role,
-        #                    'status'    => $attendee->status
-        #                )));
-        #            }
-        #        }*/
-        #        
-        #        $this->_convertVtodo($vtodo, $recurException);
-        #            
-        #        //if(! $task->exdate instanceof Tinebase_Record_RecordSet) {
-        #        //    $task->exdate = new Tinebase_Record_RecordSet('Calendar_Model_Event');
-        #        //}
-        #        $task->exdate->addRecord($recurException);
-        #    }
-        #}
-        
         // enable filters again
         $task->bypassFilters = false;
         
         $task->isValid(true);
         
-        if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' FLAMENGO3 ' . print_r($task->toarray(),TRUE));
-        
         return $task;
     }
-    
-    /**
-     * returns VObject of input data
-     * 
-     * @param mixed $_blob
-     * @return Sabre\VObject\Component
-     */
-    public static function getVObject($_blob)
-    {
-        if ($_blob instanceof \Sabre\VObject\Component) {
-            $vcalendar = $_blob;
-        } else {
-            if (is_resource($_blob)) {
-                $_blob = stream_get_contents($_blob);
-            }
-            $vcalendar = self::readVCalBlob($_blob);
-        }
-        
-        return $vcalendar;
-    }
-    
-    /**
-     * reads vcal blob and tries to repair some parsing problems that Sabre has
-     * 
-     * @param string $blob
-     * @param integer $failcount
-     * @param integer $spacecount
-     * @param integer $lastBrokenLineNumber
-     * @param array $lastLines
-     * @throws Sabre\VObject\ParseException
-     * @return Sabre\VObject\Component
-     * 
-     * @see 0006110: handle iMIP messages from outlook
-     * @see 0007438: update Sabre library
-     * 
-     * @todo maybe we can remove this when #7438 is resolved
-     */
-    public static function readVCalBlob($blob, $failcount = 0, $spacecount = 0, $lastBrokenLineNumber = 0, $lastLines = array())
-    {
-        // convert to utf-8
-        $blob = mbConvertTo($blob);
-        
-        if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ .
-            ' ' . $blob);
-        
-        try {
-            $vcalendar = \Sabre\VObject\Reader::read($blob);
-        } catch (Sabre\VObject\ParseException $svpe) {
-            // NOTE: we try to repair \Sabre\VObject\Reader as it fails to detect followup lines that do not begin with a space or tab
-            if ($failcount < 10 && preg_match(
-                '/Invalid VObject, line ([0-9]+) did not follow the icalendar\/vcard format/', $svpe->getMessage(), $matches
-            )) {
-                if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
-                    ' ' . $svpe->getMessage() .
-                    ' lastBrokenLineNumber: ' . $lastBrokenLineNumber);
-                
-                $brokenLineNumber = $matches[1] - 1 + $spacecount;
-                
-                if ($lastBrokenLineNumber === $brokenLineNumber) {
-                    if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
-                        ' Try again: concat this line to previous line.');
-                    $lines = $lastLines;
-                    $brokenLineNumber--;
-                    // increase spacecount because one line got removed
-                    $spacecount++;
-                } else {
-                    $lines = preg_split('/[\r\n]*\n/', $blob);
-                    if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
-                        ' Concat next line to this one.');
-                    $lastLines = $lines; // for retry
-                }
-                $lines[$brokenLineNumber] .= $lines[$brokenLineNumber + 1];
-                unset($lines[$brokenLineNumber + 1]);
-                
-                if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ .
-                    ' failcount: ' . $failcount .
-                    ' brokenLineNumber: ' . $brokenLineNumber .
-                    ' spacecount: ' . $spacecount);
-                
-                $vcalendar = self::readVCalBlob(implode("\n", $lines), $failcount + 1, $spacecount, $brokenLineNumber, $lastLines);
-            } else {
-                throw $svpe;
-            }
-        }
-        
-        return $vcalendar;
-    }
-    
-    /**
-     * find a matching exdate or return an empty event record
-     * 
-     * @param  Tinebase_Record_RecordSet  $_oldExdates
-     * @param  \Sabre\VObject\Component    $_vevent
-     * @return Tasks_Model_Task
-     */
-    protected function _getRecurException(Tinebase_Record_RecordSet $_oldExdates, \Sabre\VObject\Component $_vevent)
-    {
-        $exDate = clone $_vevent->{"RECURRENCE-ID"}->getDateTime();
-        $exDate->setTimeZone(new DateTimeZone('UTC'));
-        $exDateString = $exDate->format('Y-m-d H:i:s');
-        foreach ($_oldExdates as $id => $oldExdate) {
-            if ($exDateString == substr((string) $oldExdate->recurid, -19)) {
-                unset($_oldExdates[$id]);
-                
-                return $oldExdate;
-            }
-        }
-        
-        return new Calendar_Model_Event();
-    }
-    
+
     /**
      * parse VTODO part of VCALENDAR
      * 
@@ -509,7 +257,7 @@ class Tasks_Convert_Task_VCalendar_Abstract implements Tinebase_Convert_Interfac
      */
     protected function _convertVtodo(\Sabre\VObject\Component\VTodo $_vtodo, Tasks_Model_Task $_task, $options)
     {
-        if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' vevent ' . $_vtodo->serialize());  
+        if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' vtodo ' . $_vtodo->serialize());  
         
         $task = $_task;
         
@@ -639,80 +387,12 @@ class Tasks_Convert_Task_VCalendar_Abstract implements Tinebase_Convert_Interfac
                     if (preg_match('/mailto:(?P<email>.*)/i', $property->getValue(), $matches)) {
                         // it's not possible to change the organizer by spec
                         if (empty($task->organizer)) {
-//                             $name = isset($property['CN']) ? $property['CN']->getValue() : $matches['email'];
-//                             $contact = Calendar_Model_Attender::resolveEmailToContact(array(
-//                                 'email'     => $matches['email'],
-//                                 'lastName'  => $name,
-//                             ));
                             $user = Tinebase_User::getInstance()->getUserByPropertyFromSqlBackend('accountEmailAddress', $matches['email']);
                             $task->organizer = $user ? $user->getId() : Tinebase_Core::getUser()->getId();
                         }
                     }
                     
                     break;
-
-                case 'RECURRENCE-ID':
-                    // original start of the event
-                    $task->recurid = $this->_convertToTinebaseDateTime($property);
-                    
-                    // convert recurrence id to utc
-                    $task->recurid->setTimezone('UTC');
-                    
-                    break;
-                    
-                case 'RRULE':
-                    $task->rrule = $property->getValue();
-                    
-                    // convert date format
-                    $task->rrule = preg_replace_callback('/UNTIL=([\dTZ]+)(?=;?)/', function($matches) {
-                        if (strlen($matches[1]) < 10) {
-                            $dtUntil = date_create($matches[1], new DateTimeZone ((string) Tinebase_Core::get(Tinebase_Core::USERTIMEZONE)));
-                            $dtUntil->setTimezone(new DateTimeZone('UTC'));
-                        } else {
-                            $dtUntil = date_create($matches[1]);
-                        }
-                        
-                        return 'UNTIL=' . $dtUntil->format(Tinebase_Record_Abstract::ISO8601LONG);
-                    }, $task->rrule);
-
-                    // remove additional days from BYMONTHDAY property
-                    $task->rrule = preg_replace('/(BYMONTHDAY=)([\d]+)([,\d]+)/', '$1$2', $task->rrule);
-                    
-                    // process exceptions
-                    if (isset($_vtodo->EXDATE)) {
-                        $exdates = new Tinebase_Record_RecordSet('Tasks_Model_Task');
-                        
-                        foreach($_vtodo->EXDATE as $exdate) {
-                            foreach($exdate->getDateTimes() as $exception) {
-                                if (isset($exdate['VALUE']) && strtoupper($exdate['VALUE']) == 'DATE') {
-                                    $recurid = new Tinebase_DateTime($exception->format(Tinebase_Record_Abstract::ISO8601LONG), (string) Tinebase_Core::get(Tinebase_Core::USERTIMEZONE));
-                                } else {
-                                    $recurid = new Tinebase_DateTime($exception->format(Tinebase_Record_Abstract::ISO8601LONG), $exception->getTimezone());
-                                }
-                                $recurid->setTimezone(new DateTimeZone('UTC'));
-                                                        
-                                $taskException = new Calendar_Model_Event(array(
-                                    'recurid'    => $recurid,
-                                    'is_deleted' => true
-                                ));
-                        
-                                $exdates->addRecord($taskException);
-                            }
-                        }
-                    
-                        $task->exdate = $exdates;
-                    }     
-                                   
-                    break;
-                    
-                case 'TRANSP':
-                    if (in_array($property->getValue(), array(Calendar_Model_Event::TRANSP_OPAQUE, Calendar_Model_Event::TRANSP_TRANSP))) {
-                        $task->transp = $property->getValue();
-                    } else {
-                        $task->transp = Calendar_Model_Event::TRANSP_TRANSP;
-                    }
-                    
-                    break;
                     
                 case 'UID':
                     // it's not possible to change the uid by spec
@@ -725,47 +405,7 @@ class Tasks_Convert_Task_VCalendar_Abstract implements Tinebase_Convert_Interfac
                     break;
                     
                 case 'VALARM':
-                    foreach($property as $valarm) {
-                        switch(strtoupper($valarm->TRIGGER['VALUE']->getValue())) {
-                            # TRIGGER;VALUE=DATE-TIME:20111031T130000Z
-                            case 'DATE-TIME':
-                                //@TODO fixme
-                                $alarmTime = new Tinebase_DateTime($valarm->TRIGGER->getValue());
-                                $alarmTime->setTimezone('UTC');
-                                
-                                $alarm = new Tinebase_Model_Alarm(array(
-                                    'alarm_time'        => $alarmTime,
-                                    'minutes_before'    => 'custom',
-                                    'model'             => 'Tasks_Model_Task'
-                                ));
-                                
-                                $task->alarms->addRecord($alarm);
-                                
-                                break;
-                                
-                            # TRIGGER;VALUE=DURATION:-PT1H15M
-                            case 'DURATION':
-                            default:
-                                # @todo the alarm should be based on DTSTART
-                                $alarmTime = $this->_convertToTinebaseDateTime($_vtodo->DUE);
-                                $alarmTime->setTimezone('UTC');
-                                
-                                preg_match('/(?P<invert>[+-]?)(?P<spec>P.*)/', $valarm->TRIGGER->getValue(), $matches);
-                                $duration = new DateInterval($matches['spec']);
-                                $duration->invert = !!($matches['invert'] === '-');
-
-                                $alarm = new Tinebase_Model_Alarm(array(
-                                    'alarm_time'        => $alarmTime->add($duration),
-                                    'minutes_before'    => ($duration->format('%d') * 60 * 24) + ($duration->format('%h') * 60) + ($duration->format('%i')),
-                                    'model'             => 'Tasks_Model_Task'
-                                ));
-                                
-                                $task->alarms->addRecord($alarm);
-                                
-                                break;
-                        }
-                    }
-                    
+                    $this->_parseAlarm($task, $property, $_vtodo);
                     break;
                     
                 case 'CATEGORIES':
@@ -802,34 +442,4 @@ class Tasks_Convert_Task_VCalendar_Abstract implements Tinebase_Convert_Interfac
         // convert all datetime fields to UTC
         $task->setTimezone('UTC');
     }
-    
-    /**
-     * get datetime from sabredav datetime property (user TZ is fallback)
-     * 
-     * @param  \Sabre\VObject\Property  $dateTimeProperty
-     * @param  boolean                  $_useUserTZ
-     * @return Tinebase_DateTime
-     * 
-     * @todo try to guess some common timezones
-     */
-    protected function _convertToTinebaseDateTime(\Sabre\VObject\Property $dateTimeProperty, $_useUserTZ = FALSE)
-    {
-        $defaultTimezone = date_default_timezone_get();
-        date_default_timezone_set((string) Tinebase_Core::get(Tinebase_Core::USERTIMEZONE));
-        
-        if ($dateTimeProperty instanceof Sabre\VObject\Property\ICalendar\DateTime) {
-            $dateTime = $dateTimeProperty->getDateTime();
-            $tz = ($_useUserTZ || (isset($dateTimeProperty['VALUE']) && strtoupper($dateTimeProperty['VALUE']) == 'DATE')) ? 
-                (string) Tinebase_Core::get(Tinebase_Core::USERTIMEZONE) : 
-                $dateTime->getTimezone();
-            
-            $result = new Tinebase_DateTime($dateTime->format(Tinebase_Record_Abstract::ISO8601LONG), $tz);
-        } else {
-            $result = new Tinebase_DateTime($dateTimeProperty->getValue());
-        }
-        
-        date_default_timezone_set($defaultTimezone);
-        
-        return $result;
-    }
 }
diff --git a/tine20/Tinebase/Convert/VCalendar/Abstract.php b/tine20/Tinebase/Convert/VCalendar/Abstract.php
new file mode 100644 (file)
index 0000000..3be8d45
--- /dev/null
@@ -0,0 +1,258 @@
+<?php
+/**
+ * Tine 2.0
+ *
+ * @package     Tinebase
+ * @subpackage  Convert
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Philipp Schüle <p.schuele@metaways.de>
+ * @copyright   Copyright (c) 2014 Metaways Infosystems GmbH (http://www.metaways.de)
+ *
+ */
+
+/**
+ * abstract class for VCALENDAR/VTODO/VCARD/... conversion
+ *
+ * @package     Tinebase
+ * @subpackage  Convert
+ */
+abstract class Tinebase_Convert_VCalendar_Abstract
+{
+    /**
+     * use servers modlogProperties instead of given DTSTAMP & SEQUENCE
+     * use this if the concurrency checks are done differntly like in CalDAV
+     * where the etag is checked
+     */
+    const OPTION_USE_SERVER_MODLOG = 'useServerModlog';
+    
+    protected $_supportedFields = array();
+    
+    protected $_version;
+    
+    protected $_modelName = null;
+    
+    /**
+     * @param  string  $version  the version of the client
+     */
+    public function __construct($version = null)
+    {
+        if (! $this->_modelName) {
+            throw new Tinebase_Exception('modelName is required');
+        }
+        $this->_version = $version;
+    }
+
+    /**
+     * returns VObject of input data
+     * 
+     * @param   mixed  $blob
+     * @return  \Sabre\VObject\Component\VCalendar
+     */
+    public static function getVObject($blob)
+    {
+        if ($blob instanceof \Sabre\VObject\Component\VCalendar) {
+            return $blob;
+        }
+        
+        if (is_resource($blob)) {
+            $blob = stream_get_contents($blob);
+        }
+        
+        $blob = Tinebase_Core::filterInputForDatabase($blob);
+        
+        $vcalendar = self::readVCalBlob($blob);
+        
+        return $vcalendar;
+    }
+    
+    /**
+     * reads vcal blob and tries to repair some parsing problems that Sabre has
+     *
+     * @param string $blob
+     * @param integer $failcount
+     * @param integer $spacecount
+     * @param integer $lastBrokenLineNumber
+     * @param array $lastLines
+     * @throws Sabre\VObject\ParseException
+     * @return Sabre\VObject\Component\VCalendar
+     *
+     * @see 0006110: handle iMIP messages from outlook
+     *
+     * @todo maybe we can remove this when #7438 is resolved
+     */
+    public static function readVCalBlob($blob, $failcount = 0, $spacecount = 0, $lastBrokenLineNumber = 0, $lastLines = array())
+    {
+        // convert to utf-8
+        $blob = mbConvertTo($blob);
+    
+        if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ .
+                ' ' . $blob);
+    
+        try {
+            $vcalendar = \Sabre\VObject\Reader::read($blob);
+        } catch (Sabre\VObject\ParseException $svpe) {
+            // NOTE: we try to repair\Sabre\VObject\Reader as it fails to detect followup lines that do not begin with a space or tab
+            if ($failcount < 10 && preg_match(
+                    '/Invalid VObject, line ([0-9]+) did not follow the icalendar\/vcard format/', $svpe->getMessage(), $matches
+            )) {
+                if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
+                        ' ' . $svpe->getMessage() .
+                        ' lastBrokenLineNumber: ' . $lastBrokenLineNumber);
+    
+                $brokenLineNumber = $matches[1] - 1 + $spacecount;
+    
+                if ($lastBrokenLineNumber === $brokenLineNumber) {
+                    if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
+                            ' Try again: concat this line to previous line.');
+                    $lines = $lastLines;
+                    $brokenLineNumber--;
+                    // increase spacecount because one line got removed
+                    $spacecount++;
+                } else {
+                    $lines = preg_split('/[\r\n]*\n/', $blob);
+                    if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
+                            ' Concat next line to this one.');
+                    $lastLines = $lines; // for retry
+                }
+                $lines[$brokenLineNumber] .= $lines[$brokenLineNumber + 1];
+                unset($lines[$brokenLineNumber + 1]);
+    
+                if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ .
+                        ' failcount: ' . $failcount .
+                        ' brokenLineNumber: ' . $brokenLineNumber .
+                        ' spacecount: ' . $spacecount);
+    
+                $vcalendar = self::readVCalBlob(implode("\n", $lines), $failcount + 1, $spacecount, $brokenLineNumber, $lastLines);
+            } else {
+                throw $svpe;
+            }
+        }
+    
+        return $vcalendar;
+    }
+    
+    /**
+     * to be overwriten in extended classes to modify/cleanup $_vcalendar
+     *
+     * @param \Sabre\VObject\Component\VCalendar $vcalendar
+     */
+    protected function _afterFromTine20Model(\Sabre\VObject\Component\VCalendar $vcalendar)
+    {
+    }
+    
+    /**
+     * parse valarm properties
+     * 
+     * @param Tinebase_Record_Abstract $record
+     * @param iteratable $valarms
+     * @param \Sabre\VObject\Component $vcalendar
+     */
+    protected function _parseAlarm(Tinebase_Record_Abstract $record, $valarms, \Sabre\VObject\Component $vcomponent)
+    {
+        foreach ($valarms as $valarm) {
+            
+            if ($valarm->ACTION == 'NONE') {
+                if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
+                        . ' We can\'t cope with action NONE: iCal 6.0 sends default alarms in the year 1976 with action NONE. Skipping alarm.');
+                continue;
+            }
+            
+            if (! is_object($valarm->TRIGGER)) {
+                if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
+                . ' Alarm has no TRIGGER value. Skipping it.');
+                continue;
+            }
+            
+            # TRIGGER:-PT15M
+            if (is_string($valarm->TRIGGER->getValue()) && $valarm->TRIGGER instanceof Sabre\VObject\Property\ICalendar\Duration) {
+                if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
+                . ' Adding DURATION trigger value for ' . $valarm->TRIGGER->getValue());
+                $valarm->TRIGGER->add('VALUE', 'DURATION');
+            }
+            
+            $trigger = is_object($valarm->TRIGGER['VALUE']) ? $valarm->TRIGGER['VALUE'] : (is_object($valarm->TRIGGER['RELATED']) ? $valarm->TRIGGER['RELATED'] : NULL);
+            
+            if ($trigger === NULL) {
+                // added Trigger/Related for eM Client alarms
+                // 2014-01-03 - Bullshit, why don't we have testdata for emclient alarms?
+                        //              this alarm handling should be refactored, the logic is scrambled
+                // @see 0006110: handle iMIP messages from outlook
+                // @todo fix 0007446: handle broken alarm in outlook invitation message
+                if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
+                . ' Alarm has no TRIGGER value. Skipping it.');
+                continue;
+            }
+            
+            switch (strtoupper($trigger->getValue())) {
+                # TRIGGER;VALUE=DATE-TIME:20111031T130000Z
+                case 'DATE-TIME':
+                    $alarmTime = new Tinebase_DateTime($valarm->TRIGGER->getValue());
+                    $alarmTime->setTimezone('UTC');
+                    
+                    $alarm = new Tinebase_Model_Alarm(array(
+                        'alarm_time'        => $alarmTime,
+                        'minutes_before'    => 'custom',
+                        'model'             => $this->_modelName,
+                    ));
+                    
+                    break;
+                
+                # TRIGGER;VALUE=DURATION:-PT1H15M
+                case 'DURATION':
+                default:
+                    $durationBaseTime = isset($vcomponent->DTSTART) ? $vcomponent->DTSTART : $vcomponent->DUE;
+                    $alarmTime = $this->_convertToTinebaseDateTime($durationBaseTime);
+                    $alarmTime->setTimezone('UTC');
+                    
+                    preg_match('/(?P<invert>[+-]?)(?P<spec>P.*)/', $valarm->TRIGGER->getValue(), $matches);
+                    $duration = new DateInterval($matches['spec']);
+                    $duration->invert = !!($matches['invert'] === '-');
+                    
+                    $alarm = new Tinebase_Model_Alarm(array(
+                        'alarm_time'        => $alarmTime->add($duration),
+                        'minutes_before'    => ($duration->format('%d') * 60 * 24) + ($duration->format('%h') * 60) + ($duration->format('%i')),
+                        'model'             => $this->_modelName,
+                    ));
+                    if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
+                        . ' Adding DURATION alarm ' . print_r($alarm->toArray(), true));
+            }
+            
+            if ($valarm->ACKNOWLEDGED) {
+                $dtack = $valarm->ACKNOWLEDGED->getDateTime();
+                Calendar_Controller_Alarm::setAcknowledgeTime($alarm, $dtack);
+            }
+            
+            $record->alarms->addRecord($alarm);
+        }
+    }
+    
+    /**
+     * get datetime from sabredav datetime property (user TZ is fallback)
+     * 
+     * @param  Sabre\VObject\Property  $dateTimeProperty
+     * @param  boolean                 $_useUserTZ
+     * @return Tinebase_DateTime
+     * 
+     * @todo try to guess some common timezones
+     */
+    protected function _convertToTinebaseDateTime(\Sabre\VObject\Property $dateTimeProperty, $_useUserTZ = FALSE)
+    {
+        $defaultTimezone = date_default_timezone_get();
+        date_default_timezone_set((string) Tinebase_Core::get(Tinebase_Core::USERTIMEZONE));
+        
+        if ($dateTimeProperty instanceof Sabre\VObject\Property\ICalendar\DateTime) {
+            $dateTime = $dateTimeProperty->getDateTime();
+            $tz = ($_useUserTZ || (isset($dateTimeProperty['VALUE']) && strtoupper($dateTimeProperty['VALUE']) == 'DATE')) ? 
+                (string) Tinebase_Core::get(Tinebase_Core::USERTIMEZONE) : 
+                $dateTime->getTimezone();
+            
+            $result = new Tinebase_DateTime($dateTime->format(Tinebase_Record_Abstract::ISO8601LONG), $tz);
+        } else {
+            $result = new Tinebase_DateTime($dateTimeProperty->getValue());
+        }
+        
+        date_default_timezone_set($defaultTimezone);
+        
+        return $result;
+    }
+}