/** * 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'); }