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 + + +