From: Gunnar Wrobel
Date: Mon, 19 Apr 2010 12:02:32 +0000 (+0200)
Subject: MFB: kolab/issue4257 (Always accept policy doesnt set atendee status to accepted)
X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=2be282e64fc4519a6254ec721be3751cd42c7a2d;p=horde.git
MFB: kolab/issue4257 (Always accept policy doesnt set atendee status to accepted)
---
diff --git a/framework/Kolab_Filter/lib/Horde/Kolab/Resource.php b/framework/Kolab_Filter/lib/Horde/Kolab/Resource.php
index 4d561db61..1c918c80e 100644
--- a/framework/Kolab_Filter/lib/Horde/Kolab/Resource.php
+++ b/framework/Kolab_Filter/lib/Horde/Kolab/Resource.php
@@ -21,6 +21,12 @@ require_once 'Horde/MIME/Message.php';
require_once 'Horde/MIME/Headers.php';
require_once 'Horde/MIME/Part.php';
require_once 'Horde/MIME/Structure.php';
+
+/** Load Kolab_Filter elements */
+require_once 'Horde/Kolab/Resource/Epoch.php';
+require_once 'Horde/Kolab/Resource/Itip.php';
+
+require_once 'Horde/String.php';
Horde_String::setDefaultCharset('utf-8');
// What actions we can take when receiving an event request
@@ -379,28 +385,35 @@ class Kolab_Resource
Horde::logMessage(sprintf('No VEVENT found in iCalendar data, passing through to %s', $id), 'INFO');
return true;
}
+ $itip = new Horde_Kolab_Resource_Itip($itip);
// What is the request's method? i.e. should we create a new event/cancel an
// existing event, etc.
- $method = strtoupper($iCalendar->getAttributeDefault('METHOD',
- $itip->getAttributeDefault('METHOD', 'REQUEST')));
+ $method = strtoupper(
+ $iCalendar->getAttributeDefault(
+ 'METHOD',
+ $itip->getMethod()
+ )
+ );
// What resource are we managing?
Horde::logMessage(sprintf('Processing %s method for %s', $method, $id), 'DEBUG');
// This is assumed to be constant across event creation/modification/deletipn
- $uid = $itip->getAttributeDefault('UID', '');
+ $uid = $itip->getUid();
Horde::logMessage(sprintf('Event has UID %s', $uid), 'DEBUG');
// Who is the organiser?
- $organiser = preg_replace('/^mailto:\s*/i', '', $itip->getAttributeDefault('ORGANIZER', ''));
+ $organiser = $itip->getOrganizer();
Horde::logMessage(sprintf('Request made by %s', $organiser), 'DEBUG');
// What is the events summary?
- $summary = $itip->getAttributeDefault('SUMMARY', '');
-
- $dtstart = $this->convert2epoch($itip->getAttributeDefault('DTSTART', 0));
- $dtend = $this->convert2epoch($itip->getAttributeDefault('DTEND', 0));
+ $summary = $itip->getSummary();
+
+ $estart = new Horde_Kolab_Resource_Epoch($itip->getStart());
+ $dtstart = $estart->getEpoch();
+ $eend = new Horde_Kolab_Resource_Epoch($itip->getEnd());
+ $dtend = $eend->getEpoch();
Horde::logMessage(sprintf('Event starts on <%s> %s and ends on <%s> %s.',
$dtstart, $this->iCalDate2Kolab($dtstart), $dtend, $this->iCalDate2Kolab($dtend)), 'DEBUG');
@@ -461,7 +474,7 @@ class Kolab_Resource
}
/** Generate the Kolab object */
- $object = $this->_objectFromItip($itip);
+ $object = $itip->getKolabObject();
$outofperiod=0;
@@ -483,7 +496,8 @@ class Kolab_Resource
Horde::logMessage(sprintf('Free/busy info starts on <%s> %s and ends on <%s> %s',
$vfbstart, $this->iCalDate2Kolab($vfbstart), $vfbend, $this->iCalDate2Kolab($vfbend)), 'DEBUG');
- if ($vfbstart && $dtstart > $this->convert2epoch ($vfbend)) {
+ $evfbend = new Horde_Kolab_Resource_Epoch($vfbend);
+ if ($vfbstart && $dtstart > $evfbend->getEpoch()) {
$outofperiod=1;
} else {
// Check whether we are busy or not
@@ -608,39 +622,14 @@ class Kolab_Resource
}
}
- $result = $data->save($object, $old_uid);
+ $itip->setAccepted($resource);
+
+ $result = $data->save($itip->getKolabObject(), $old_uid);
if (is_a($result, 'PEAR_Error')) {
$result->code = OUT_LOG | EX_UNAVAILABLE;
return $result;
}
- // Update our status within the iTip request and send the reply
- $itip->setAttribute('STATUS', 'CONFIRMED', array(), false);
- $attendees = $itip->getAttribute('ATTENDEE');
- if (!is_array($attendees)) {
- $attendees = array($attendees);
- }
- $attparams = $itip->getAttribute('ATTENDEE', true);
- foreach ($attendees as $i => $attendee) {
- $attendee = preg_replace('/^mailto:\s*/i', '', $attendee);
- if ($attendee != $resource) {
- continue;
- }
-
- $attparams[$i]['PARTSTAT'] = 'ACCEPTED';
- if (array_key_exists('RSVP', $attparams[$i])) {
- unset($attparams[$i]['RSVP']);
- }
- }
-
- // Re-add all the attendees to the event, using our updates status info
- $firstatt = array_pop($attendees);
- $firstattparams = array_pop($attparams);
- $itip->setAttribute('ATTENDEE', $firstatt, $firstattparams, false);
- foreach ($attendees as $i => $attendee) {
- $itip->setAttribute('ATTENDEE', $attendee, $attparams[$i]);
- }
-
if ($outofperiod) {
$this->sendITipReply($cn, $resource, $itip, RM_ITIP_TENTATIVE,
$organiser, $uid, $is_update);
@@ -952,8 +941,9 @@ class Kolab_Resource
$temp = $this->cleanArray($ical_date);
if (array_key_exists('DATE', $temp)) {
if ($type == 'ENDDATE') {
+ $etemp = new Horde_Kolab_Resource_Epoch($temp);
// substract a day (86400 seconds) using epochs to take number of days per month into account
- $epoch= $this->convert2epoch($temp) - 86400;
+ $epoch= $etemp->getEpoch() - 86400;
$date = gmstrftime('%Y-%m-%d', $epoch);
} else {
$date= sprintf('%04d-%02d-%02d', $temp['year'], $temp['month'], $temp['mday']);
@@ -971,28 +961,4 @@ class Kolab_Resource
Horde::logMessage(sprintf('To <%s>', $date), 'DEBUG');
return $date;
}
-
- /**
- * Convert a date to an epoch.
- *
- * @param array $values The array to convert.
- *
- * @return int Time.
- */
- function convert2epoch($values)
- {
- Horde::logMessage(sprintf('Converting to epoch %s',
- print_r($values, true)), 'DEBUG');
-
- if (is_array($values)) {
- $temp = $this->cleanArray($values);
- $epoch = gmmktime($temp['hour'], $temp['minute'], $temp['second'],
- $temp['month'], $temp['mday'], $temp['year']);
- } else {
- $epoch = $values;
- }
-
- Horde::logMessage(sprintf('Converted <%s>', $epoch), 'DEBUG');
- return $epoch;
- }
}
diff --git a/framework/Kolab_Filter/lib/Horde/Kolab/Resource/Epoch.php b/framework/Kolab_Filter/lib/Horde/Kolab/Resource/Epoch.php
new file mode 100644
index 000000000..6178dba21
--- /dev/null
+++ b/framework/Kolab_Filter/lib/Horde/Kolab/Resource/Epoch.php
@@ -0,0 +1,102 @@
+
+ * @author Gunnar Wrobel
+ * @license http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link http://pear.horde.org/index.php?package=Kolab_Server
+ */
+
+/**
+ * Handles Date conversion for the resource handler.
+ *
+ * Copyright 2004-2010 Klarälvdalens Datakonsult AB
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did not
+ * receive this file, see
+ * http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+ *
+ * @category Kolab
+ * @package Kolab_Filter
+ * @author Steffen Hansen
+ * @author Gunnar Wrobel
+ * @license http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link http://pear.horde.org/index.php?package=Kolab_Server
+ */
+class Horde_Kolab_Resource_Epoch
+{
+
+ /**
+ * The date to be converted.
+ *
+ * @var mixed
+ */
+ private $_date;
+
+ /**
+ * Constructor.
+ *
+ * @param mixed $date The date to be converted.
+ */
+ public function __construct($date)
+ {
+ $this->_date = $date;
+ }
+
+ /**
+ * Clear information from a date array.
+ *
+ * @param array $ical_date The array to clear.
+ *
+ * @return array The cleaned array.
+ */
+ function cleanArray($ical_date)
+ {
+ if (!array_key_exists('hour', $ical_date)) {
+ $temp['DATE'] = '1';
+ }
+ $temp['hour'] = array_key_exists('hour', $ical_date) ? $ical_date['hour'] : '00';
+ $temp['minute'] = array_key_exists('minute', $ical_date) ? $ical_date['minute'] : '00';
+ $temp['second'] = array_key_exists('second', $ical_date) ? $ical_date['second'] : '00';
+ $temp['year'] = array_key_exists('year', $ical_date) ? $ical_date['year'] : '0000';
+ $temp['month'] = array_key_exists('month', $ical_date) ? $ical_date['month'] : '00';
+ $temp['mday'] = array_key_exists('mday', $ical_date) ? $ical_date['mday'] : '00';
+ $temp['zone'] = array_key_exists('zone', $ical_date) ? $ical_date['zone'] : 'UTC';
+
+ return $temp;
+ }
+
+ /**
+ * Convert a date to an epoch.
+ *
+ * @param array $values The array to convert.
+ *
+ * @return int Time.
+ */
+ private function convert2epoch($values)
+ {
+ Horde::logMessage(sprintf('Converting to epoch %s',
+ print_r($values, true)), 'DEBUG');
+
+ if (is_array($values)) {
+ $temp = $this->cleanArray($values);
+ $epoch = gmmktime($temp['hour'], $temp['minute'], $temp['second'],
+ $temp['month'], $temp['mday'], $temp['year']);
+ } else {
+ $epoch=$values;
+ }
+
+ Horde::logMessage(sprintf('Converted <%s>', $epoch), 'DEBUG');
+ return $epoch;
+ }
+
+ public function getEpoch()
+ {
+ return $this->convert2Epoch($this->_date);
+ }
+}
\ No newline at end of file
diff --git a/framework/Kolab_Filter/lib/Horde/Kolab/Resource/Itip.php b/framework/Kolab_Filter/lib/Horde/Kolab/Resource/Itip.php
new file mode 100644
index 000000000..fea369788
--- /dev/null
+++ b/framework/Kolab_Filter/lib/Horde/Kolab/Resource/Itip.php
@@ -0,0 +1,257 @@
+
+ * @author Gunnar Wrobel
+ * @license http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link http://pear.horde.org/index.php?package=Kolab_Server
+ */
+
+/**
+ * Handles Itip data.
+ *
+ * Copyright 2004-2010 Klarälvdalens Datakonsult AB
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did not
+ * receive this file, see
+ * http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+ *
+ * @category Kolab
+ * @package Kolab_Filter
+ * @author Steffen Hansen
+ * @author Gunnar Wrobel
+ * @license http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link http://pear.horde.org/index.php?package=Kolab_Server
+ */
+class Horde_Kolab_Resource_Itip
+{
+
+ /**
+ * Reference to the iCalendar iTip object.
+ *
+ * @var Horde_iCalendar_vevent
+ */
+ private $_itip;
+
+ /**
+ * Constructor.
+ *
+ * @param Horde_iCalendar_vevent $itip Reference to the iCalendar iTip object.
+ */
+ public function __construct($itip)
+ {
+ $this->_itip = $itip;
+ }
+
+ public function __call($method, $args)
+ {
+ return call_user_func_array(array($this->_itip, $method), $args);
+ }
+
+ /**
+ * Return the method of the iTip request.
+ *
+ * @return string The method of the request.
+ */
+ public function getMethod()
+ {
+ return $this->_itip->getAttributeDefault('METHOD', 'REQUEST');
+ }
+
+ /**
+ * Return the uid of the iTip event.
+ *
+ * @return string The uid of the event.
+ */
+ public function getUid()
+ {
+ return $this->_itip->getAttributeDefault('UID', '');
+ }
+
+ /**
+ * Return the organizer of the iTip event.
+ *
+ * @return string The organizer of the event.
+ */
+ public function getOrganizer()
+ {
+ return preg_replace('/^mailto:\s*/i', '', $this->_itip->getAttributeDefault('ORGANIZER', ''));
+ }
+
+ /**
+ * Return the summary of the iTip event.
+ *
+ * @return string The summary of the event.
+ */
+ public function getSummary()
+ {
+ return $this->_itip->getAttributeDefault('SUMMARY', '');
+ }
+
+ /**
+ * Return the start of the iTip event.
+ *
+ * @return string The start of the event.
+ */
+ public function getStart()
+ {
+ return $this->_itip->getAttributeDefault('DTSTART', 0);
+ }
+
+ /**
+ * Return the end of the iTip event.
+ *
+ * @return string The end of the event.
+ */
+ public function getEnd()
+ {
+ return $this->_itip->getAttributeDefault('DTEND', 0);
+ }
+
+ public function getKolabObject()
+ {
+ $object = array();
+ $object['uid'] = $this->_itip->getAttributeDefault('UID', '');
+
+ $org_params = $this->_itip->getAttribute('ORGANIZER', true);
+ if (!is_a( $org_params, 'PEAR_Error')) {
+ if (!empty($org_params[0]['CN'])) {
+ $object['organizer']['display-name'] = $org_params[0]['CN'];
+ }
+ $orgemail = $this->_itip->getAttributeDefault('ORGANIZER', '');
+ if (preg_match('/mailto:(.*)/i', $orgemail, $regs )) {
+ $orgemail = $regs[1];
+ }
+ $object['organizer']['smtp-address'] = $orgemail;
+ }
+ $object['summary'] = $this->_itip->getAttributeDefault('SUMMARY', '');
+ $object['location'] = $this->_itip->getAttributeDefault('LOCATION', '');
+ $object['body'] = $this->_itip->getAttributeDefault('DESCRIPTION', '');
+ $dtend = $this->_itip->getAttributeDefault('DTEND', '');
+ if (is_array($dtend)) {
+ $object['_is_all_day'] = true;
+ }
+ $start = new Horde_Kolab_Resource_Epoch($this->getStart());
+ $object['start-date'] = $start->getEpoch();
+ $end = new Horde_Kolab_Resource_Epoch($dtend);
+ $object['end-date'] = $end->getEpoch();
+
+ $attendees = $this->_itip->getAttribute('ATTENDEE');
+ if (!is_a( $attendees, 'PEAR_Error')) {
+ $attendees_params = $this->_itip->getAttribute('ATTENDEE', true);
+ if (!is_array($attendees)) {
+ $attendees = array($attendees);
+ }
+ if (!is_array($attendees_params)) {
+ $attendees_params = array($attendees_params);
+ }
+
+ $object['attendee'] = array();
+ for ($i = 0; $i < count($attendees); $i++) {
+ $attendee = array();
+ if (isset($attendees_params[$i]['CN'])) {
+ $attendee['display-name'] = $attendees_params[$i]['CN'];
+ }
+
+ $attendeeemail = $attendees[$i];
+ if (preg_match('/mailto:(.*)/i', $attendeeemail, $regs)) {
+ $attendeeemail = $regs[1];
+ }
+ $attendee['smtp-address'] = $attendeeemail;
+
+ if (!isset($attendees_params[$i]['RSVP'])
+ || $attendees_params[$i]['RSVP'] == 'FALSE') {
+ $attendee['request-response'] = false;
+ } else {
+ $attendee['request-response'] = true;
+ }
+
+ if (isset($attendees_params[$i]['ROLE'])) {
+ $attendee['role'] = $attendees_params[$i]['ROLE'];
+ }
+
+ if (isset($attendees_params[$i]['PARTSTAT'])) {
+ $status = strtolower($attendees_params[$i]['PARTSTAT']);
+ switch ($status) {
+ case 'needs-action':
+ case 'delegated':
+ $attendee['status'] = 'none';
+ break;
+ default:
+ $attendee['status'] = $status;
+ break;
+ }
+ }
+
+ $object['attendee'][] = $attendee;
+ }
+ }
+
+ // Alarm
+ $valarm = $this->_itip->findComponent('VALARM');
+ if ($valarm) {
+ $trigger = $valarm->getAttribute('TRIGGER');
+ if (!is_a($trigger, 'PEAR_Error')) {
+ $p = $valarm->getAttribute('TRIGGER', true);
+ if ($trigger < 0) {
+ // All OK, enter the alarm into the XML
+ // NOTE: The Kolab XML format seems underspecified
+ // wrt. alarms currently...
+ $object['alarm'] = -$trigger / 60;
+ }
+ } else {
+ Horde::logMessage('No TRIGGER in VALARM. ' . $trigger->getMessage(), 'ERR');
+ }
+ }
+
+ // Recurrence
+ $rrule_str = $this->_itip->getAttribute('RRULE');
+ if (!is_a($rrule_str, 'PEAR_Error')) {
+ require_once 'Horde/Date/Recurrence.php';
+ $recurrence = new Horde_Date_Recurrence(time());
+ $recurrence->fromRRule20($rrule_str);
+ $object['recurrence'] = $recurrence->toHash();
+ }
+
+ Horde::logMessage(sprintf('Assembled event object: %s',
+ print_r($object, true)), 'DEBUG');
+
+ return $object;
+ }
+
+ public function setAccepted($resource)
+ {
+ // Update our status within the iTip request and send the reply
+ $this->_itip->setAttribute('STATUS', 'CONFIRMED', array(), false);
+ $attendees = $this->_itip->getAttribute('ATTENDEE');
+ if (!is_array($attendees)) {
+ $attendees = array($attendees);
+ }
+ $attparams = $this->_itip->getAttribute('ATTENDEE', true);
+ foreach ($attendees as $i => $attendee) {
+ $attendee = preg_replace('/^mailto:\s*/i', '', $attendee);
+ if ($attendee != $resource) {
+ continue;
+ }
+
+ $attparams[$i]['PARTSTAT'] = 'ACCEPTED';
+ if (array_key_exists('RSVP', $attparams[$i])) {
+ unset($attparams[$i]['RSVP']);
+ }
+ }
+
+ // Re-add all the attendees to the event, using our updates status info
+ $firstatt = array_pop($attendees);
+ $firstattparams = array_pop($attparams);
+ $this->_itip->setAttribute('ATTENDEE', $firstatt, $firstattparams, false);
+ foreach ($attendees as $i => $attendee) {
+ $this->_itip->setAttribute('ATTENDEE', $attendee, $attparams[$i]);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/framework/Kolab_Filter/package.xml b/framework/Kolab_Filter/package.xml
index 710109046..7146a159a 100644
--- a/framework/Kolab_Filter/package.xml
+++ b/framework/Kolab_Filter/package.xml
@@ -28,7 +28,7 @@ http://pear.php.net/dtd/package-2.0.xsd">
jan@horde.org
yes
- 2009-11-16
+ 2010-04-19
0.2.0
0.1.0
@@ -43,6 +43,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
* Fixed handling of whole day invitations.
* Extended iTip reply delivery to support different transport mechanisms.
* Splitted Free/Busy functionality in a separate driver class.
+ * kolab/issue4257 ("Always accept" policy doesn't set atendee status to
+ "accepted")
* kolab/issue3962 (Names of config variables for "untrusted" text missleading)
* kolab/issue3967 (UNTRUSTED vs. UNAUTHENTICATED, distinction considered
harmful)
@@ -67,6 +69,10 @@ http://pear.php.net/dtd/package-2.0.xsd">
+
+
+
+
@@ -143,6 +149,7 @@ http://pear.php.net/dtd/package-2.0.xsd">
+
@@ -229,6 +236,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
+
+
@@ -255,6 +264,7 @@ http://pear.php.net/dtd/package-2.0.xsd">
+
diff --git a/framework/Kolab_Filter/test/Horde/Kolab/Filter/ResourceTest.php b/framework/Kolab_Filter/test/Horde/Kolab/Filter/ResourceTest.php
index cd6338fe2..5fa0fbd27 100644
--- a/framework/Kolab_Filter/test/Horde/Kolab/Filter/ResourceTest.php
+++ b/framework/Kolab_Filter/test/Horde/Kolab/Filter/ResourceTest.php
@@ -283,6 +283,37 @@ class Horde_Kolab_Filter_ResourceTest extends Horde_Kolab_Test_Filter
}
/**
+ * Test all day events
+ */
+ public function testAllDay()
+ {
+ $GLOBALS['KOLAB_FILTER_TESTING'] = &new Horde_iCalendar_vfreebusy();
+ $GLOBALS['KOLAB_FILTER_TESTING']->setAttribute('DTSTART', Horde_iCalendar::_parseDateTime('20090901T000000Z'));
+ $GLOBALS['KOLAB_FILTER_TESTING']->setAttribute('DTEND', Horde_iCalendar::_parseDateTime('20091101T000000Z'));
+
+ $params = array('unmodified_content' => true,
+ 'incoming' => true);
+
+ $this->sendFixture(dirname(__FILE__) . '/fixtures/allday_invitation.eml',
+ dirname(__FILE__) . '/fixtures/null.ret',
+ '', '', 'test@example.org', 'wrobel@example.org',
+ 'home.example.org', $params);
+
+ $result = $this->auth->authenticate('wrobel', array('password' => 'none'));
+ $this->assertNoError($result);
+
+ $folder = $this->storage->getFolder('INBOX/Kalender');
+ $data = $folder->getData();
+ $events = $data->getObjects();
+
+ $this->assertEquals(1251928800, $events[0]['start-date']);
+ $this->assertEquals(1252015200, $events[0]['end-date']);
+
+ $result = $data->deleteAll();
+ $this->assertNoError($result);
+ }
+
+ /**
* Test that the attendee status gets transferred.
*/
public function testAttendeeStatusInvitation()
diff --git a/framework/Kolab_Filter/test/Horde/Kolab/Filter/fixtures/allday_invitation.eml b/framework/Kolab_Filter/test/Horde/Kolab/Filter/fixtures/allday_invitation.eml
new file mode 100644
index 000000000..efb6c8c34
--- /dev/null
+++ b/framework/Kolab_Filter/test/Horde/Kolab/Filter/fixtures/allday_invitation.eml
@@ -0,0 +1,86 @@
+Received: from localhost (example.com [127.0.0.1])
+ (Authenticated sender: 1@example.com)
+ by example.com (Postfix) with ESMTP id D47689B1C0
+ for <3@example.com>; Tue, 22 Sep 2009 15:36:22 +0200 (CEST)
+Message-ID: <20090922153619.71582t7kl56avveo@example.com>
+Date: Tue, 22 Sep 2009 15:36:19 +0200
+From: 1 1 <1@example.com>
+To: 3@example.com
+Subject: 5
+User-Agent: Kronolith H3 (2.3)
+MIME-Version: 1.0
+Content-Type: multipart/alternative;
+ boundary="=_2qehldvr5f40"
+Content-Transfer-Encoding: 7bit
+
+This message is in MIME format.
+
+--=_2qehldvr5f40
+Content-Type: text/plain;
+ charset=UTF-8
+Content-Disposition: inline
+Content-Transfer-Encoding: quoted-printable
+
+5 (am 03.09.2009 um 06:00)
+
+Ort: 5
+
+Teilnehmer: 3@example.com
+
+Im Anhang befindet sich eine iCalendar-Datei mit mehr Informationen zu diese=
+m Termin. Wenn Ihr E-Mail-Programm iTip-Anfragen beherrscht, k=C3=B6nnen Sie=
+ diese Datei dazu benutzen, Ihre lokale Version des Termins zu aktualisieren=
+.
+
+Falls Ihr E-Mail-Programm keine iTip-Anfragen unterst=C3=BCtzt, k=C3=B6nnen =
+Sie einen der folgenden Links verwenden, um den Termin zu best=C3=A4tigen od=
+er abzulehnen.
+
+Um den Termin zu best=C3=A4tigen:
+https://example.com/client/kronolith/attend.php=
+?c=3D1%40example.com&e=3Dcee9e56161efb8b8831059=
+b2bc506e4e&u=3D3%40example.com&a=3Daccept
+
+Um den Termin unter Vorbehalt zu best=C3=A4tigen:
+https://example.com/client/kronolith/attend.php=
+?c=3D1%40example.com&e=3Dcee9e56161efb8b8831059=
+b2bc506e4e&u=3D3%40example.com&a=3Dtentative
+
+Um den Termin abzulehnen:
+https://example.com/client/kronolith/attend.php=
+?c=3D1%40example.com&e=3Dcee9e56161efb8b8831059=
+b2bc506e4e&u=3D3%40example.com&a=3Ddecline
+--=_2qehldvr5f40
+Content-Type: text/calendar;
+ charset=UTF-8;
+ name="event-invitation.ics";
+ METHOD="REQUEST"
+Content-Disposition: inline;
+ filename="event-invitation.ics"
+Content-Transfer-Encoding: 7bit
+
+BEGIN:VCALENDAR
+VERSION:2.0
+METHOD:REQUEST
+X-WR-CALNAME:Kalender
+PRODID:-//The Horde Project//Horde_iCalendar Library//EN
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20090903
+DTEND;VALUE=DATE:20090904
+DTSTAMP:20090922T133615Z
+UID:cee9e56161efb8b8831059b2bc506e4e
+CREATED:20090922T133619Z
+LAST-MODIFIED:20090922T133619Z
+SUMMARY:5
+ORGANIZER;CN=1 1:mailto:1@example.com
+LOCATION:5
+CLASS:PUBLIC
+STATUS:CONFIRMED
+TRANSP:OPAQUE
+ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:3@exam
+ ple.com
+RRULE:FREQ=MONTHLY;INTERVAL=1;BYDAY=1TH
+END:VEVENT
+END:VCALENDAR
+
+--=_2qehldvr5f40--
diff --git a/framework/Kolab_Filter/test/Horde/Kolab/Filter/phpunit.xml b/framework/Kolab_Filter/test/Horde/Kolab/Filter/phpunit.xml
new file mode 100644
index 000000000..0148736fe
--- /dev/null
+++ b/framework/Kolab_Filter/test/Horde/Kolab/Filter/phpunit.xml
@@ -0,0 +1,8 @@
+
+
+
+
+ ../../../../lib
+
+
+