/**
  * testXMozSnooze
  * 
  * @see 0007682: CalDav - Tine - Thunderbird - Palm Pre
  */
 public function testXMozSnooze()
 {
     $vcalendarStream = Calendar_Frontend_WebDAV_EventTest::getVCalendar(dirname(__FILE__) . '/../../../Import/files/lightning_snooze.ics');
     $converter = Calendar_Convert_Event_VCalendar_Factory::factory(Calendar_Convert_Event_VCalendar_Factory::CLIENT_THUNDERBIRD);
     $event = $converter->toTine20Model($vcalendarStream);
     $alarmSnoozeTime = Calendar_Controller_Alarm::getSnoozeTime($event->alarms->getFirstRecord());
     $this->assertTrue($alarmSnoozeTime instanceof DateTime);
     $this->assertEquals('2013-04-12 06:24:46', $alarmSnoozeTime->toString());
 }
 /**
  * test converting vcard from apple iCal to Calendar_Model_Event
  */
 public function testConvertToTine20Model()
 {
     $vcalendarStream = fopen(dirname(__FILE__) . '/../../../Import/files/apple_caldendar_mavericks_organizer_only.ics', 'r');
     $converter = Calendar_Convert_Event_VCalendar_Factory::factory(Calendar_Convert_Event_VCalendar_Factory::CLIENT_MACOSX, '10.9');
     $event = $converter->toTine20Model($vcalendarStream);
     // assert testuser is not attendee
     $this->assertEquals(1, $event->attendee->count(), 'there sould only be one attendee');
     $this->assertNotEquals($event->organizer, $event->attendee[0]->user_id, 'organizer should not attend');
     // assert alarm
     $this->assertEquals(1, $event->alarms->count(), 'there should be exactly one alarm');
     $this->assertFalse((bool) $event->alarms->getFirstRecord()->getOption('custom'), 'alarm should be duration alarm');
     $this->assertEquals(15, $event->alarms->getFirstRecord()->minutes_before, 'alarm should be 15 min. before');
     $this->assertEquals('2013-11-15 11:47:23', Calendar_Controller_Alarm::getAcknowledgeTime($event->alarms->getFirstRecord())->format(Tinebase_Record_Abstract::ISO8601LONG), 'ACKNOWLEDGED was not imported properly');
 }
 /**
  * converts an iTIP event to a tine20 event
  * 
  * @param Calendar_Model_Event $_event
  * @param Calendar_Model_Event $_currentEvent (not iTIP!)
  */
 protected function _fromiTIP($_event, $_currentEvent)
 {
     if (!$_event->rrule) {
         $_event->exdate = NULL;
     }
     if ($_event->exdate instanceof Tinebase_Record_RecordSet) {
         try {
             $currExdates = $this->_eventController->getRecurExceptions($_event, TRUE);
             $this->getAlarms($currExdates);
             $currClientExdates = $this->_eventController->getRecurExceptions($_event, TRUE, $this->getEventFilter());
             $this->getAlarms($currClientExdates);
         } catch (Tinebase_Exception_NotFound $e) {
             $currExdates = NULL;
             $currClientExdates = NULL;
         }
         foreach ($_event->exdate as $idx => $exdate) {
             try {
                 $this->_prepareException($_event, $exdate);
             } catch (Exception $e) {
             }
             $currExdate = $currExdates instanceof Tinebase_Record_RecordSet ? $currExdates->filter('recurid', $exdate->recurid)->getFirstRecord() : NULL;
             if ($exdate->is_deleted) {
                 // reset implicit filter fallouts and mark as don't touch (seq = -1)
                 $currClientExdate = $currClientExdates instanceof Tinebase_Record_RecordSet ? $currClientExdates->filter('recurid', $exdate->recurid)->getFirstRecord() : NULL;
                 if ($currClientExdate && $currClientExdate->is_deleted) {
                     $_event->exdate[$idx] = $currExdate;
                     $currExdate->seq = -1;
                     continue;
                 }
             }
             $this->_fromiTIP($exdate, $currExdate ? $currExdate : clone $_currentEvent);
         }
     }
     // assert organizer
     $_event->organizer = $_event->organizer ?: ($_currentEvent->organizer ?: $this->_calendarUser->user_id);
     $this->_addAttendeeWithoutEmail($_event, $_currentEvent);
     $CUAttendee = Calendar_Model_Attender::getAttendee($_event->attendee, $this->_calendarUser);
     $currentCUAttendee = Calendar_Model_Attender::getAttendee($_currentEvent->attendee, $this->_calendarUser);
     $isOrganizer = $_event->isOrganizer($this->_calendarUser);
     // remove perspective
     if ($CUAttendee && !$isOrganizer) {
         $CUAttendee->transp = $_event->transp;
         $_event->transp = $_currentEvent->transp ? $_currentEvent->transp : $_event->transp;
     }
     // apply changes to original alarms
     $_currentEvent->alarms = $_currentEvent->alarms instanceof Tinebase_Record_RecordSet ? $_currentEvent->alarms : new Tinebase_Record_RecordSet('Tinebase_Model_Alarm');
     $_event->alarms = $_event->alarms instanceof Tinebase_Record_RecordSet ? $_event->alarms : new Tinebase_Record_RecordSet('Tinebase_Model_Alarm');
     foreach ($_currentEvent->alarms as $currentAlarm) {
         if (Calendar_Model_Attender::isAlarmForAttendee($this->_calendarUser, $currentAlarm)) {
             $alarmUpdate = Calendar_Controller_Alarm::getMatchingAlarm($_event->alarms, $currentAlarm);
             if ($alarmUpdate) {
                 // we could map the alarm => save ack & snooze options
                 if ($dtAck = Calendar_Controller_Alarm::getAcknowledgeTime($alarmUpdate)) {
                     Calendar_Controller_Alarm::setAcknowledgeTime($currentAlarm, $dtAck, $this->getCalendarUser()->user_id);
                 }
                 if ($dtSnooze = Calendar_Controller_Alarm::getSnoozeTime($alarmUpdate)) {
                     Calendar_Controller_Alarm::setSnoozeTime($currentAlarm, $dtSnooze, $this->getCalendarUser()->user_id);
                 }
                 $_event->alarms->removeRecord($alarmUpdate);
             } else {
                 // alarm is to be skiped/deleted
                 if (!$currentAlarm->getOption('attendee')) {
                     Calendar_Controller_Alarm::skipAlarm($currentAlarm, $this->_calendarUser);
                 } else {
                     $_currentEvent->alarms->removeRecord($currentAlarm);
                 }
             }
         }
     }
     if (!$isOrganizer) {
         $_event->alarms->setOption('attendee', Calendar_Controller_Alarm::attendeeToOption($this->_calendarUser));
     }
     $_event->alarms->merge($_currentEvent->alarms);
     // assert organizer for personal calendars to be calendar owner
     if ($this->_currentEventFacadeContainer && $this->_currentEventFacadeContainer->getId() == $_event->container_id && $this->_currentEventFacadeContainer->type == Tinebase_Model_Container::TYPE_PERSONAL && !$_event->hasExternalOrganizer()) {
         $_event->organizer = $this->_calendarUser->user_id;
     }
     // in MS world only cal_user can do status updates
     if ($CUAttendee) {
         $CUAttendee->status_authkey = $currentCUAttendee ? $currentCUAttendee->status_authkey : NULL;
     }
 }
 /**
  * handle alarms / Reminder
  * 
  * @param SimpleXMLElement $xmlData
  * @param Calendar_Model_Event $event
  */
 protected function _handleAlarms($data, $event)
 {
     // NOTE: existing alarms are already filtered for CU by MSEF
     $event->alarms = $event->alarms instanceof Tinebase_Record_RecordSet ? $event->alarms : new Tinebase_Record_RecordSet('Tinebase_Model_Alarm');
     $event->alarms->sort('alarm_time');
     $currentAlarm = $event->alarms->getFirstRecord();
     $alarm = NULL;
     if (isset($data->reminder)) {
         $dtstart = clone $event->dtstart;
         $alarm = new Tinebase_Model_Alarm(array('alarm_time' => $dtstart->subMinute($data->reminder), 'minutes_before' => in_array($data->reminder, array(0, 5, 15, 30, 60, 120, 720, 1440, 2880)) ? $data->reminder : 'custom', 'model' => 'Calendar_Model_Event'));
         $alarmUpdate = Calendar_Controller_Alarm::getMatchingAlarm($event->alarms, $alarm);
         if (!$alarmUpdate) {
             // alarm not existing -> add it
             $event->alarms->addRecord($alarm);
             if ($currentAlarm) {
                 // ActiveSync supports one alarm only -> current got deleted
                 $event->alarms->removeRecord($currentAlarm);
             }
         }
     } else {
         if ($currentAlarm) {
             // current alarm got removed
             $event->alarms->removeRecord($currentAlarm);
         }
     }
 }
 /**
  * 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
             # 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);
     }
 }
 /**
  * testAlarmAckInRecurException
  * 
  * @see 0009396: alarm_ack_time and alarm_snooze_time are not updated
  */
 public function testAlarmAckInRecurException()
 {
     $event = $this->testCreate();
     // save event as sclever to ack sclevers alarm
     Tinebase_Core::set(Tinebase_Core::USER, $this->_personas['sclever']);
     $exdateAlarms = $event->exdate->getFirstRecord()->alarms;
     $ackTime = Tinebase_DateTime::now();
     $scleverAlarm = new Tinebase_Model_Alarm(array('model' => 'Calendar_Model_Event', 'alarm_time' => $ackTime, 'minutes_before' => 90));
     $ackAlarm = Calendar_Controller_Alarm::getMatchingAlarm($exdateAlarms, $scleverAlarm);
     Calendar_Controller_Alarm::setAcknowledgeTime($ackAlarm, $ackTime);
     $updatedEvent = $this->_uit->update($event);
     $this->assertEquals(3, count($updatedEvent->alarms));
     $updatedAlarm = Calendar_Controller_Alarm::getMatchingAlarm($updatedEvent->exdate->getFirstRecord()->alarms, $scleverAlarm);
     $this->assertEquals($ackTime, Calendar_Controller_Alarm::getAcknowledgeTime($updatedAlarm));
     // check alarm ack client + ip
     $accessLog = Tinebase_Core::get(Tinebase_Core::USERACCESSLOG);
     $this->assertEquals($accessLog->ip, $updatedAlarm->getOption(Tinebase_Model_Alarm::OPTION_ACK_IP), 'ip not found in options: ' . print_r($updatedAlarm->toArray(), true));
     $expectedClient = 'type: ' . $accessLog->clienttype . '|useragent: ' . $_SERVER['HTTP_USER_AGENT'];
     $this->assertEquals($expectedClient, $updatedAlarm->getOption(Tinebase_Model_Alarm::OPTION_ACK_CLIENT), 'clienttype not found in options: ' . print_r($updatedAlarm->toArray(), true));
 }
 /**
  * print alarm acknowledgement report (when, ip, client, user, ...)
  * 
  * @param Zend_Console_Getopt $_opts
  */
 public function alarmAckReport(Zend_Console_Getopt $_opts)
 {
     $until = Tinebase_DateTime::now();
     $from = Tinebase_DateTime::now()->subDay(1);
     // get all events for today for current user
     $filter = new Calendar_Model_EventFilter(array(array('field' => 'attender', 'operator' => 'in', 'value' => array(array('user_type' => 'user', 'user_id' => Tinebase_Core::getUser()->contact_id))), array('field' => 'attender_status', 'operator' => 'not', 'value' => Calendar_Model_Attender::STATUS_DECLINED), array('field' => 'period', 'operator' => 'within', 'value' => array("from" => $from, "until" => $until))));
     $events = Calendar_Controller_Event::getInstance()->search($filter);
     Calendar_Model_Rrule::mergeAndRemoveNonMatchingRecurrences($events, $filter);
     Calendar_Controller_Event::getInstance()->getAlarms($events);
     echo "Reporting alarms for events of user " . Tinebase_Core::getUser()->accountLoginName . " (All times in UTC) from " . $from->toString() . ' until ' . $until->toString() . "...\n\n";
     // loop alarms and print alarm ack info
     foreach ($events as $event) {
         if (count($event->alarms) > 0) {
             $this->_printShortEvent($event);
             foreach ($event->alarms as $alarm) {
                 echo "  Alarm " . $alarm->alarm_time . "\n";
                 $ackTime = Calendar_Controller_Alarm::getAcknowledgeTime($alarm);
                 if (empty($ackTime)) {
                     echo "    not acknowledged!";
                 } else {
                     if (is_array($ackTime)) {
                         $ackTime = array_pop($ackTime);
                     }
                     echo "    acknowledged " . $ackTime->toString() . "\n    IP -> " . $alarm->getOption(Tinebase_Model_Alarm::OPTION_ACK_IP) . "\n    Client ->" . $alarm->getOption(Tinebase_Model_Alarm::OPTION_ACK_CLIENT) . "\n";
                 }
                 echo "\n";
             }
             echo "\n";
         }
     }
 }
 /**
  * updates an attender status of a event
  * 
  * @param  Calendar_Model_Event    $_event
  * @param  Calendar_Model_Attender $_attender
  * @param  string                  $_authKey
  * @return Calendar_Model_Attender updated attender
  */
 public function attenderStatusUpdate(Calendar_Model_Event $_event, Calendar_Model_Attender $_attender, $_authKey)
 {
     try {
         $event = $this->get($_event->getId());
         if (!$event->attendee) {
             throw new Tinebase_Exception_NotFound('Could not find any attendee of event.');
         }
         if (($currentAttender = Calendar_Model_Attender::getAttendee($event->attendee, $_attender)) == null) {
             throw new Tinebase_Exception_NotFound('Could not find attender in event.');
         }
         $updatedAttender = clone $currentAttender;
         if ($currentAttender->status_authkey !== $_authKey) {
             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
                 Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " no permissions to update status for {$currentAttender->user_type} {$currentAttender->user_id}");
             }
             return $updatedAttender;
         }
         Calendar_Controller_Alarm::enforceACL($_event, $event);
         $currentAttenderDisplayContainerId = $currentAttender->displaycontainer_id instanceof Tinebase_Model_Container ? $currentAttender->displaycontainer_id->getId() : $currentAttender->displaycontainer_id;
         $attenderDisplayContainerId = $_attender->displaycontainer_id instanceof Tinebase_Model_Container ? $_attender->displaycontainer_id->getId() : $_attender->displaycontainer_id;
         // check if something what can be set as user has changed
         if ($currentAttender->status == $_attender->status && $currentAttenderDisplayContainerId == $attenderDisplayContainerId && $currentAttender->transp == $_attender->transp && !Calendar_Controller_Alarm::hasUpdates($_event, $event)) {
             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
                 Tinebase_Core::getLogger()->DEBUG(__METHOD__ . '::' . __LINE__ . "no status change -> do nothing");
             }
             return $updatedAttender;
         }
         $updatedAttender->status = $_attender->status;
         $updatedAttender->displaycontainer_id = isset($_attender->displaycontainer_id) ? $_attender->displaycontainer_id : $updatedAttender->displaycontainer_id;
         $updatedAttender->transp = isset($_attender->transp) ? $_attender->transp : Calendar_Model_Event::TRANSP_OPAQUE;
         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
             Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " update attender status to {$_attender->status} for {$currentAttender->user_type} {$currentAttender->user_id}");
             Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' set alarm_ack_time / alarm_snooze_time: ' . $updatedAttender->alarm_ack_time . ' / ' . $updatedAttender->alarm_snooze_time);
         }
         $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction(Tinebase_Core::getDb());
         $updatedAttender = $this->_backend->updateAttendee($updatedAttender);
         if ($_event->alarms instanceof Tinebase_Record_RecordSet) {
             foreach ($_event->alarms as $alarm) {
                 $this->_inspectAlarmSet($event, $alarm);
             }
             Tinebase_Alarm::getInstance()->setAlarmsOfRecord($_event);
         }
         $this->_increaseDisplayContainerContentSequence($updatedAttender, $event);
         if ($currentAttender->status != $updatedAttender->status) {
             $this->_touch($event, TRUE);
         }
         Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
     } catch (Exception $e) {
         Tinebase_TransactionManager::getInstance()->rollBack();
         throw $e;
     }
     // send notifications
     if ($currentAttender->status != $updatedAttender->status && $this->_sendNotifications && $_event->mute != 1) {
         $updatedEvent = $this->get($event->getId());
         $this->doSendNotifications($updatedEvent, Tinebase_Core::getUser(), 'changed', $event);
     }
     return $updatedAttender;
 }
 /**
  * parse VEVENT part of VCALENDAR
  * 
  * @param  \Sabre\VObject\Component\VEvent  $vevent  the VEVENT to parse
  * @param  Calendar_Model_Event             $event   the Tine 2.0 event to update
  * @param  array                            $options
  */
 protected function _convertVevent(\Sabre\VObject\Component\VEvent $vevent, Calendar_Model_Event $event, $options)
 {
     if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) {
         Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' vevent ' . $vevent->serialize());
     }
     $newAttendees = array();
     $attachments = new Tinebase_Record_RecordSet('Tinebase_Model_Tree_Node');
     $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm');
     $skipFieldsIfOnlyBasicData = array('ATTENDEE', 'UID', 'ORGANIZER', 'VALARM', 'ATTACH', 'CATEGORIES');
     foreach ($vevent->children() as $property) {
         if (isset($this->_options['onlyBasicData']) && $this->_options['onlyBasicData'] && in_array((string) $property->name, $skipFieldsIfOnlyBasicData)) {
             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
                 Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Skipping ' . $property->name . ' (using option onlyBasicData)');
             }
             continue;
         }
         switch ($property->name) {
             case 'CREATED':
             case 'DTSTAMP':
                 if (!isset($options[self::OPTION_USE_SERVER_MODLOG]) || $options[self::OPTION_USE_SERVER_MODLOG] !== true) {
                     $event->{$property->name == 'CREATED' ? 'creation_time' : 'last_modified_time'} = $this->_convertToTinebaseDateTime($property);
                 }
                 break;
             case 'LAST-MODIFIED':
                 $event->last_modified_time = new Tinebase_DateTime($property->getValue());
                 break;
             case 'ATTENDEE':
                 $newAttendee = $this->_getAttendee($property);
                 if ($newAttendee) {
                     $newAttendees[] = $newAttendee;
                 }
                 break;
             case 'CLASS':
                 if (in_array($property->getValue(), array(Calendar_Model_Event::CLASS_PRIVATE, Calendar_Model_Event::CLASS_PUBLIC))) {
                     $event->class = $property->getValue();
                 } else {
                     $event->class = Calendar_Model_Event::CLASS_PUBLIC;
                 }
                 break;
             case 'STATUS':
                 if (in_array($property->getValue(), array(Calendar_Model_Event::STATUS_CONFIRMED, Calendar_Model_Event::STATUS_TENTATIVE, Calendar_Model_Event::STATUS_CANCELED))) {
                     $event->status = $property->getValue();
                 } else {
                     if ($property->getValue() == 'CANCELLED') {
                         $event->status = Calendar_Model_Event::STATUS_CANCELED;
                     } else {
                         $event->status = Calendar_Model_Event::STATUS_CONFIRMED;
                     }
                 }
                 break;
             case 'DTEND':
                 if (isset($property['VALUE']) && strtoupper($property['VALUE']) == 'DATE') {
                     // all day event
                     $event->is_all_day_event = true;
                     $dtend = $this->_convertToTinebaseDateTime($property, TRUE);
                     // whole day events ends at 23:59:59 in Tine 2.0 but 00:00 the next day in vcalendar
                     $dtend->subSecond(1);
                 } else {
                     $event->is_all_day_event = false;
                     $dtend = $this->_convertToTinebaseDateTime($property);
                 }
                 $event->dtend = $dtend;
                 break;
             case 'DTSTART':
                 if (isset($property['VALUE']) && strtoupper($property['VALUE']) == 'DATE') {
                     // all day event
                     $event->is_all_day_event = true;
                     $dtstart = $this->_convertToTinebaseDateTime($property, TRUE);
                 } else {
                     $event->is_all_day_event = false;
                     $dtstart = $this->_convertToTinebaseDateTime($property);
                 }
                 $event->originator_tz = $dtstart->getTimezone()->getName();
                 $event->dtstart = $dtstart;
                 break;
             case 'SEQUENCE':
                 if (!isset($options[self::OPTION_USE_SERVER_MODLOG]) || $options[self::OPTION_USE_SERVER_MODLOG] !== true) {
                     $event->seq = $property->getValue();
                 }
                 // iMIP only
                 $event->external_seq = $property->getValue();
                 break;
             case 'DESCRIPTION':
             case 'LOCATION':
             case 'SUMMARY':
                 $key = strtolower($property->name);
                 $value = $property->getValue();
                 $event->{$key} = $value;
                 break;
             case 'ORGANIZER':
                 $email = null;
                 if (!empty($property['EMAIL'])) {
                     $email = $property['EMAIL'];
                 } elseif (preg_match('/mailto:(?P<email>.*)/i', $property->getValue(), $matches)) {
                     $email = $matches['email'];
                 }
                 if ($email !== null) {
                     // it's not possible to change the organizer by spec
                     if (empty($event->organizer)) {
                         $name = isset($property['CN']) ? $property['CN']->getValue() : $email;
                         $contact = Calendar_Model_Attender::resolveEmailToContact(array('email' => $email, 'lastName' => $name));
                         $event->organizer = $contact->getId();
                     }
                     // Lightning attaches organizer ATTENDEE properties to ORGANIZER property and does not add an ATTENDEE for the organizer
                     if (isset($property['PARTSTAT'])) {
                         $newAttendees[] = $this->_getAttendee($property);
                     }
                 }
                 break;
             case 'RECURRENCE-ID':
                 // original start of the event
                 $event->recurid = $this->_convertToTinebaseDateTime($property);
                 // convert recurrence id to utc
                 $event->recurid->setTimezone('UTC');
                 break;
             case 'RRULE':
                 $rruleString = $property->getValue();
                 // convert date format
                 $rruleString = preg_replace_callback('/UNTIL=([\\dTZ]+)(?=;?)/', function ($matches) {
                     $dtUntil = Calendar_Convert_Event_VCalendar_Abstract::getUTCDateFromStringInUsertime($matches[1]);
                     return 'UNTIL=' . $dtUntil->format(Tinebase_Record_Abstract::ISO8601LONG);
                 }, $rruleString);
                 // remove additional days from BYMONTHDAY property (BYMONTHDAY=11,15 => BYMONTHDAY=11)
                 $rruleString = preg_replace('/(BYMONTHDAY=)([\\d]+),([,\\d]+)/', '$1$2', $rruleString);
                 $event->rrule = $rruleString;
                 // process exceptions
                 if (isset($vevent->EXDATE)) {
                     $exdates = new Tinebase_Record_RecordSet('Calendar_Model_Event');
                     foreach ($vevent->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::getUserTimezone());
                             } else {
                                 $recurid = new Tinebase_DateTime($exception->format(Tinebase_Record_Abstract::ISO8601LONG), $exception->getTimezone());
                             }
                             $recurid->setTimezone(new DateTimeZone('UTC'));
                             $eventException = new Calendar_Model_Event(array('recurid' => $recurid, 'is_deleted' => true));
                             $exdates->addRecord($eventException);
                         }
                     }
                     $event->exdate = $exdates;
                 }
                 break;
             case 'TRANSP':
                 if (in_array($property->getValue(), array(Calendar_Model_Event::TRANSP_OPAQUE, Calendar_Model_Event::TRANSP_TRANSP))) {
                     $event->transp = $property->getValue();
                 } else {
                     $event->transp = Calendar_Model_Event::TRANSP_TRANSP;
                 }
                 break;
             case 'UID':
                 // it's not possible to change the uid by spec
                 if (!empty($event->uid)) {
                     continue;
                 }
                 $event->uid = $property->getValue();
                 break;
             case 'VALARM':
                 $this->_parseAlarm($event, $property, $vevent);
                 break;
             case 'CATEGORIES':
                 $tags = Tinebase_Model_Tag::resolveTagNameToTag($property->getParts(), 'Calendar');
                 if (!isset($event->tags)) {
                     $event->tags = $tags;
                 } else {
                     $event->tags->merge($tags);
                 }
                 break;
             case 'ATTACH':
                 $name = (string) $property['FILENAME'];
                 $managedId = (string) $property['MANAGED-ID'];
                 $value = (string) $property['VALUE'];
                 $attachment = NULL;
                 $readFromURL = false;
                 $url = '';
                 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
                     Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' attachment found: ' . $name . ' ' . $managedId);
                 }
                 if ($managedId) {
                     $attachment = $event->attachments instanceof Tinebase_Record_RecordSet ? $event->attachments->filter('hash', $property['MANAGED-ID'])->getFirstRecord() : NULL;
                     // NOTE: we might miss a attachment here for the following reasons
                     //       1. client reuses a managed id (we are server):
                     //          We havn't observerd this yet. iCal client reuse manged id's
                     //          from base events in exceptions but this is covered as we
                     //          initialize new exceptions with base event attachments
                     //
                     //          When a client reuses a managed id it's not clear yet if
                     //          this managed id needs to be in the same series/calendar/server
                     //
                     //          As we use the object hash the managed id might be used in the
                     //          same files with different names. We need to evaluate the name
                     //          (if attached) in this case as well.
                     //
                     //       2. server send his managed id (we are client)
                     //          * we need to download the attachment (here?)
                     //          * we need to have a mapping externalid / internalid (where?)
                     if (!$attachment) {
                         $readFromURL = true;
                         $url = $property->getValue();
                     } else {
                         $attachments->addRecord($attachment);
                     }
                 } elseif ('URI' === $value) {
                     /*
                     * ATTACH;VALUE=URI:https://server.com/calendars/__uids__/0AA0
                      3A3B-F7B6-459A-AB3E-4726E53637D0/dropbox/4971F93F-8657-412B-841A-A0FD913
                      9CD61.dropbox/Canada.png
                     */
                     $url = $property->getValue();
                     $urlParts = parse_url($url);
                     $host = $urlParts['host'];
                     $name = pathinfo($urlParts['path'], PATHINFO_BASENAME);
                     // iCal 10.7 places URI before uploading
                     if (parse_url(Tinebase_Core::getHostname(), PHP_URL_HOST) != $host) {
                         $readFromURL = true;
                     }
                 } else {
                     // @TODO: implement (check if add / update / update is needed)
                     if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) {
                         Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' attachment found that could not be imported due to missing managed id');
                     }
                 }
                 if ($readFromURL) {
                     if (preg_match('#^(https?://)(.*)$#', str_replace(array("\n", "\r"), '', $url), $matches)) {
                         // we are client and found an external hosted attachment that we need to import
                         $userCredentialCache = Tinebase_Core::getUserCredentialCache();
                         $url = $matches[1] . $userCredentialCache->username . ':' . $userCredentialCache->password . '@' . $matches[2];
                         $attachmentInfo = $matches[1] . $matches[2] . ' ' . $name . ' ' . $managedId;
                         if (Tinebase_Helper::urlExists($url)) {
                             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
                                 Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Downloading attachment: ' . $attachmentInfo);
                             }
                             $stream = fopen($url, 'r');
                             $attachment = new Tinebase_Model_Tree_Node(array('name' => rawurldecode($name), 'type' => Tinebase_Model_Tree_Node::TYPE_FILE, 'contenttype' => (string) $property['FMTTYPE'], 'tempFile' => $stream), true);
                             $attachments->addRecord($attachment);
                         } else {
                             if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) {
                                 Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Url not found (got 404): ' . $attachmentInfo . ' - Skipping attachment');
                             }
                         }
                     } else {
                         if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) {
                             Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Attachment found with malformed url: ' . $url);
                         }
                     }
                 }
                 break;
             case 'X-MOZ-LASTACK':
                 $lastAck = $this->_convertToTinebaseDateTime($property);
                 break;
             case 'X-MOZ-SNOOZE-TIME':
                 $snoozeTime = $this->_convertToTinebaseDateTime($property);
                 break;
             default:
                 // thunderbird saves snooze time for recurring event occurrences in properties with names like this -
                 // we just assume that the event/recur series has only one snooze time
                 if (preg_match('/^X-MOZ-SNOOZE-TIME-[0-9]+$/', $property->name)) {
                     $snoozeTime = $this->_convertToTinebaseDateTime($property);
                     if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
                         Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Found snooze time for recur occurrence: ' . $snoozeTime->toString());
                     }
                 }
                 break;
         }
     }
     // NOTE: X-CALENDARSERVER-ACCESS overwrites CLASS
     if (isset($vevent->{'X-CALENDARSERVER-ACCESS'})) {
         $event->class = $vevent->{'X-CALENDARSERVER-ACCESS'} == 'PUBLIC' ? Calendar_Model_Event::CLASS_PUBLIC : Calendar_Model_Event::CLASS_PRIVATE;
     }
     if (isset($lastAck)) {
         Calendar_Controller_Alarm::setAcknowledgeTime($event->alarms, $lastAck);
     }
     if (isset($snoozeTime)) {
         Calendar_Controller_Alarm::setSnoozeTime($event->alarms, $snoozeTime);
     }
     // merge old and new attendee
     Calendar_Model_Attender::emailsToAttendee($event, $newAttendees);
     if (empty($event->seq)) {
         $event->seq = 1;
     }
     if (empty($event->class)) {
         $event->class = Calendar_Model_Event::CLASS_PUBLIC;
     }
     $this->_manageAttachmentsFromClient($event, $attachments);
     if (empty($event->dtend)) {
         if (empty($event->dtstart)) {
             throw new Tinebase_Exception_UnexpectedValue("Got event without dtstart and dtend");
         }
         // TODO find out duration (see TRIGGER DURATION)
         //             if (isset($vevent->DURATION)) {
         //             }
         if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) {
             Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' Got event without dtend. Assuming 30 minutes duration');
         }
         $event->dtend = clone $event->dtstart;
         $event->dtend->addMinute(30);
     }
     $this->_manageAttachmentsFromClient($event, $attachments);
     // convert all datetime fields to UTC
     $event->setTimezone('UTC');
 }