public function testIsRecurException() { $event = new Calendar_Model_Event(array('uid' => '839404a34a8005d2ebd0da68b6aa922460ae945a', 'summary' => 'conference exception (late)', 'dtstart' => '2003-03-28 10:00:00', 'dtend' => '2003-03-28 12:00:00', 'originator_tz' => 'US/Pacific', 'recurid' => '839404a34a8005d2ebd0da68b6aa922460ae945a-2003-03-28 08:00:00')); $this->assertTrue($event->isRecurException(), 'recur exception was not detected'); $this->assertEquals('2003-03-28 08:00:00', $event->getOriginalDtStart()->format(Tinebase_Record_Abstract::ISO8601LONG)); }
/** * prepares an exception instance for persistence * * @param Calendar_Model_Event $_baseEvent * @param Calendar_Model_Event $_exception * @return void * @throws Tinebase_Exception_InvalidArgument */ protected function _prepareException(Calendar_Model_Event $_baseEvent, Calendar_Model_Event $_exception) { if (!$_baseEvent->uid) { throw new Tinebase_Exception_InvalidArgument('base event has no uid'); } if ($_exception->is_deleted == false) { $_exception->container_id = $_baseEvent->container_id; } $_exception->uid = $_baseEvent->uid; $_exception->base_event_id = $_baseEvent->getId(); $_exception->recurid = $_baseEvent->uid . '-' . $_exception->getOriginalDtStart()->format(Tinebase_Record_Abstract::ISO8601LONG); // NOTE: we always refetch the base event as it might be touched in the meantime $currBaseEvent = $this->_eventController->get($_baseEvent, null, false); $_exception->last_modified_time = $currBaseEvent->last_modified_time; }
/** * prepares an exception instance for persitence * * @param Calendar_Model_Event $_baseEvent * @param Calendar_Model_Event $_exception * @return void * @throws Tinebase_Exception_InvalidArgument */ protected function _prepareException($_baseEvent, $_exception) { if ($_exception instanceof Tinebase_Record_RecordSet) { foreach ($_exception as $exception) { $this->_prepareException($_baseEvent, $exception); } return; } if (!$_baseEvent->uid) { throw new Tinebase_Exception_InvalidArgument('base event has no uid'); } if ($_exception->is_deleted == false) { $_exception->container_id = $_baseEvent->container_id; } $_exception->uid = $_baseEvent->uid; $_exception->recurid = $_baseEvent->uid . '-' . $_exception->getOriginalDtStart()->format(Tinebase_Record_Abstract::ISO8601LONG); }
/** * eventToIcal * * @param Tinebase_Record_RecordSet|Calendar_Model_Event $_event * @return qCal_Component_Vcalendar */ public function eventToIcal($_event) { if ($_event instanceof Tinebase_Record_RecordSet) { foreach ($_event as $event) { $this->eventToIcal($event); } return $this->_vcalendar; } // NOTE: we deliver events in originators tz $_event->setTimezone($_event->originator_tz); if (!in_array($_event->originator_tz, $this->_attachedTimezones)) { $this->_vcalendar->attach(self::getVtimezone($_event->originator_tz)); $this->_attachedTimezones[] = $_event->originator_tz; } if ($_event->is_all_day_event) { $dtstart = new qCal_Property_Dtstart($_event->dtstart->format('Ymd'), array('VALUE' => 'DATE')); $dtend = new qCal_Property_Dtend($_event->dtend->format('Ymd'), array('VALUE' => 'DATE')); } else { $dtstart = new qCal_Property_Dtstart(qCal_DateTime::factory($_event->dtstart->format('Ymd\\THis'), $_event->originator_tz), array('TZID' => $_event->originator_tz)); $dtend = new qCal_Property_Dtend(qCal_DateTime::factory($_event->dtend->format('Ymd\\THis'), $_event->originator_tz), array('TZID' => $_event->originator_tz)); } $vevent = new qCal_Component_Vevent(array('uid' => $_event->uid, 'sequence' => $_event->seq, 'summary' => $_event->summary, 'dtstart' => $dtstart, 'dtend' => $dtend)); foreach (self::$veventMap as $icalProp => $tineField) { if (isset($_event[$tineField])) { $vevent->addProperty($icalProp, $_event->{$tineField}); } } // rrule if ($_event->rrule) { $vevent->addProperty('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 ($exdateArray = $_event->exdate) { // use multiple EXDATE for the moment, as apple ical uses them foreach ($_event->exdate as $exdate) { $exdates = new qCal_Property_Exdate(qCal_DateTime::factory($exdate->format('Ymd\\THis'), $_event->originator_tz), array('TZID' => $_event->originator_tz)); $vevent->addProperty($exdates); } // $exdates = new qCal_Property_Exdate(qCal_DateTime::factory(array_shift($exdateArray)->format('Ymd\THis'), $_event->originator_tz), array('TZID' => $_event->originator_tz)); // foreach($exdateArray as $exdate) { // $exdates->addValue(qCal_DateTime::factory($exdate->format('Ymd\THis'), $_event->originator_tz)); // } // // $vevent->addProperty($exdates); } } // recurid if ($_event->isRecurException()) { $originalDtStart = $_event->getOriginalDtStart(); $originalDtStart->setTimezone($_event->originator_tz); $vevent->addProperty(new qCal_Property_RecurrenceId(qCal_DateTime::factory($originalDtStart->format('Ymd\\THis'), $_event->originator_tz), array('TZID' => $_event->originator_tz))); } // organizer $organizerId = $_event->organizer instanceof Addressbook_Model_Contact ? array($_event->organizer->getId()) : array($_event->organizer); $organizer = Addressbook_Controller_Contact::getInstance()->getMultiple($organizerId, TRUE)->getFirstRecord(); if ($organizer && ($organizerEmail = $organizer->getPreferedEmailAddress())) { $vevent->addProperty(new qCal_Property_Organizer("mailto:{$organizerEmail}", array('CN' => $organizer->n_fileas))); } // attendee if ($_event->attendee) { Calendar_Model_Attender::resolveAttendee($_event->attendee, FALSE); foreach ($_event->attendee as $attender) { $attenderEmail = $attender->getEmail(); if ($attenderEmail) { $vevent->addProperty(new qCal_Property_Attendee("mailto:{$attenderEmail}", array('CN' => $attender->getName(), 'CUTYPE' => self::$cutypeMap[$attender->user_type], 'EMAIL' => $attenderEmail, 'PARTSTAT' => $attender->status, 'ROLE' => "{$attender->role}-PARTICIPANT", 'RSVP' => 'FALSE'))); } } } // alarms if ($_event->alarms) { foreach ($_event->alarms as $alarm) { $valarm = new qCal_Component_Valarm(array('ACTION' => 'DISPLAY', 'DESCRIPTION' => $_event->summary)); // qCal only support DURATION ;-( $diffSeconds = $_event->dtstart->php52compat_diff($alarm->alarm_time); $valarm->addProperty(new qCal_Property_Trigger($diffSeconds)); // if (is_numeric($alarm->minutes_before)) { // $valarm->addProperty(new qCal_Property_Trigger("-PT{$alarm->minutes_before}M")); // } else { // $valarm->addProperty(new qCal_Property_Trigger(qCal_DateTime::factory($alarm->alarm_time->format('Ymd\THis'), $_event->originator_tz)), array('TZID' => $_event->originator_tz)); // } $vevent->attach($valarm); } } // @todo status $this->_vcalendar->attach($vevent); return $this->_vcalendar; }
/** * convert calendar event to Sabre\VObject\Component * * @param \Sabre\VObject\Component\VCalendar $vcalendar * @param Calendar_Model_Event $_event * @param Calendar_Model_Event $_mainEvent */ protected function _convertCalendarModelEvent(\Sabre\VObject\Component\VCalendar $vcalendar, Calendar_Model_Event $_event, Calendar_Model_Event $_mainEvent = null) { // clone the event and change the timezone $event = clone $_event; $event->setTimezone($event->originator_tz); $lastModifiedDateTime = $_event->last_modified_time ? $_event->last_modified_time : $_event->creation_time; if (!$event->creation_time instanceof Tinebase_DateTime) { throw new Tinebase_Exception_Record_Validation('creation_time needed for conversion to Sabre\\VObject\\Component'); } $vevent = $vcalendar->create('VEVENT', array('CREATED' => $_event->creation_time->getClone()->setTimezone('UTC'), 'LAST-MODIFIED' => $lastModifiedDateTime->getClone()->setTimezone('UTC'), 'DTSTAMP' => Tinebase_DateTime::now(), 'UID' => $event->uid)); $vevent->add('SEQUENCE', $event->hasExternalOrganizer() ? $event->external_seq : $event->seq); if ($event->isRecurException()) { $originalDtStart = $_event->getOriginalDtStart()->setTimezone($_event->originator_tz); $recurrenceId = $vevent->add('RECURRENCE-ID', $originalDtStart); if ($_mainEvent && $_mainEvent->is_all_day_event == true) { $recurrenceId['VALUE'] = 'DATE'; } } // dtstart and dtend $dtstart = $vevent->add('DTSTART', $_event->dtstart->getClone()->setTimezone($event->originator_tz)); if ($event->is_all_day_event == true) { $dtstart['VALUE'] = 'DATE'; // whole day events ends at 23:59:(00|59) in Tine 2.0 but 00:00 the next day in vcalendar $event->dtend->addSecond($event->dtend->get('s') == 59 ? 1 : 0); $event->dtend->addMinute($event->dtend->get('i') == 59 ? 1 : 0); $dtend = $vevent->add('DTEND', $event->dtend); $dtend['VALUE'] = 'DATE'; } else { $dtend = $vevent->add('DTEND', $event->dtend); } // auto status for deleted events if ($event->is_deleted) { $event->status = Calendar_Model_Event::STATUS_CANCELED; } // event organizer if (!empty($event->organizer)) { $organizerContact = $event->resolveOrganizer(); if ($organizerContact instanceof Addressbook_Model_Contact && !empty($organizerContact->email)) { $organizer = $vevent->add('ORGANIZER', 'mailto:' . $organizerContact->email, array('CN' => $organizerContact->n_fileas, 'EMAIL' => $organizerContact->email)); } } $this->_addEventAttendee($vevent, $event); $optionalProperties = array('class', 'status', 'description', 'geo', 'location', 'priority', 'summary', 'transp', 'url'); foreach ($optionalProperties as $property) { if (!empty($event->{$property})) { $vevent->add(strtoupper($property), $event->{$property}); } } $class = $event->class == Calendar_Model_Event::CLASS_PUBLIC ? 'PUBLIC' : 'CONFIDENTIAL'; $vcalendar->add('X-CALENDARSERVER-ACCESS', $class); $vevent->add('X-CALENDARSERVER-ACCESS', $class); // categories if (!isset($event->tags)) { $event->tags = Tinebase_Tags::getInstance()->getTagsOfRecord($event); } if (isset($event->tags) && count($event->tags) > 0) { $vevent->add('CATEGORIES', (array) $event->tags->name); } // repeating event properties if ($event->rrule) { if ($event->is_all_day_event == true) { $vevent->add('RRULE', preg_replace_callback('/UNTIL=([\\d :-]{19})(?=;?)/', function ($matches) { $dtUntil = new Tinebase_DateTime($matches[1]); $dtUntil->setTimezone((string) Tinebase_Core::getUserTimezone()); return 'UNTIL=' . $dtUntil->format('Ymd'); }, $event->rrule)); } else { $vevent->add('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) { $event->exdate->addIndices(array('is_deleted')); $deletedEvents = $event->exdate->filter('is_deleted', true); foreach ($deletedEvents as $deletedEvent) { $dateTime = $deletedEvent->getOriginalDtStart(); $exdate = $vevent->add('EXDATE'); if ($event->is_all_day_event == true) { $dateTime->setTimezone($event->originator_tz); $exdate['VALUE'] = 'DATE'; } $exdate->setValue($dateTime); } } } $ownAttendee = Calendar_Model_Attender::getOwnAttender($event->attendee); if ($event->alarms instanceof Tinebase_Record_RecordSet) { $mozLastAck = NULL; $mozSnooze = NULL; foreach ($event->alarms as $alarm) { $valarm = $vcalendar->create('VALARM'); $valarm->add('ACTION', 'DISPLAY'); $valarm->add('DESCRIPTION', $event->summary); if ($dtack = Calendar_Controller_Alarm::getAcknowledgeTime($alarm)) { $valarm->add('ACKNOWLEDGED', $dtack->getClone()->setTimezone('UTC')->format('Ymd\\THis\\Z')); $mozLastAck = $dtack > $mozLastAck ? $dtack : $mozLastAck; } if ($dtsnooze = Calendar_Controller_Alarm::getSnoozeTime($alarm)) { $mozSnooze = $dtsnooze > $mozSnooze ? $dtsnooze : $mozSnooze; } if (is_numeric($alarm->minutes_before)) { if ($event->dtstart == $alarm->alarm_time) { $periodString = 'PT0S'; } else { $interval = $event->dtstart->diff($alarm->alarm_time); $periodString = sprintf('%sP%s%s%s%s', $interval->format('%r'), $interval->format('%d') > 0 ? $interval->format('%dD') : null, $interval->format('%h') > 0 || $interval->format('%i') > 0 ? 'T' : null, $interval->format('%h') > 0 ? $interval->format('%hH') : null, $interval->format('%i') > 0 ? $interval->format('%iM') : null); } # TRIGGER;VALUE=DURATION:-PT1H15M $trigger = $valarm->add('TRIGGER', $periodString); $trigger['VALUE'] = "DURATION"; } else { # TRIGGER;VALUE=DATE-TIME:... $trigger = $valarm->add('TRIGGER', $alarm->alarm_time->getClone()->setTimezone('UTC')->format('Ymd\\THis\\Z')); $trigger['VALUE'] = "DATE-TIME"; } $vevent->add($valarm); } if ($mozLastAck instanceof DateTime) { $vevent->add('X-MOZ-LASTACK', $mozLastAck->getClone()->setTimezone('UTC'), array('VALUE' => 'DATE-TIME')); } if ($mozSnooze instanceof DateTime) { $vevent->add('X-MOZ-SNOOZE-TIME', $mozSnooze->getClone()->setTimezone('UTC'), array('VALUE' => 'DATE-TIME')); } } $baseUrl = Tinebase_Core::getHostname() . "/webdav/Calendar/records/Calendar_Model_Event/{$event->getId()}/"; if ($event->attachments instanceof Tinebase_Record_RecordSet) { foreach ($event->attachments as $attachment) { $filename = rawurlencode($attachment->name); $attach = $vcalendar->createProperty('ATTACH', "{$baseUrl}{$filename}", array('MANAGED-ID' => $attachment->hash, 'FMTTYPE' => $attachment->contenttype, 'SIZE' => $attachment->size, 'FILENAME' => $filename), 'TEXT'); $vevent->add($attach); } } $vcalendar->add($vevent); }