From: Gunnar Wrobel Date: Wed, 7 Jul 2010 09:41:05 +0000 (+0200) Subject: MFB: X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=115879a777fa1cdd4c625d1a6539a4a6326ceb45;p=horde.git MFB: Split Kolab_Resource from Kolab_Filter. Complete separation between resource handling and the filter package. --- diff --git a/framework/Kolab_Filter/lib/Horde/Kolab/Filter/Exception.php b/framework/Kolab_Filter/lib/Horde/Kolab/Filter/Exception.php deleted file mode 100644 index d0294481f..000000000 --- a/framework/Kolab_Filter/lib/Horde/Kolab/Filter/Exception.php +++ /dev/null @@ -1,72 +0,0 @@ - - * @license http://www.fsf.org/copyleft/lgpl.html LGPL - * @link http://pear.horde.org/index.php?package=Kolab_Filter - */ - -/** - * This class provides the standard error class for Kolab Filter exceptions. - * - * Copyright 2009-2010 The Horde Project (http://www.horde.org/) - * - * See the enclosed file COPYING for license information (LGPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. - * - * @category Kolab - * @package Kolab_Filter - * @author Gunnar Wrobel - * @license http://www.fsf.org/copyleft/lgpl.html LGPL - * @link http://pear.horde.org/index.php?package=Kolab_Filter - */ -class Horde_Kolab_Filter_Exception extends Horde_Exception -{ - /** - * Constants to define the error type. - */ - const SYSTEM = 1; - const NO_FREEBUSY = 2; - - /** - * The array of available error messages. These are connected to the error - * codes used above and might be used to differentiate between what we show - * the user in the frontend and what we actually log in the backend. - * - * @var array - */ - protected $messages; - - /** - * Exception constructor - * - * @param mixed $message The exception message, a PEAR_Error object, or an - * Exception object. - * @param mixed $code A numeric error code, or - * an array from error_get_last(). - */ - public function __construct($message = null, $code = null) - { - $this->setMessages(); - - parent::__construct($message, $code); - } - - /** - * Initialize the messages handled by this exception. - * - * @return NULL - */ - protected function setMessages() - { - $this->messages = array( - self::SYSTEM => _("An internal error occured."), - self::NO_FREEBUSY => _("There is no free/busy data available."), - ); - } -} diff --git a/framework/Kolab_Filter/lib/Horde/Kolab/Filter/Incoming.php b/framework/Kolab_Filter/lib/Horde/Kolab/Filter/Incoming.php index f8e0bb040..544133e05 100644 --- a/framework/Kolab_Filter/lib/Horde/Kolab/Filter/Incoming.php +++ b/framework/Kolab_Filter/lib/Horde/Kolab/Filter/Incoming.php @@ -143,6 +143,12 @@ class Horde_Kolab_Filter_Incoming extends Horde_Kolab_Filter_Base $r->cleanup(); if (is_a($rc, 'PEAR_Error')) { return $rc; + } else if (is_a($rc, 'Horde_Kolab_Resource_Reply')) { + $result = $this->_transportItipReply($rc); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + Horde::logMessage('Successfully sent iTip reply', 'DEBUG'); } else if ($rc === true) { $newrecips[] = $resource; } @@ -167,6 +173,44 @@ class Horde_Kolab_Filter_Incoming extends Horde_Kolab_Filter_Base Horde::logMessage("Filter_Incoming successfully completed.", 'DEBUG'); } + private function _transportItipReply(Horde_Kolab_Resource_Reply $reply) + { + global $conf; + + if (isset($conf['kolab']['filter']['itipreply'])) { + $driver = $conf['kolab']['filter']['itipreply']['driver']; + $host = $conf['kolab']['filter']['itipreply']['params']['host']; + $port = $conf['kolab']['filter']['itipreply']['params']['port']; + } else { + $driver = 'smtp'; + $host = 'localhost'; + $port = 25; + } + + $transport = Horde_Kolab_Filter_Transport::factory( + $driver, + array('host' => $host, 'port' => $port) + ); + + $result = $transport->start($reply->getSender(), $reply->getRecipient()); + if (is_a($result, 'PEAR_Error')) { + return PEAR::raiseError('Unable to send iTip reply: ' . $result->getMessage(), + OUT_LOG | EX_TEMPFAIL); + } + + $result = $transport->data($reply->getData()); + if (is_a($result, 'PEAR_Error')) { + return PEAR::raiseError('Unable to send iTip reply: ' . $result->getMessage(), + OUT_LOG | EX_TEMPFAIL); + } + + $result = $transport->end(); + if (is_a($result, 'PEAR_Error')) { + return PEAR::raiseError('Unable to send iTip reply: ' . $result->getMessage(), + OUT_LOG | EX_TEMPFAIL); + } + } + /** * Deliver the message. * diff --git a/framework/Kolab_Filter/lib/Horde/Kolab/Filter/Transport/DovecotLDA.php b/framework/Kolab_Filter/lib/Horde/Kolab/Filter/Transport/DovecotLDA.php index 21fd30dd0..b237a4453 100644 --- a/framework/Kolab_Filter/lib/Horde/Kolab/Filter/Transport/DovecotLDA.php +++ b/framework/Kolab_Filter/lib/Horde/Kolab/Filter/Transport/DovecotLDA.php @@ -213,8 +213,8 @@ class Dovecot_LDA $deliver = $conf['kolab']['filter']['dovecot_deliver']; - $this->_deliver_fh = popen($deliver . ' -f ' . $this->_envelopeSender . - ' -d ' . $recipient, "w"); + $this->_deliver_fh = popen($deliver . ' -f "' . $this->_envelopeSender . + '" -d "' . $recipient . '"', "w"); if ($this->_deliver_fh === false) { return PEAR::raiseError('Failed to connect to the dovecot delivery tool!'); } diff --git a/framework/Kolab_Filter/lib/Horde/Kolab/Filter/Transport/lmtp.php b/framework/Kolab_Filter/lib/Horde/Kolab/Filter/Transport/lmtp.php index 852da8c4e..6d502d8c2 100644 --- a/framework/Kolab_Filter/lib/Horde/Kolab/Filter/Transport/lmtp.php +++ b/framework/Kolab_Filter/lib/Horde/Kolab/Filter/Transport/lmtp.php @@ -35,7 +35,7 @@ class Horde_Kolab_Filter_Transport_lmtp extends Horde_Kolab_Filter_Transport } $transport = new Net_LMTP_TLS($this->_params['host'], - $this->_params['port']); + $this->_params['port']); return $transport; } diff --git a/framework/Kolab_Filter/lib/Horde/Kolab/Filter/Transport/smtp.php b/framework/Kolab_Filter/lib/Horde/Kolab/Filter/Transport/smtp.php index b2a978ed2..2ffb28648 100644 --- a/framework/Kolab_Filter/lib/Horde/Kolab/Filter/Transport/smtp.php +++ b/framework/Kolab_Filter/lib/Horde/Kolab/Filter/Transport/smtp.php @@ -35,7 +35,7 @@ class Horde_Kolab_Filter_Transport_smtp extends Horde_Kolab_Filter_Transport } $transport = new Net_SMTP($this->_params['host'], - $this->_params['port']); + $this->_params['port']); return $transport; } } diff --git a/framework/Kolab_Filter/lib/Horde/Kolab/Resource.php b/framework/Kolab_Filter/lib/Horde/Kolab/Resource.php deleted file mode 100644 index 8513745cf..000000000 --- a/framework/Kolab_Filter/lib/Horde/Kolab/Resource.php +++ /dev/null @@ -1,859 +0,0 @@ - - * @author Gunnar Wrobel - * @license http://www.fsf.org/copyleft/lgpl.html LGPL - * @link http://pear.horde.org/index.php?package=Kolab_Server - */ - -/** Load the iCal handling */ -require_once 'Horde/iCalendar.php'; - -/** Load MIME handlers */ -require_once 'Horde/MIME.php'; -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 -define('RM_ACT_ALWAYS_ACCEPT', 'ACT_ALWAYS_ACCEPT'); -define('RM_ACT_REJECT_IF_CONFLICTS', 'ACT_REJECT_IF_CONFLICTS'); -define('RM_ACT_MANUAL_IF_CONFLICTS', 'ACT_MANUAL_IF_CONFLICTS'); -define('RM_ACT_MANUAL', 'ACT_MANUAL'); -define('RM_ACT_ALWAYS_REJECT', 'ACT_ALWAYS_REJECT'); - -// What possible ITIP notification we can send -define('RM_ITIP_DECLINE', 1); -define('RM_ITIP_ACCEPT', 2); -define('RM_ITIP_TENTATIVE', 3); - -/** - * Provides Kolab resource handling - * - * 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. - * - * @package Kolab_Filter - * @author Steffen Hansen - * @author Gunnar Wrobel - */ -class Kolab_Resource -{ - - /** - * Returns the resource policy applying for the given sender - * - * @param string $sender The sender address - * @param string $resource The resource - * - * @return array|PEAR_Error An array with "cn", "home server" and the policy. - */ - function _getResourceData($sender, $resource) - { - require_once 'Horde/Kolab/Server.php'; - $db = &Horde_Kolab_Server::singleton(); - if (is_a($db, 'PEAR_Error')) { - $db->code = OUT_LOG | EX_SOFTWARE; - return $db; - } - - $dn = $db->uidForMail($resource, Horde_Kolab_Server_Object::RESULT_MANY); - if (is_a($dn, 'PEAR_Error')) { - $dn->code = OUT_LOG | EX_NOUSER; - return $dn; - } - if (is_array($dn)) { - if (count($dn) > 1) { - Horde::logMessage(sprintf("%s objects returned for %s", - $count($dn), $resource), 'WARN'); - return false; - } else { - $dn = $dn[0]; - } - } - $user = $db->fetch($dn, 'Horde_Kolab_Server_Object_Kolab_User'); - - $cn = $user->get(Horde_Kolab_Server_Object_Kolab_User::ATTRIBUTE_CN); - $id = $user->get(Horde_Kolab_Server_Object_Kolab_User::ATTRIBUTE_MAIL); - $hs = $user->get(Horde_Kolab_Server_Object_Kolab_User::ATTRIBUTE_HOMESERVER); - if (is_a($hs, 'PEAR_Error')) { - return $hs; - } - $hs = strtolower($hs); - $actions = $user->get(Horde_Kolab_Server_Object_Kolab_User::ATTRIBUTE_IPOLICY, false); - if (is_a($actions, 'PEAR_Error')) { - $actions->code = OUT_LOG | EX_UNAVAILABLE; - return $actions; - } - if ($actions === false) { - $actions = array(RM_ACT_MANUAL); - } - - $policies = array(); - $defaultpolicy = false; - foreach ($actions as $action) { - if (preg_match('/(.*):(.*)/', $action, $regs)) { - $policies[strtolower($regs[1])] = $regs[2]; - } else { - $defaultpolicy = $action; - } - } - // Find sender's policy - if (array_key_exists($sender, $policies)) { - // We have an exact match, stop processing - $action = $policies[$sender]; - } else { - $action = false; - $dn = $db->uidForMailOrAlias($sender); - if (is_a($dn, 'PEAR_Error')) { - $dn->code = OUT_LOG | EX_NOUSER; - return $dn; - } - if ($dn) { - // Sender is local, check for groups - foreach ($policies as $gid => $policy) { - if ($db->memberOfGroupAddress($dn, $gid)) { - // User is member of group - if (!$action) { - $action = $policy; - } else { - $action = min($action, $policy); - } - } - } - } - if (!$action && $defaultpolicy) { - $action = $defaultpolicy; - } - } - return array('cn' => $cn, 'id' => $id, - 'homeserver' => $hs, 'action' => $action); - } - - function &_getICal($filename) - { - $requestText = ''; - $handle = fopen($filename, 'r'); - while (!feof($handle)) { - $requestText .= fread($handle, 8192); - } - - $mime = &MIME_Structure::parseTextMIMEMessage($requestText); - - $parts = $mime->contentTypeMap(); - foreach ($parts as $mimeid => $conttype) { - if ($conttype == 'text/calendar') { - $part = $mime->getPart($mimeid); - - $iCalendar = new Horde_iCalendar(); - $iCalendar->parsevCalendar($part->transferDecode()); - - return $iCalendar; - } - } - // No iCal found - return false; - } - - function _imapConnect($id) - { - global $conf; - - // Handle virtual domains - list($user, $domain) = explode('@', $id); - if (empty($domain)) { - $domain = $conf['kolab']['filter']['email_domain']; - } - $calendar_user = $conf['kolab']['filter']['calendar_id'] . '@' . $domain; - - /* Load the authentication libraries */ - $auth = $GLOBALS['injector']->getInstance('Horde_Auth')->getAuth(isset($conf['auth']['driver']) ? null : 'kolab'); - $authenticated = $auth->authenticate($calendar_user, - array('password' => $conf['kolab']['filter']['calendar_pass']), - false); - - if (is_a($authenticated, 'PEAR_Error')) { - $authenticated->code = OUT_LOG | EX_UNAVAILABLE; - return $authenticated; - } - if (!$authenticated) { - return PEAR::raiseError(sprintf('Failed to authenticate as calendar user: %s', - $auth->getLogoutReasonString()), - OUT_LOG | EX_UNAVAILABLE); - } - @session_start(); - - $secret = $GLOBALS['injector']->getInstance('Horde_Secret'); - - $_SESSION['__auth'] = array( - 'authenticated' => true, - 'userId' => $calendar_user, - 'timestamp' => time(), - 'credentials' => $secret->write($secret->getKey('auth'), - serialize(array('password' => $conf['kolab']['filter']['calendar_pass']))), - 'remote_addr' => isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null, - ); - - /* Kolab IMAP handling */ - require_once 'Horde/Kolab/Storage/List.php'; - $list = &Kolab_List::singleton(); - $default = $list->getForeignDefault($id, 'event'); - if (!$default || is_a($default, 'PEAR_Error')) { - $default = new Kolab_Folder(); - $default->setList($list); - $default->setName($conf['kolab']['filter']['calendar_store']); - //FIXME: The calendar user needs access here - $attributes = array('default' => true, - 'type' => 'event', - 'owner' => $id); - $result = $default->save($attributes); - if (is_a($result, 'PEAR_Error')) { - $result->code = OUT_LOG | EX_UNAVAILABLE; - return $result; - } - } - return $default; - } - - function handleMessage($fqhostname, $sender, $resource, $tmpfname) - { - global $conf; - - $rdata = $this->_getResourceData($sender, $resource); - if (is_a($rdata, 'PEAR_Error')) { - return $rdata; - } else if ($rdata === false) { - /* No data, probably not a local user */ - return true; - } else if ($rdata['homeserver'] && $rdata['homeserver'] != $fqhostname) { - /* Not the users homeserver, ignore */ - return true; - } - - $cn = $rdata['cn']; - $id = $rdata['id']; - if (isset($rdata['action'])) { - $action = $rdata['action']; - } else { - // Manual is the only safe default! - $action = RM_ACT_MANUAL; - } - Horde::logMessage(sprintf('Action for %s is %s', - $sender, $action), 'DEBUG'); - - // Get out as early as possible if manual - if ($action == RM_ACT_MANUAL) { - Horde::logMessage(sprintf('Passing through message to %s', $id), 'INFO'); - return true; - } - - /* Get the iCalendar data (i.e. the iTip request) */ - $iCalendar = &$this->_getICal($tmpfname); - if ($iCalendar === false) { - // No iCal in mail - Horde::logMessage(sprintf('Could not parse iCalendar data, passing through to %s', $id), 'INFO'); - return true; - } - // Get the event details out of the iTip request - $itip = &$iCalendar->findComponent('VEVENT'); - if ($itip === false) { - 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->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->getUid(); - Horde::logMessage(sprintf('Event has UID %s', $uid), 'DEBUG'); - - // Who is the organiser? - $organiser = $itip->getOrganizer(); - Horde::logMessage(sprintf('Request made by %s', $organiser), 'DEBUG'); - - // What is the events summary? - $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'); - - if ($action == RM_ACT_ALWAYS_REJECT) { - if ($method == 'REQUEST') { - Horde::logMessage(sprintf('Rejecting %s method', $method), 'INFO'); - $this->sendITipReply($cn, $resource, $itip, RM_ITIP_DECLINE, - $organiser, $uid, $is_update); - return false; - } else { - Horde::logMessage(sprintf('Passing through %s method for ACT_ALWAYS_REJECT policy', $method), 'INFO'); - return true; - } - } - - $is_update = false; - $imap_error = false; - $ignore = array(); - - $folder = $this->_imapConnect($id); - if (is_a($folder, 'PEAR_Error')) { - $imap_error = &$folder; - } - if (!is_a($imap_error, 'PEAR_Error') && !$folder->exists()) { - $imap_error = &PEAR::raiseError('Error, could not open calendar folder!', - OUT_LOG | EX_TEMPFAIL); - } - - if (!is_a($imap_error, 'PEAR_Error')) { - $data = $folder->getData(); - if (is_a($data, 'PEAR_Error')) { - $imap_error = &$data; - } - } - - if (is_a($imap_error, 'PEAR_Error')) { - Horde::logMessage(sprintf('Failed accessing IMAP calendar: %s', - $folder->getMessage()), 'ERR'); - if ($action == RM_ACT_MANUAL_IF_CONFLICTS) { - return true; - } - } - - switch ($method) { - case 'REQUEST': - if ($action == RM_ACT_MANUAL) { - Horde::logMessage(sprintf('Passing through %s method', $method), 'INFO'); - break; - } - - if (is_a($imap_error, 'PEAR_Error') || !$data->objectUidExists($uid)) { - $old_uid = null; - } else { - $old_uid = $uid; - $ignore[] = $uid; - $is_update = true; - } - - /** Generate the Kolab object */ - $object = $itip->getKolabObject(); - - $outofperiod=0; - - // Don't even bother checking free/busy info if RM_ACT_ALWAYS_ACCEPT - // is specified - if ($action != RM_ACT_ALWAYS_ACCEPT) { - - try { - require_once 'Horde/Kolab/Resource/Freebusy.php'; - $fb = Horde_Kolab_Resource_Freebusy::singleton(); - $vfb = $fb->get($resource); - } catch (Exception $e) { - return PEAR::raiseError($e->getMessage(), - OUT_LOG | EX_UNAVAILABLE); - } - - $vfbstart = $vfb->getAttributeDefault('DTSTART', 0); - $vfbend = $vfb->getAttributeDefault('DTEND', 0); - Horde::logMessage(sprintf('Free/busy info starts on <%s> %s and ends on <%s> %s', - $vfbstart, $this->iCalDate2Kolab($vfbstart), $vfbend, $this->iCalDate2Kolab($vfbend)), 'DEBUG'); - - $evfbend = new Horde_Kolab_Resource_Epoch($vfbend); - if ($vfbstart && $dtstart > $evfbend->getEpoch()) { - $outofperiod=1; - } else { - // Check whether we are busy or not - $busyperiods = $vfb->getBusyPeriods(); - Horde::logMessage(sprintf('Busyperiods: %s', - print_r($busyperiods, true)), 'DEBUG'); - $extraparams = $vfb->getExtraParams(); - Horde::logMessage(sprintf('Extraparams: %s', - print_r($extraparams, true)), 'DEBUG'); - $conflict = false; - if (!empty($object['recurrence'])) { - $recurrence = new Horde_Date_Recurrence($dtstart); - $recurrence->fromHash($object['recurrence']); - $duration = $dtend - $dtstart; - $events = array(); - $next_start = $vfbstart; - $next = $recurrence->nextActiveRecurrence($vfbstart); - while ($next !== false && $next->compareDate($vfbend) <= 0) { - $next_ts = $next->timestamp(); - $events[$next_ts] = $next_ts + $duration; - $next = $recurrence->nextActiveRecurrence(array('year' => $next->year, - 'month' => $next->month, - 'mday' => $next->mday + 1, - 'hour' => $next->hour, - 'min' => $next->min, - 'sec' => $next->sec)); - } - } else { - $events = array($dtstart => $dtend); - } - - foreach ($events as $dtstart => $dtend) { - Horde::logMessage(sprintf('Requested event from %s to %s', - strftime('%a, %d %b %Y %H:%M:%S %z', $dtstart), - strftime('%a, %d %b %Y %H:%M:%S %z', $dtend) - ), 'DEBUG'); - foreach ($busyperiods as $busyfrom => $busyto) { - if (empty($busyfrom) && empty($busyto)) { - continue; - } - Horde::logMessage(sprintf('Busy period from %s to %s', - strftime('%a, %d %b %Y %H:%M:%S %z', $busyfrom), - strftime('%a, %d %b %Y %H:%M:%S %z', $busyto) - ), 'DEBUG'); - if ((isset($extraparams[$busyfrom]['X-UID']) - && in_array(base64_decode($extraparams[$busyfrom]['X-UID']), $ignore)) - || (isset($extraparams[$busyfrom]['X-SID']) - && in_array(base64_decode($extraparams[$busyfrom]['X-SID']), $ignore))) { - // Ignore - continue; - } - if (($busyfrom >= $dtstart && $busyfrom < $dtend) || ($dtstart >= $busyfrom && $dtstart < $busyto)) { - Horde::logMessage('Request overlaps', 'DEBUG'); - $conflict = true; - break; - } - } - if ($conflict) { - break; - } - } - - if ($conflict) { - if ($action == RM_ACT_MANUAL_IF_CONFLICTS) { - //sendITipReply(RM_ITIP_TENTATIVE); - Horde::logMessage('Conflict detected; Passing mail through', 'INFO'); - return true; - } else if ($action == RM_ACT_REJECT_IF_CONFLICTS) { - Horde::logMessage('Conflict detected; rejecting', 'INFO'); - $this->sendITipReply($cn, $id, $itip, RM_ITIP_DECLINE, - $organiser, $uid, $is_update); - return false; - } - } - } - } - - if (is_a($imap_error, 'PEAR_Error')) { - Horde::logMessage('Could not access users calendar; rejecting', 'INFO'); - $this->sendITipReply($cn, $id, $itip, RM_ITIP_DECLINE, - $organiser, $uid, $is_update); - return false; - } - - // At this point there was either no conflict or RM_ACT_ALWAYS_ACCEPT - // was specified; either way we add the new event & send an 'ACCEPT' - // iTip reply - - Horde::logMessage(sprintf('Adding event %s', $uid), 'INFO'); - - if (!empty($conf['kolab']['filter']['simple_locks'])) { - if (!empty($conf['kolab']['filter']['simple_locks_timeout'])) { - $timeout = $conf['kolab']['filter']['simple_locks_timeout']; - } else { - $timeout = 60; - } - if (!empty($conf['kolab']['filter']['simple_locks_dir'])) { - $lockdir = $conf['kolab']['filter']['simple_locks_dir']; - } else { - $lockdir = Horde::getTempDir() . '/Kolab_Filter_locks'; - if (!is_dir($lockdir)) { - mkdir($lockdir, 0700); - } - } - if (is_dir($lockdir)) { - $lockfile = $lockdir . '/' . $resource . '.lock'; - $counter = 0; - while ($counter < $timeout && @file_get_contents($lockfile) == 'LOCKED') { - sleep(1); - $counter++; - } - if ($counter == $timeout) { - Horde::logMessage(sprintf('Lock timeout of %s seconds exceeded. Rejecting invitation.', $timeout), 'ERR'); - $this->sendITipReply($cn, $id, $itip, RM_ITIP_DECLINE, - $organiser, $uid, $is_update); - return false; - } - $result = file_put_contents($lockfile, 'LOCKED'); - if ($result === false) { - Horde::logMessage(sprintf('Failed creating lock file %s.', $lockfile), 'ERR'); - } else { - $this->lockfile = $lockfile; - } - } else { - Horde::logMessage(sprintf('The lock directory %s is missing. Disabled locking.', $lockdir), 'ERR'); - } - } - - $itip->setAccepted($resource); - - $result = $data->save($itip->getKolabObject(), $old_uid); - if (is_a($result, 'PEAR_Error')) { - $result->code = OUT_LOG | EX_UNAVAILABLE; - return $result; - } - - if ($outofperiod) { - $this->sendITipReply($cn, $resource, $itip, RM_ITIP_TENTATIVE, - $organiser, $uid, $is_update); - Horde::logMessage('No freebusy information available', 'NOTICE'); - } else { - $this->sendITipReply($cn, $resource, $itip, RM_ITIP_ACCEPT, - $organiser, $uid, $is_update); - } - return false; - - case 'CANCEL': - Horde::logMessage(sprintf('Removing event %s', $uid), 'INFO'); - - if (is_a($imap_error, 'PEAR_Error')) { - $body = sprintf(_("Unable to access %s's calendar:"), $resource) . "\n\n" . $summary; - $subject = sprintf(_("Error processing \"%s\""), $summary); - } else if (!$data->objectUidExists($uid)) { - Horde::logMessage(sprintf('Canceled event %s is not present in %s\'s calendar', - $uid, $resource), 'WARNING'); - $body = sprintf(_("The following event that was canceled is not present in %s's calendar:"), $resource) . "\n\n" . $summary; - $subject = sprintf(_("Error processing \"%s\""), $summary); - } else { - /** - * Delete the messages from IMAP - * Delete any old events that we updated - */ - Horde::logMessage(sprintf('Deleting %s because of cancel', - $uid), 'DEBUG'); - - $result = $data->delete($uid); - if (is_a($result, 'PEAR_Error')) { - Horde::logMessage(sprintf('Deleting %s failed with %s', - $uid, $result->getMessage()), 'DEBUG'); - } - - $body = _("The following event has been successfully removed:") . "\n\n" . $summary; - $subject = sprintf(_("%s has been cancelled"), $summary); - } - - Horde::logMessage(sprintf('Sending confirmation of cancelation to %s', $organiser), 'WARNING'); - - $body = new MIME_Part('text/plain', Horde_String::wrap($body, 76, "\n", 'utf-8'), 'utf-8'); - $mime = &MIME_Message::convertMimePart($body); - $mime->setTransferEncoding('quoted-printable'); - $mime->transferEncodeContents(); - - // Build the reply headers. - $msg_headers = new MIME_Headers(); - $msg_headers->addHeader('Date', date('r')); - $msg_headers->addHeader('From', $resource); - $msg_headers->addHeader('To', $organiser); - $msg_headers->addHeader('Subject', $subject); - $msg_headers->addMIMEHeaders($mime); - - $result = $this->transportReply($resource, MIME::encodeAddress($organiser), - $msg_headers->toString() . '\r\n\r\n' . $mime->toString()); - if (is_a($result, 'PEAR_Error')) { - return $result; - } - - Horde::logMessage('Successfully sent cancellation reply', 'INFO'); - - return false; - - default: - // We either don't currently handle these iTip methods, or they do not - // apply to what we're trying to accomplish here - Horde::logMessage(sprintf('Ignoring %s method and passing message through to %s', - $method, $resource), 'INFO'); - return true; - } - } - - /** - * Helper function to clean up after handling an invitation - * - * @return NULL - */ - function cleanup() - { - if (!empty($this->lockfile)) { - @unlink($this->lockfile); - if (file_exists($this->lockfile)) { - Horde::logMessage(sprintf('Failed removing the lockfile %s.', $lockfile), 'ERR'); - } - $this->lockfile = null; - } - } - - /** - * Send an automated reply. - * - * @param string $cn Common name to be used in the iTip - * response. - * @param string $resource Resource we send the reply for. - * @param string $Horde_iCalendar_vevent The iTip information. - * @param int $type Type of response. - * @param string $organiser The event organiser. - * @param string $uid The UID of the event. - * @param boolean $is_update Is this an event update? - */ - function sendITipReply($cn, $resource, $itip, $type = RM_ITIP_ACCEPT, - $organiser, $uid, $is_update) - { - Horde::logMessage(sprintf('sendITipReply(%s, %s, %s, %s)', - $cn, $resource, get_class($itip), $type), 'DEBUG'); - - // Build the reply. - $vCal = new Horde_iCalendar(); - $vCal->setAttribute('PRODID', '-//kolab.org//NONSGML Kolab Server 2//EN'); - $vCal->setAttribute('METHOD', 'REPLY'); - - $summary = _('No summary available'); - - $itip_reply =& Horde_iCalendar::newComponent('VEVENT', $vCal); - $itip_reply->setAttribute('UID', $uid); - if (!is_a($itip->getAttribute('SUMMARY'), 'PEAR_error')) { - $itip_reply->setAttribute('SUMMARY', $itip->getAttribute('SUMMARY')); - $summary = $itip->getAttribute('SUMMARY'); - } - if (!is_a($itip->getAttribute('DESCRIPTION'), 'PEAR_error')) { - $itip_reply->setAttribute('DESCRIPTION', $itip->getAttribute('DESCRIPTION')); - } - if (!is_a($itip->getAttribute('LOCATION'), 'PEAR_error')) { - $itip_reply->setAttribute('LOCATION', $itip->getAttribute('LOCATION')); - } - $itip_reply->setAttribute('DTSTART', $itip->getAttribute('DTSTART'), array_pop($itip->getAttribute('DTSTART', true))); - if (!is_a($itip->getAttribute('DTEND'), 'PEAR_error')) { - $itip_reply->setAttribute('DTEND', $itip->getAttribute('DTEND'), array_pop($itip->getAttribute('DTEND', true))); - } else { - $itip_reply->setAttribute('DURATION', $itip->getAttribute('DURATION'), array_pop($itip->getAttribute('DURATION', true))); - } - if (!is_a($itip->getAttribute('SEQUENCE'), 'PEAR_error')) { - $itip_reply->setAttribute('SEQUENCE', $itip->getAttribute('SEQUENCE')); - } else { - $itip_reply->setAttribute('SEQUENCE', 0); - } - $itip_reply->setAttribute('ORGANIZER', $itip->getAttribute('ORGANIZER'), array_pop($itip->getAttribute('ORGANIZER', true))); - - // Let's try and remove this code and just create - // the ATTENDEE stuff in the reply from scratch - // $attendees = $itip->getAttribute( 'ATTENDEE' ); - // if( !is_array( $attendees ) ) { - // $attendees = array( $attendees ); - // } - // $params = $itip->getAttribute( 'ATTENDEE', true ); - // for( $i = 0; $i < count($attendees); $i++ ) { - // $attendee = preg_replace('/^mailto:\s*/i', '', $attendees[$i]); - // if ($attendee != $resource) { - // continue; - // } - // $params = $params[$i]; - // break; - // } - - $params = array(); - $params['CN'] = $cn; - switch ($type) { - case RM_ITIP_DECLINE: - Horde::logMessage(sprintf('Sending DECLINE iTip reply to %s', - $organiser), 'DEBUG'); - $message = $is_update - ? sprintf(_("%s has declined the update to the following event:"), $resource) . "\n\n" . $summary - : sprintf(_("%s has declined the invitation to the following event:"), $resource) . "\n\n" . $summary; - $subject = _("Declined: ") . $summary; - $params['PARTSTAT'] = 'DECLINED'; - break; - - case RM_ITIP_ACCEPT: - Horde::logMessage(sprintf('Sending ACCEPT iTip reply to %s', $organiser), 'DEBUG'); - $message = $is_update - ? sprintf(_("%s has accepted the update to the following event:"), $resource) . "\n\n" . $summary - : sprintf(_("%s has accepted the invitation to the following event:"), $resource) . "\n\n" . $summary; - $subject = _("Accepted: ") . $summary; - $params['PARTSTAT'] = 'ACCEPTED'; - break; - - case RM_ITIP_TENTATIVE: - Horde::logMessage(sprintf('Sending TENTATIVE iTip reply to %s', $organiser), 'DEBUG'); - $message = $is_update - ? sprintf(_("%s has tentatively accepted the update to the following event:"), $resource) . "\n\n" . $summary - : sprintf(_("%s has tentatively accepted the invitation to the following event:"), $resource) . "\n\n" . $summary; - $subject = _("Tentative: ") . $summary; - $params['PARTSTAT'] = 'TENTATIVE'; - break; - - default: - Horde::logMessage(sprintf('Unknown iTip method (%s passed to sendITipReply())', $type), 'ERR'); - } - - $itip_reply->setAttribute('ATTENDEE', 'MAILTO:' . $resource, $params); - $vCal->addComponent($itip_reply); - - $ics = new MIME_Part('text/calendar', $vCal->exportvCalendar(), 'UTF-8' ); - //$ics->setName('event-reply.ics'); - $ics->setContentTypeParameter('method', 'REPLY'); - - //$mime->addPart($body); - //$mime->addPart($ics); - // The following was ::convertMimePart($mime). This was removed so that we - // send out single-part MIME replies that have the iTip file as the body, - // with the correct mime-type header set, etc. The reason we want to do this - // is so that Outlook interprets the messages as it does Outlook-generated - // responses, i.e. double-clicking a reply will automatically update your - // meetings, showing different status icons in the UI, etc. - $mime = &MIME_Message::convertMimePart($ics); - $mime->setCharset('UTF-8'); - $mime->setTransferEncoding('quoted-printable'); - $mime->transferEncodeContents(); - - // Build the reply headers. - $msg_headers = new MIME_Headers(); - $msg_headers->addHeader('Date', date('r')); - $msg_headers->addHeader('From', "$cn <$resource>"); - $msg_headers->addHeader('To', $organiser); - $msg_headers->addHeader('Subject', $subject); - $msg_headers->addMIMEHeaders($mime); - - $result = $this->transportReply($resource, MIME::encodeAddress($organiser), - $msg_headers->toString() . '\r\n\r\n' . $mime->toString()); - if (is_a($result, 'PEAR_Error')) { - return $result; - } - - Horde::logMessage('Successfully sent iTip reply', 'DEBUG'); - } - - - function transportReply($sender, $recipients, $data) - { - global $conf; - - if (isset($conf['kolab']['filter']['itipreply'])) { - $driver = $conf['kolab']['filter']['itipreply']['driver']; - $host = $conf['kolab']['filter']['itipreply']['params']['host']; - $port = $conf['kolab']['filter']['itipreply']['params']['port']; - } else { - $driver = 'smtp'; - $host = 'localhost'; - $port = 25; - } - - $transport = &Horde_Kolab_Filter_Transport::factory($driver, - array('host' => $host, - 'port' => $port)); - - $result = $transport->start($sender, $recipients); - if (is_a($result, 'PEAR_Error')) { - return PEAR::raiseError('Unable to send iTip reply: ' . $result->getMessage(), - OUT_LOG | EX_TEMPFAIL); - } - - $result = $transport->data($data); - if (is_a($result, 'PEAR_Error')) { - return PEAR::raiseError('Unable to send iTip reply: ' . $result->getMessage(), - OUT_LOG | EX_TEMPFAIL); - } - - $result = $transport->end(); - if (is_a($result, 'PEAR_Error')) { - return PEAR::raiseError('Unable to send iTip reply: ' . $result->getMessage(), - OUT_LOG | EX_TEMPFAIL); - } - } - - /** - * 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; - } - - /** - * Conveert iCal dates to Kolab format. - * - * An all day event must have a dd--mm-yyyy notation and not a - * yyyy-dd-mmT00:00:00z notation Otherwise the event is shown as a - * 2-day event --> do not try to convert everything to epoch first - * - * @param array $ical_date The array to convert. - * @param string $type The type of the date to convert. - * - * @return string The converted date. - */ - function iCalDate2Kolab($ical_date, $type= ' ') - { - Horde::logMessage(sprintf('Converting to kolab format %s', - print_r($ical_date, true)), 'DEBUG'); - - // $ical_date should be a timestamp - if (is_array($ical_date)) { - // going to create date again - $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= $etemp->getEpoch() - 86400; - $date = gmstrftime('%Y-%m-%d', $epoch); - } else { - $date= sprintf('%04d-%02d-%02d', $temp['year'], $temp['month'], $temp['mday']); - } - } else { - $time = sprintf('%02d:%02d:%02d', $temp['hour'], $temp['minute'], $temp['second']); - if ($temp['zone'] == 'UTC') { - $time .= 'Z'; - } - $date = sprintf('%04d-%02d-%02d', $temp['year'], $temp['month'], $temp['mday']) . 'T' . $time; - } - } else { - $date = gmstrftime('%Y-%m-%dT%H:%M:%SZ', $ical_date); - } - Horde::logMessage(sprintf('To <%s>', $date), 'DEBUG'); - return $date; - } -} diff --git a/framework/Kolab_Filter/lib/Horde/Kolab/Resource/Epoch.php b/framework/Kolab_Filter/lib/Horde/Kolab/Resource/Epoch.php deleted file mode 100644 index a9c2c78ea..000000000 --- a/framework/Kolab_Filter/lib/Horde/Kolab/Resource/Epoch.php +++ /dev/null @@ -1,102 +0,0 @@ - - * @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. - */ - private 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/Freebusy.php b/framework/Kolab_Filter/lib/Horde/Kolab/Resource/Freebusy.php deleted file mode 100644 index ac783022f..000000000 --- a/framework/Kolab_Filter/lib/Horde/Kolab/Resource/Freebusy.php +++ /dev/null @@ -1,162 +0,0 @@ - - * @license http://www.fsf.org/copyleft/lgpl.html LGPL - * @link http://pear.horde.org/index.php?package=Kolab_Server - */ - -/** - * Retrieves free/busy data for an email address. - * - * Copyright 2004-2009 Klarälvdalens Datakonsult AB - * - * See the enclosed file COPYING for license information (LGPL>=2.1). 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 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_Freebusy -{ - /** - * Singleton instances. - * - * @var array - */ - static protected $_instances = array(); - - /** - * Class parameters. - * - * @var array - */ - protected $_params; - - /** - * Constructor. - * - * @param array $params A hash containing any additional configuration or - * connection parameters a subclass might need. - */ - protected function __construct($params) - { - $this->_params = $params; - } - - /** - * Attempts to return a concrete Horde_Kolab_Resource_Getfreebusy instance - * based on $driver. - * - * @param mixed $driver The type of concrete - * Horde_Kolab_Resource_Getfreebusy subclass to - * return. - * @param array $params A hash containing any additional configuration or - * connection parameters a subclass might need. - * - * @return Horde_Kolab_Resource_Getfreebusy The newly created concrete - * Horde_Kolab_Resource_Getfreebusy - * instance, or false an error. - */ - static public function factory($driver, $params = array()) - { - $driver = ucfirst(basename($driver)); - $class = ($driver == 'None') - ? 'Horde_Kolab_Resource_Freebusy' - : 'Horde_Kolab_Resource_Freebusy_' . $driver; - - require_once dirname(__FILE__) . '/Freebusy/' . $driver . '.php'; - - if (!class_exists($class)) { - $class = 'Horde_Kolab_Resource_Freebusy'; - } - - return new $class($params); - } - - /** - * Attempts to return a reference to a concrete - * Horde_Kolab_Resource_Getfreebusy instance based on $driver. - * - * It will only create a new instance if no Horde_Kolab_Resource_Getfreebusy - * instance with the same parameters currently exists. - * - * This method must be invoked as: - * $var = Horde_Kolab_Resource_Getfreebusy::singleton(); - * - * @param mixed $driver The type of concrete - * Horde_Kolab_Resource_Getfreebusy subclass to - * return. - * @param array $params A hash containing any additional configuration or - * connection parameters a subclass might need. - * - * @return Horde_Token The concrete Horde_Kolab_Resource_Getfreebusy - * reference, or false on error. - */ - static public function singleton($driver = null, $params = array()) - { - global $conf; - - if (isset($GLOBALS['KOLAB_FILTER_TESTING'])) { - $driver = 'mock'; - $params['data'] = $GLOBALS['KOLAB_FILTER_TESTING']; - } - - if (empty($driver)) { - $driver = $conf['freebusy']['driver']; - } - - ksort($params); - $sig = hash('md5', serialize(array($driver, $params))); - - if (!isset(self::$_instances[$sig])) { - self::$_instances[$sig] = Horde_Kolab_Resource_Freebusy::factory($driver, - $params); - } - - return self::$_instances[$sig]; - } - - /** - * Retrieve Free/Busy URL for the specified resource id. - * - * @param string $resource The id of the resource (usually a mail address). - * - * @return string The Free/Busy URL for that resource. - */ - protected function getUrl($resource) - { - return ''; - } - - /** - * Retrieve Free/Busy data for the specified resource. - * - * @param string $resource Fetch the Free/Busy data for this resource - * (usually a mail address). - * - * @return Horde_iCalendar_vfreebusy The Free/Busy data. - */ - public function get($resource) - { - /* Return an empty VFB object. */ - $vCal = new Horde_iCalendar(); - $vFb = Horde_iCalendar::newComponent('vfreebusy', $vCal); - $vFb->setAttribute('ORGANIZER', $resource); - - return $vFb; - - } -} \ No newline at end of file diff --git a/framework/Kolab_Filter/lib/Horde/Kolab/Resource/Freebusy/Kolab.php b/framework/Kolab_Filter/lib/Horde/Kolab/Resource/Freebusy/Kolab.php deleted file mode 100644 index ed860c4ee..000000000 --- a/framework/Kolab_Filter/lib/Horde/Kolab/Resource/Freebusy/Kolab.php +++ /dev/null @@ -1,117 +0,0 @@ - - * @author Gunnar Wrobel - * @license http://www.fsf.org/copyleft/lgpl.html LGPL - * @link http://pear.horde.org/index.php?package=Kolab_Server - */ - -/** - * Retrieves free/busy data for an email address on a Kolab server. - * - * Copyright 2004-2009 Klarälvdalens Datakonsult AB - * - * See the enclosed file COPYING for license information (LGPL>=2.1). 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_Freebusy_Kolab extends Horde_Kolab_Resource_Freebusy -{ - /** - * Retrieve Free/Busy URL for the specified resource id. - * - * @param string $resource The id of the resource (usually a mail address). - * - * @return string The Free/Busy URL for that resource. - */ - protected function getUrl($resource) - { - $server = Horde_Kolab_Server::singleton(); - $uid = $server->uidForIdOrMailOrAlias($resource); - $result = $server->fetch($uid)->getServer('freebusy'); - return sprintf('%s/%s.xfb', $result, $resource); - } - - /** - * Retrieve Free/Busy data for the specified resource. - * - * @param string $resource Fetch the Free/Busy data for this resource. - * - * @return Horde_iCalendar_vfreebusy The Free/Busy data. - */ - public function get($resource) - { - global $conf; - - $url = self::getUrl($resource); - - Horde::logMessage(sprintf('Freebusy URL for resource %s is %s', - $resource, $url), 'DEBUG'); - - list($user, $domain) = explode('@', $resource); - if (empty($domain)) { - $domain = $conf['kolab']['filter']['email_domain']; - } - - /** - * This section matches Kronolith_Freebusy and should be merged with it - * again in a single Horde_Freebusy module. - */ - $options = array( - 'method' => 'GET', - 'timeout' => 5, - 'allowRedirects' => true - ); - - if (!empty($conf['http']['proxy']['proxy_host'])) { - $options = array_merge($options, $conf['http']['proxy']); - } - - $http = new HTTP_Request($url, $options); - $http->setBasicAuth($conf['kolab']['filter']['calendar_id'] . '@' . $domain, - $conf['kolab']['filter']['calendar_pass']); - @$http->sendRequest(); - if ($http->getResponseCode() != 200) { - throw new Horde_Kolab_Filter_Exception(sprintf('Unable to retrieve free/busy information for %s', - $resource), - Horde_Kolab_Filter_Exception::NO_FREEBUSY); - } - $vfb_text = $http->getResponseBody(); - - // Detect the charset of the iCalendar data. - $contentType = $http->getResponseHeader('Content-Type'); - if ($contentType && strpos($contentType, ';') !== false) { - list(,$charset,) = explode(';', $contentType); - $charset = trim(str_replace('charset=', '', $charset)); - } else { - $charset = 'UTF-8'; - } - - $iCal = new Horde_iCalendar; - $iCal->parsevCalendar($vfb_text, 'VCALENDAR', $charset); - - $vfb = &$iCal->findComponent('VFREEBUSY'); - - if ($vfb === false) { - throw new Horde_Kolab_Filter_Exception(sprintf('Invalid or no free/busy information available for %s', - $resource), - Horde_Kolab_Filter_Exception::NO_FREEBUSY); - } - $vfb->simplify(); - - return $vfb; - } -} diff --git a/framework/Kolab_Filter/lib/Horde/Kolab/Resource/Freebusy/Mock.php b/framework/Kolab_Filter/lib/Horde/Kolab/Resource/Freebusy/Mock.php deleted file mode 100644 index 9169b051b..000000000 --- a/framework/Kolab_Filter/lib/Horde/Kolab/Resource/Freebusy/Mock.php +++ /dev/null @@ -1,55 +0,0 @@ - - * @license http://www.fsf.org/copyleft/lgpl.html LGPL - * @link http://pear.horde.org/index.php?package=Kolab_Server - */ - -/** - * Retrieves free/busy mockup data. - * - * Copyright 2004-2009 Klarälvdalens Datakonsult AB - * - * See the enclosed file COPYING for license information (LGPL>=2.1). 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 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_Freebusy_Mock extends Horde_Kolab_Resource_Freebusy -{ - /** - * Retrieve Free/Busy URL for the specified resource id. - * - * @param string $resource The id of the resource (usually a mail address). - * - * @return string The Free/Busy URL for that resource. - */ - protected function getUrl($resource) - { - return ''; - } - - /** - * Retrieve Free/Busy data for the specified resource. - * - * @param string $resource Fetch the Free/Busy data for this resource - * (usually a mail address). - * - * @return Horde_iCalendar_vfreebusy The Free/Busy data. - */ - public function get($resource) - { - return $this->_params['data']; - } -} \ 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 deleted file mode 100644 index fea369788..000000000 --- a/framework/Kolab_Filter/lib/Horde/Kolab/Resource/Itip.php +++ /dev/null @@ -1,257 +0,0 @@ - - * @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_Resource/COPYING b/framework/Kolab_Resource/COPYING new file mode 100644 index 000000000..d1c6b98aa --- /dev/null +++ b/framework/Kolab_Resource/COPYING @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/framework/Kolab_Resource/lib/Horde/Kolab/Resource.php b/framework/Kolab_Resource/lib/Horde/Kolab/Resource.php new file mode 100644 index 000000000..e08377405 --- /dev/null +++ b/framework/Kolab_Resource/lib/Horde/Kolab/Resource.php @@ -0,0 +1,812 @@ + + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ + +/** Load the iCal handling */ +require_once 'Horde/iCalendar.php'; + +/** Load MIME handlers */ +require_once 'Horde/MIME.php'; +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_Resource elements */ +require_once 'Horde/Kolab/Resource/Epoch.php'; +require_once 'Horde/Kolab/Resource/Itip.php'; +require_once 'Horde/Kolab/Resource/Reply.php'; +require_once 'Horde/Kolab/Resource/Freebusy.php'; + +require_once 'Horde/String.php'; +Horde_String::setDefaultCharset('utf-8'); + +// What actions we can take when receiving an event request +define('RM_ACT_ALWAYS_ACCEPT', 'ACT_ALWAYS_ACCEPT'); +define('RM_ACT_REJECT_IF_CONFLICTS', 'ACT_REJECT_IF_CONFLICTS'); +define('RM_ACT_MANUAL_IF_CONFLICTS', 'ACT_MANUAL_IF_CONFLICTS'); +define('RM_ACT_MANUAL', 'ACT_MANUAL'); +define('RM_ACT_ALWAYS_REJECT', 'ACT_ALWAYS_REJECT'); + +// What possible ITIP notification we can send +define('RM_ITIP_DECLINE', 1); +define('RM_ITIP_ACCEPT', 2); +define('RM_ITIP_TENTATIVE', 3); + +/** + * Provides Kolab resource handling + * + * 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. + * + * @package Kolab_Filter + * @author Steffen Hansen + * @author Gunnar Wrobel + */ +class Kolab_Resource +{ + + /** + * Returns the resource policy applying for the given sender + * + * @param string $sender The sender address + * @param string $resource The resource + * + * @return array|PEAR_Error An array with "cn", "home server" and the policy. + */ + function _getResourceData($sender, $resource) + { + require_once 'Horde/Kolab/Server.php'; + $db = Horde_Kolab_Server::singleton(); + if ($db instanceOf PEAR_Error) { + $db->code = OUT_LOG | EX_SOFTWARE; + return $db; + } + + $dn = $db->uidForMail($resource, Horde_Kolab_Server_Object::RESULT_MANY); + if ($dn instanceOf PEAR_Error) { + $dn->code = OUT_LOG | EX_NOUSER; + return $dn; + } + if (is_array($dn)) { + if (count($dn) > 1) { + Horde::logMessage(sprintf("%s objects returned for %s", + $count($dn), $resource), 'WARN'); + return false; + } else { + $dn = $dn[0]; + } + } + $user = $db->fetch($dn, 'Horde_Kolab_Server_Object_Kolab_User'); + + $cn = $user->get(Horde_Kolab_Server_Object_Kolab_User::ATTRIBUTE_CN); + $id = $user->get(Horde_Kolab_Server_Object_Kolab_User::ATTRIBUTE_MAIL); + $hs = $user->get(Horde_Kolab_Server_Object_Kolab_User::ATTRIBUTE_HOMESERVER); + if (is_a($hs, 'PEAR_Error')) { + return $hs; + } + $hs = strtolower($hs); + $actions = $user->get(Horde_Kolab_Server_Object_Kolab_User::ATTRIBUTE_IPOLICY, false); + if (is_a($actions, 'PEAR_Error')) { + $actions->code = OUT_LOG | EX_UNAVAILABLE; + return $actions; + } + if ($actions === false) { + $actions = array(RM_ACT_MANUAL); + } + + $policies = array(); + $defaultpolicy = false; + foreach ($actions as $action) { + if (preg_match('/(.*):(.*)/', $action, $regs)) { + $policies[strtolower($regs[1])] = $regs[2]; + } else { + $defaultpolicy = $action; + } + } + // Find sender's policy + if (array_key_exists($sender, $policies)) { + // We have an exact match, stop processing + $action = $policies[$sender]; + } else { + $action = false; + $dn = $db->uidForMailOrAlias($sender); + if (is_a($dn, 'PEAR_Error')) { + $dn->code = OUT_LOG | EX_NOUSER; + return $dn; + } + if ($dn) { + // Sender is local, check for groups + foreach ($policies as $gid => $policy) { + if ($db->memberOfGroupAddress($dn, $gid)) { + // User is member of group + if (!$action) { + $action = $policy; + } else { + $action = min($action, $policy); + } + } + } + } + if (!$action && $defaultpolicy) { + $action = $defaultpolicy; + } + } + return array('cn' => $cn, 'id' => $id, + 'homeserver' => $hs, 'action' => $action); + } + + function &_getICal($filename) + { + $requestText = ''; + $handle = fopen($filename, 'r'); + while (!feof($handle)) { + $requestText .= fread($handle, 8192); + } + + $mime = &MIME_Structure::parseTextMIMEMessage($requestText); + + $parts = $mime->contentTypeMap(); + foreach ($parts as $mimeid => $conttype) { + if ($conttype == 'text/calendar') { + $part = $mime->getPart($mimeid); + + $iCalendar = new Horde_iCalendar(); + $iCalendar->parsevCalendar($part->transferDecode()); + + return $iCalendar; + } + } + // No iCal found + return false; + } + + function _imapConnect($id) + { + global $conf; + + // Handle virtual domains + list($user, $domain) = explode('@', $id); + if (empty($domain)) { + $domain = $conf['kolab']['filter']['email_domain']; + } + $calendar_user = $conf['kolab']['filter']['calendar_id'] . '@' . $domain; + + /* Load the authentication libraries */ + $auth = $GLOBALS['injector']->getInstance('Horde_Auth')->getAuth(isset($conf['auth']['driver']) ? null : 'kolab'); + $authenticated = $auth->authenticate($calendar_user, + array('password' => $conf['kolab']['filter']['calendar_pass']), + false); + + if (is_a($authenticated, 'PEAR_Error')) { + $authenticated->code = OUT_LOG | EX_UNAVAILABLE; + return $authenticated; + } + if (!$authenticated) { + return PEAR::raiseError(sprintf('Failed to authenticate as calendar user: %s', + $auth->getLogoutReasonString()), + OUT_LOG | EX_UNAVAILABLE); + } + @session_start(); + + $secret = $GLOBALS['injector']->getInstance('Horde_Secret'); + + $_SESSION['__auth'] = array( + 'authenticated' => true, + 'userId' => $calendar_user, + 'timestamp' => time(), + 'credentials' => $secret->write($secret->getKey('auth'), + serialize(array('password' => $conf['kolab']['filter']['calendar_pass']))), + 'remote_addr' => isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null, + ); + + /* Kolab IMAP handling */ + require_once 'Horde/Kolab/Storage/List.php'; + $list = &Kolab_List::singleton(); + $default = $list->getForeignDefault($id, 'event'); + if (!$default || is_a($default, 'PEAR_Error')) { + $default = new Kolab_Folder(); + $default->setList($list); + $default->setName($conf['kolab']['filter']['calendar_store']); + //FIXME: The calendar user needs access here + $attributes = array('default' => true, + 'type' => 'event', + 'owner' => $id); + $result = $default->save($attributes); + if (is_a($result, 'PEAR_Error')) { + $result->code = OUT_LOG | EX_UNAVAILABLE; + return $result; + } + } + return $default; + } + + function handleMessage($fqhostname, $sender, $resource, $tmpfname) + { + global $conf; + + $rdata = $this->_getResourceData($sender, $resource); + if (is_a($rdata, 'PEAR_Error')) { + return $rdata; + } else if ($rdata === false) { + /* No data, probably not a local user */ + return true; + } else if ($rdata['homeserver'] && $rdata['homeserver'] != $fqhostname) { + /* Not the users homeserver, ignore */ + return true; + } + + $cn = $rdata['cn']; + $id = $rdata['id']; + if (isset($rdata['action'])) { + $action = $rdata['action']; + } else { + // Manual is the only safe default! + $action = RM_ACT_MANUAL; + } + Horde::logMessage(sprintf('Action for %s is %s', + $sender, $action), 'DEBUG'); + + // Get out as early as possible if manual + if ($action == RM_ACT_MANUAL) { + Horde::logMessage(sprintf('Passing through message to %s', $id), 'INFO'); + return true; + } + + /* Get the iCalendar data (i.e. the iTip request) */ + $iCalendar = &$this->_getICal($tmpfname); + if ($iCalendar === false) { + // No iCal in mail + Horde::logMessage(sprintf('Could not parse iCalendar data, passing through to %s', $id), 'INFO'); + return true; + } + // Get the event details out of the iTip request + $itip = &$iCalendar->findComponent('VEVENT'); + if ($itip === false) { + 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->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->getUid(); + Horde::logMessage(sprintf('Event has UID %s', $uid), 'DEBUG'); + + // Who is the organiser? + $organiser = $itip->getOrganizer(); + Horde::logMessage(sprintf('Request made by %s', $organiser), 'DEBUG'); + + // What is the events summary? + $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'); + + if ($action == RM_ACT_ALWAYS_REJECT) { + if ($method == 'REQUEST') { + Horde::logMessage(sprintf('Rejecting %s method', $method), 'INFO'); + return $this->sendITipReply($cn, $resource, $itip, RM_ITIP_DECLINE, + $organiser, $uid, $is_update); + } else { + Horde::logMessage(sprintf('Passing through %s method for ACT_ALWAYS_REJECT policy', $method), 'INFO'); + return true; + } + } + + $is_update = false; + $imap_error = false; + $ignore = array(); + + $folder = $this->_imapConnect($id); + if (is_a($folder, 'PEAR_Error')) { + $imap_error = &$folder; + } + if (!is_a($imap_error, 'PEAR_Error') && !$folder->exists()) { + $imap_error = &PEAR::raiseError('Error, could not open calendar folder!', + OUT_LOG | EX_TEMPFAIL); + } + + if (!is_a($imap_error, 'PEAR_Error')) { + $data = $folder->getData(); + if (is_a($data, 'PEAR_Error')) { + $imap_error = &$data; + } + } + + if (is_a($imap_error, 'PEAR_Error')) { + Horde::logMessage(sprintf('Failed accessing IMAP calendar: %s', + $folder->getMessage()), 'ERR'); + if ($action == RM_ACT_MANUAL_IF_CONFLICTS) { + return true; + } + } + + switch ($method) { + case 'REQUEST': + if ($action == RM_ACT_MANUAL) { + Horde::logMessage(sprintf('Passing through %s method', $method), 'INFO'); + break; + } + + if (is_a($imap_error, 'PEAR_Error') || !$data->objectUidExists($uid)) { + $old_uid = null; + } else { + $old_uid = $uid; + $ignore[] = $uid; + $is_update = true; + } + + /** Generate the Kolab object */ + $object = $itip->getKolabObject(); + + $outofperiod=0; + + // Don't even bother checking free/busy info if RM_ACT_ALWAYS_ACCEPT + // is specified + if ($action != RM_ACT_ALWAYS_ACCEPT) { + + try { + require_once 'Horde/Kolab/Resource/Freebusy.php'; + $fb = Horde_Kolab_Resource_Freebusy::singleton(); + $vfb = $fb->get($resource); + } catch (Exception $e) { + return PEAR::raiseError($e->getMessage(), + OUT_LOG | EX_UNAVAILABLE); + } + + $vfbstart = $vfb->getAttributeDefault('DTSTART', 0); + $vfbend = $vfb->getAttributeDefault('DTEND', 0); + Horde::logMessage(sprintf('Free/busy info starts on <%s> %s and ends on <%s> %s', + $vfbstart, $this->iCalDate2Kolab($vfbstart), $vfbend, $this->iCalDate2Kolab($vfbend)), 'DEBUG'); + + $evfbend = new Horde_Kolab_Resource_Epoch($vfbend); + if ($vfbstart && $dtstart > $evfbend->getEpoch()) { + $outofperiod=1; + } else { + // Check whether we are busy or not + $busyperiods = $vfb->getBusyPeriods(); + Horde::logMessage(sprintf('Busyperiods: %s', + print_r($busyperiods, true)), 'DEBUG'); + $extraparams = $vfb->getExtraParams(); + Horde::logMessage(sprintf('Extraparams: %s', + print_r($extraparams, true)), 'DEBUG'); + $conflict = false; + if (!empty($object['recurrence'])) { + $recurrence = new Horde_Date_Recurrence($dtstart); + $recurrence->fromHash($object['recurrence']); + $duration = $dtend - $dtstart; + $events = array(); + $next_start = $vfbstart; + $next = $recurrence->nextActiveRecurrence($vfbstart); + while ($next !== false && $next->compareDate($vfbend) <= 0) { + $next_ts = $next->timestamp(); + $events[$next_ts] = $next_ts + $duration; + $next = $recurrence->nextActiveRecurrence(array('year' => $next->year, + 'month' => $next->month, + 'mday' => $next->mday + 1, + 'hour' => $next->hour, + 'min' => $next->min, + 'sec' => $next->sec)); + } + } else { + $events = array($dtstart => $dtend); + } + + foreach ($events as $dtstart => $dtend) { + Horde::logMessage(sprintf('Requested event from %s to %s', + strftime('%a, %d %b %Y %H:%M:%S %z', $dtstart), + strftime('%a, %d %b %Y %H:%M:%S %z', $dtend) + ), 'DEBUG'); + foreach ($busyperiods as $busyfrom => $busyto) { + if (empty($busyfrom) && empty($busyto)) { + continue; + } + Horde::logMessage(sprintf('Busy period from %s to %s', + strftime('%a, %d %b %Y %H:%M:%S %z', $busyfrom), + strftime('%a, %d %b %Y %H:%M:%S %z', $busyto) + ), 'DEBUG'); + if ((isset($extraparams[$busyfrom]['X-UID']) + && in_array(base64_decode($extraparams[$busyfrom]['X-UID']), $ignore)) + || (isset($extraparams[$busyfrom]['X-SID']) + && in_array(base64_decode($extraparams[$busyfrom]['X-SID']), $ignore))) { + // Ignore + continue; + } + if (($busyfrom >= $dtstart && $busyfrom < $dtend) || ($dtstart >= $busyfrom && $dtstart < $busyto)) { + Horde::logMessage('Request overlaps', 'DEBUG'); + $conflict = true; + break; + } + } + if ($conflict) { + break; + } + } + + if ($conflict) { + if ($action == RM_ACT_MANUAL_IF_CONFLICTS) { + //sendITipReply(RM_ITIP_TENTATIVE); + Horde::logMessage('Conflict detected; Passing mail through', 'INFO'); + return true; + } else if ($action == RM_ACT_REJECT_IF_CONFLICTS) { + Horde::logMessage('Conflict detected; rejecting', 'INFO'); + return $this->sendITipReply($cn, $id, $itip, RM_ITIP_DECLINE, + $organiser, $uid, $is_update); + } + } + } + } + + if (is_a($imap_error, 'PEAR_Error')) { + Horde::logMessage('Could not access users calendar; rejecting', 'INFO'); + return $this->sendITipReply($cn, $id, $itip, RM_ITIP_DECLINE, + $organiser, $uid, $is_update); + } + + // At this point there was either no conflict or RM_ACT_ALWAYS_ACCEPT + // was specified; either way we add the new event & send an 'ACCEPT' + // iTip reply + + Horde::logMessage(sprintf('Adding event %s', $uid), 'INFO'); + + if (!empty($conf['kolab']['filter']['simple_locks'])) { + if (!empty($conf['kolab']['filter']['simple_locks_timeout'])) { + $timeout = $conf['kolab']['filter']['simple_locks_timeout']; + } else { + $timeout = 60; + } + if (!empty($conf['kolab']['filter']['simple_locks_dir'])) { + $lockdir = $conf['kolab']['filter']['simple_locks_dir']; + } else { + $lockdir = Horde::getTempDir() . '/Kolab_Filter_locks'; + if (!is_dir($lockdir)) { + mkdir($lockdir, 0700); + } + } + if (is_dir($lockdir)) { + $lockfile = $lockdir . '/' . $resource . '.lock'; + $counter = 0; + while ($counter < $timeout && @file_get_contents($lockfile) == 'LOCKED') { + sleep(1); + $counter++; + } + if ($counter == $timeout) { + Horde::logMessage(sprintf('Lock timeout of %s seconds exceeded. Rejecting invitation.', $timeout), 'ERR'); + return $this->sendITipReply($cn, $id, $itip, RM_ITIP_DECLINE, + $organiser, $uid, $is_update); + } + $result = file_put_contents($lockfile, 'LOCKED'); + if ($result === false) { + Horde::logMessage(sprintf('Failed creating lock file %s.', $lockfile), 'ERR'); + } else { + $this->lockfile = $lockfile; + } + } else { + Horde::logMessage(sprintf('The lock directory %s is missing. Disabled locking.', $lockdir), 'ERR'); + } + } + + $itip->setAccepted($resource); + + $result = $data->save($itip->getKolabObject(), $old_uid); + if (is_a($result, 'PEAR_Error')) { + $result->code = OUT_LOG | EX_UNAVAILABLE; + return $result; + } + + if ($outofperiod) { + Horde::logMessage('No freebusy information available', 'NOTICE'); + return $this->sendITipReply($cn, $resource, $itip, RM_ITIP_TENTATIVE, + $organiser, $uid, $is_update); + } else { + return $this->sendITipReply($cn, $resource, $itip, RM_ITIP_ACCEPT, + $organiser, $uid, $is_update); + } + + case 'CANCEL': + Horde::logMessage(sprintf('Removing event %s', $uid), 'INFO'); + + if (is_a($imap_error, 'PEAR_Error')) { + $body = sprintf(_("Unable to access %s's calendar:"), $resource) . "\n\n" . $summary; + $subject = sprintf(_("Error processing \"%s\""), $summary); + } else if (!$data->objectUidExists($uid)) { + Horde::logMessage(sprintf('Canceled event %s is not present in %s\'s calendar', + $uid, $resource), 'WARNING'); + $body = sprintf(_("The following event that was canceled is not present in %s's calendar:"), $resource) . "\n\n" . $summary; + $subject = sprintf(_("Error processing \"%s\""), $summary); + } else { + /** + * Delete the messages from IMAP + * Delete any old events that we updated + */ + Horde::logMessage(sprintf('Deleting %s because of cancel', + $uid), 'DEBUG'); + + $result = $data->delete($uid); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage(sprintf('Deleting %s failed with %s', + $uid, $result->getMessage()), 'DEBUG'); + } + + $body = _("The following event has been successfully removed:") . "\n\n" . $summary; + $subject = sprintf(_("%s has been cancelled"), $summary); + } + + Horde::logMessage(sprintf('Sending confirmation of cancelation to %s', $organiser), 'WARNING'); + + $body = new MIME_Part('text/plain', Horde_String::wrap($body, 76, "\n", 'utf-8'), 'utf-8'); + $mime = &MIME_Message::convertMimePart($body); + $mime->setTransferEncoding('quoted-printable'); + $mime->transferEncodeContents(); + + // Build the reply headers. + $msg_headers = new MIME_Headers(); + $msg_headers->addHeader('Date', date('r')); + $msg_headers->addHeader('From', $resource); + $msg_headers->addHeader('To', $organiser); + $msg_headers->addHeader('Subject', $subject); + $msg_headers->addMIMEHeaders($mime); + + $reply = new Horde_Kolab_Resource_Reply( + $resource, $organiser, $msg_headers, $mime + ); + Horde::logMessage('Successfully prepared cancellation reply', 'INFO'); + return $reply; + + default: + // We either don't currently handle these iTip methods, or they do not + // apply to what we're trying to accomplish here + Horde::logMessage(sprintf('Ignoring %s method and passing message through to %s', + $method, $resource), 'INFO'); + return true; + } + } + + /** + * Helper function to clean up after handling an invitation + * + * @return NULL + */ + function cleanup() + { + if (!empty($this->lockfile)) { + @unlink($this->lockfile); + if (file_exists($this->lockfile)) { + Horde::logMessage(sprintf('Failed removing the lockfile %s.', $lockfile), 'ERR'); + } + $this->lockfile = null; + } + } + + /** + * Send an automated reply. + * + * @param string $cn Common name to be used in the iTip + * response. + * @param string $resource Resource we send the reply for. + * @param string $Horde_iCalendar_vevent The iTip information. + * @param int $type Type of response. + * @param string $organiser The event organiser. + * @param string $uid The UID of the event. + * @param boolean $is_update Is this an event update? + */ + function sendITipReply($cn, $resource, $itip, $type = RM_ITIP_ACCEPT, + $organiser, $uid, $is_update) + { + Horde::logMessage(sprintf('sendITipReply(%s, %s, %s, %s)', + $cn, $resource, get_class($itip), $type), 'DEBUG'); + + // Build the reply. + $vCal = new Horde_iCalendar(); + $vCal->setAttribute('PRODID', '-//kolab.org//NONSGML Kolab Server 2//EN'); + $vCal->setAttribute('METHOD', 'REPLY'); + + $summary = _('No summary available'); + + $itip_reply =& Horde_iCalendar::newComponent('VEVENT', $vCal); + $itip_reply->setAttribute('UID', $uid); + if (!is_a($itip->getAttribute('SUMMARY'), 'PEAR_error')) { + $itip_reply->setAttribute('SUMMARY', $itip->getAttribute('SUMMARY')); + $summary = $itip->getAttribute('SUMMARY'); + } + if (!is_a($itip->getAttribute('DESCRIPTION'), 'PEAR_error')) { + $itip_reply->setAttribute('DESCRIPTION', $itip->getAttribute('DESCRIPTION')); + } + if (!is_a($itip->getAttribute('LOCATION'), 'PEAR_error')) { + $itip_reply->setAttribute('LOCATION', $itip->getAttribute('LOCATION')); + } + $itip_reply->setAttribute('DTSTART', $itip->getAttribute('DTSTART'), array_pop($itip->getAttribute('DTSTART', true))); + if (!is_a($itip->getAttribute('DTEND'), 'PEAR_error')) { + $itip_reply->setAttribute('DTEND', $itip->getAttribute('DTEND'), array_pop($itip->getAttribute('DTEND', true))); + } else { + $itip_reply->setAttribute('DURATION', $itip->getAttribute('DURATION'), array_pop($itip->getAttribute('DURATION', true))); + } + if (!is_a($itip->getAttribute('SEQUENCE'), 'PEAR_error')) { + $itip_reply->setAttribute('SEQUENCE', $itip->getAttribute('SEQUENCE')); + } else { + $itip_reply->setAttribute('SEQUENCE', 0); + } + $itip_reply->setAttribute('ORGANIZER', $itip->getAttribute('ORGANIZER'), array_pop($itip->getAttribute('ORGANIZER', true))); + + // Let's try and remove this code and just create + // the ATTENDEE stuff in the reply from scratch + // $attendees = $itip->getAttribute( 'ATTENDEE' ); + // if( !is_array( $attendees ) ) { + // $attendees = array( $attendees ); + // } + // $params = $itip->getAttribute( 'ATTENDEE', true ); + // for( $i = 0; $i < count($attendees); $i++ ) { + // $attendee = preg_replace('/^mailto:\s*/i', '', $attendees[$i]); + // if ($attendee != $resource) { + // continue; + // } + // $params = $params[$i]; + // break; + // } + + $params = array(); + $params['CN'] = $cn; + switch ($type) { + case RM_ITIP_DECLINE: + Horde::logMessage(sprintf('Sending DECLINE iTip reply to %s', + $organiser), 'DEBUG'); + $message = $is_update + ? sprintf(_("%s has declined the update to the following event:"), $resource) . "\n\n" . $summary + : sprintf(_("%s has declined the invitation to the following event:"), $resource) . "\n\n" . $summary; + $subject = _("Declined: ") . $summary; + $params['PARTSTAT'] = 'DECLINED'; + break; + + case RM_ITIP_ACCEPT: + Horde::logMessage(sprintf('Sending ACCEPT iTip reply to %s', $organiser), 'DEBUG'); + $message = $is_update + ? sprintf(_("%s has accepted the update to the following event:"), $resource) . "\n\n" . $summary + : sprintf(_("%s has accepted the invitation to the following event:"), $resource) . "\n\n" . $summary; + $subject = _("Accepted: ") . $summary; + $params['PARTSTAT'] = 'ACCEPTED'; + break; + + case RM_ITIP_TENTATIVE: + Horde::logMessage(sprintf('Sending TENTATIVE iTip reply to %s', $organiser), 'DEBUG'); + $message = $is_update + ? sprintf(_("%s has tentatively accepted the update to the following event:"), $resource) . "\n\n" . $summary + : sprintf(_("%s has tentatively accepted the invitation to the following event:"), $resource) . "\n\n" . $summary; + $subject = _("Tentative: ") . $summary; + $params['PARTSTAT'] = 'TENTATIVE'; + break; + + default: + Horde::logMessage(sprintf('Unknown iTip method (%s passed to sendITipReply())', $type), 'ERR'); + } + + $itip_reply->setAttribute('ATTENDEE', 'MAILTO:' . $resource, $params); + $vCal->addComponent($itip_reply); + + $ics = new MIME_Part('text/calendar', $vCal->exportvCalendar(), 'UTF-8' ); + //$ics->setName('event-reply.ics'); + $ics->setContentTypeParameter('method', 'REPLY'); + + //$mime->addPart($body); + //$mime->addPart($ics); + // The following was ::convertMimePart($mime). This was removed so that we + // send out single-part MIME replies that have the iTip file as the body, + // with the correct mime-type header set, etc. The reason we want to do this + // is so that Outlook interprets the messages as it does Outlook-generated + // responses, i.e. double-clicking a reply will automatically update your + // meetings, showing different status icons in the UI, etc. + $mime = &MIME_Message::convertMimePart($ics); + $mime->setCharset('UTF-8'); + $mime->setTransferEncoding('quoted-printable'); + $mime->transferEncodeContents(); + + // Build the reply headers. + $msg_headers = new MIME_Headers(); + $msg_headers->addHeader('Date', date('r')); + $msg_headers->addHeader('From', "$cn <$resource>"); + $msg_headers->addHeader('To', $organiser); + $msg_headers->addHeader('Subject', $subject); + $msg_headers->addMIMEHeaders($mime); + + $reply = new Horde_Kolab_Resource_Reply( + $resource, $organiser, $msg_headers, $mime + ); + Horde::logMessage('Successfully prepared iTip reply', 'DEBUG'); + return $reply; + } + + /** + * 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; + } + + /** + * Conveert iCal dates to Kolab format. + * + * An all day event must have a dd--mm-yyyy notation and not a + * yyyy-dd-mmT00:00:00z notation Otherwise the event is shown as a + * 2-day event --> do not try to convert everything to epoch first + * + * @param array $ical_date The array to convert. + * @param string $type The type of the date to convert. + * + * @return string The converted date. + */ + function iCalDate2Kolab($ical_date, $type= ' ') + { + Horde::logMessage(sprintf('Converting to kolab format %s', + print_r($ical_date, true)), 'DEBUG'); + + // $ical_date should be a timestamp + if (is_array($ical_date)) { + // going to create date again + $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= $etemp->getEpoch() - 86400; + $date = gmstrftime('%Y-%m-%d', $epoch); + } else { + $date= sprintf('%04d-%02d-%02d', $temp['year'], $temp['month'], $temp['mday']); + } + } else { + $time = sprintf('%02d:%02d:%02d', $temp['hour'], $temp['minute'], $temp['second']); + if ($temp['zone'] == 'UTC') { + $time .= 'Z'; + } + $date = sprintf('%04d-%02d-%02d', $temp['year'], $temp['month'], $temp['mday']) . 'T' . $time; + } + } else { + $date = gmstrftime('%Y-%m-%dT%H:%M:%SZ', $ical_date); + } + Horde::logMessage(sprintf('To <%s>', $date), 'DEBUG'); + return $date; + } +} diff --git a/framework/Kolab_Resource/lib/Horde/Kolab/Resource/Epoch.php b/framework/Kolab_Resource/lib/Horde/Kolab/Resource/Epoch.php new file mode 100644 index 000000000..a9c2c78ea --- /dev/null +++ b/framework/Kolab_Resource/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. + */ + private 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_Resource/lib/Horde/Kolab/Resource/Exception.php b/framework/Kolab_Resource/lib/Horde/Kolab/Resource/Exception.php new file mode 100644 index 000000000..02c6ee957 --- /dev/null +++ b/framework/Kolab_Resource/lib/Horde/Kolab/Resource/Exception.php @@ -0,0 +1,72 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Resource + */ + +/** + * This class provides the standard error class for Kolab_Resource exceptions. + * + * Copyright 2009-2010 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Kolab + * @package Kolab_Resource + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Resource + */ +class Horde_Kolab_Resource_Exception extends Horde_Exception +{ + /** + * Constants to define the error type. + */ + const SYSTEM = 1; + const NO_FREEBUSY = 2; + + /** + * The array of available error messages. These are connected to the error + * codes used above and might be used to differentiate between what we show + * the user in the frontend and what we actually log in the backend. + * + * @var array + */ + protected $messages; + + /** + * Exception constructor + * + * @param mixed $message The exception message, a PEAR_Error object, or an + * Exception object. + * @param mixed $code A numeric error code, or + * an array from error_get_last(). + */ + public function __construct($message = null, $code = null) + { + $this->setMessages(); + + parent::__construct($message, $code); + } + + /** + * Initialize the messages handled by this exception. + * + * @return NULL + */ + protected function setMessages() + { + $this->messages = array( + self::SYSTEM => _("An internal error occured."), + self::NO_FREEBUSY => _("There is no free/busy data available."), + ); + } +} diff --git a/framework/Kolab_Resource/lib/Horde/Kolab/Resource/Freebusy.php b/framework/Kolab_Resource/lib/Horde/Kolab/Resource/Freebusy.php new file mode 100644 index 000000000..031e8badd --- /dev/null +++ b/framework/Kolab_Resource/lib/Horde/Kolab/Resource/Freebusy.php @@ -0,0 +1,162 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ + +/** + * Retrieves free/busy data for an email address. + * + * Copyright 2004-2010 Klarälvdalens Datakonsult AB + * + * See the enclosed file COPYING for license information (LGPL>=2.1). 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 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_Freebusy +{ + /** + * Singleton instances. + * + * @var array + */ + static protected $_instances = array(); + + /** + * Class parameters. + * + * @var array + */ + protected $_params; + + /** + * Constructor. + * + * @param array $params A hash containing any additional configuration or + * connection parameters a subclass might need. + */ + protected function __construct($params) + { + $this->_params = $params; + } + + /** + * Attempts to return a concrete Horde_Kolab_Resource_Getfreebusy instance + * based on $driver. + * + * @param mixed $driver The type of concrete + * Horde_Kolab_Resource_Getfreebusy subclass to + * return. + * @param array $params A hash containing any additional configuration or + * connection parameters a subclass might need. + * + * @return Horde_Kolab_Resource_Getfreebusy The newly created concrete + * Horde_Kolab_Resource_Getfreebusy + * instance, or false an error. + */ + static public function factory($driver, $params = array()) + { + $driver = ucfirst(basename($driver)); + $class = ($driver == 'None') + ? 'Horde_Kolab_Resource_Freebusy' + : 'Horde_Kolab_Resource_Freebusy_' . $driver; + + require_once dirname(__FILE__) . '/Freebusy/' . $driver . '.php'; + + if (!class_exists($class)) { + $class = 'Horde_Kolab_Resource_Freebusy'; + } + + return new $class($params); + } + + /** + * Attempts to return a reference to a concrete + * Horde_Kolab_Resource_Getfreebusy instance based on $driver. + * + * It will only create a new instance if no Horde_Kolab_Resource_Getfreebusy + * instance with the same parameters currently exists. + * + * This method must be invoked as: + * $var = Horde_Kolab_Resource_Getfreebusy::singleton(); + * + * @param mixed $driver The type of concrete + * Horde_Kolab_Resource_Getfreebusy subclass to + * return. + * @param array $params A hash containing any additional configuration or + * connection parameters a subclass might need. + * + * @return Horde_Token The concrete Horde_Kolab_Resource_Getfreebusy + * reference, or false on error. + */ + static public function singleton($driver = null, $params = array()) + { + global $conf; + + if (isset($GLOBALS['KOLAB_FILTER_TESTING'])) { + $driver = 'mock'; + $params['data'] = $GLOBALS['KOLAB_FILTER_TESTING']; + } + + if (empty($driver)) { + $driver = $conf['freebusy']['driver']; + } + + ksort($params); + $sig = hash('md5', serialize(array($driver, $params))); + + if (!isset(self::$_instances[$sig])) { + self::$_instances[$sig] = Horde_Kolab_Resource_Freebusy::factory($driver, + $params); + } + + return self::$_instances[$sig]; + } + + /** + * Retrieve Free/Busy URL for the specified resource id. + * + * @param string $resource The id of the resource (usually a mail address). + * + * @return string The Free/Busy URL for that resource. + */ + protected function getUrl($resource) + { + return ''; + } + + /** + * Retrieve Free/Busy data for the specified resource. + * + * @param string $resource Fetch the Free/Busy data for this resource + * (usually a mail address). + * + * @return Horde_iCalendar_vfreebusy The Free/Busy data. + */ + public function get($resource) + { + /* Return an empty VFB object. */ + $vCal = new Horde_iCalendar(); + $vFb = Horde_iCalendar::newComponent('vfreebusy', $vCal); + $vFb->setAttribute('ORGANIZER', $resource); + + return $vFb; + + } +} \ No newline at end of file diff --git a/framework/Kolab_Resource/lib/Horde/Kolab/Resource/Freebusy/Kolab.php b/framework/Kolab_Resource/lib/Horde/Kolab/Resource/Freebusy/Kolab.php new file mode 100644 index 000000000..bc45cc69f --- /dev/null +++ b/framework/Kolab_Resource/lib/Horde/Kolab/Resource/Freebusy/Kolab.php @@ -0,0 +1,117 @@ + + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ + +/** + * Retrieves free/busy data for an email address on a Kolab server. + * + * Copyright 2004-2009 Klarälvdalens Datakonsult AB + * + * See the enclosed file COPYING for license information (LGPL>=2.1). 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_Freebusy_Kolab extends Horde_Kolab_Resource_Freebusy +{ + /** + * Retrieve Free/Busy URL for the specified resource id. + * + * @param string $resource The id of the resource (usually a mail address). + * + * @return string The Free/Busy URL for that resource. + */ + protected function getUrl($resource) + { + $server = Horde_Kolab_Server::singleton(); + $uid = $server->uidForIdOrMailOrAlias($resource); + $result = $server->fetch($uid)->getServer('freebusy'); + return sprintf('%s/%s.xfb', $result, $resource); + } + + /** + * Retrieve Free/Busy data for the specified resource. + * + * @param string $resource Fetch the Free/Busy data for this resource. + * + * @return Horde_iCalendar_vfreebusy The Free/Busy data. + */ + public function get($resource) + { + global $conf; + + $url = self::getUrl($resource); + + Horde::logMessage(sprintf('Freebusy URL for resource %s is %s', + $resource, $url), 'DEBUG'); + + list($user, $domain) = explode('@', $resource); + if (empty($domain)) { + $domain = $conf['kolab']['filter']['email_domain']; + } + + /** + * This section matches Kronolith_Freebusy and should be merged with it + * again in a single Horde_Freebusy module. + */ + $options = array( + 'method' => 'GET', + 'timeout' => 5, + 'allowRedirects' => true + ); + + if (!empty($conf['http']['proxy']['proxy_host'])) { + $options = array_merge($options, $conf['http']['proxy']); + } + + $http = new HTTP_Request($url, $options); + $http->setBasicAuth($conf['kolab']['filter']['calendar_id'] . '@' . $domain, + $conf['kolab']['filter']['calendar_pass']); + @$http->sendRequest(); + if ($http->getResponseCode() != 200) { + throw new Horde_Kolab_Resource_Exception(sprintf('Unable to retrieve free/busy information for %s', + $resource), + Horde_Kolab_Resource_Exception::NO_FREEBUSY); + } + $vfb_text = $http->getResponseBody(); + + // Detect the charset of the iCalendar data. + $contentType = $http->getResponseHeader('Content-Type'); + if ($contentType && strpos($contentType, ';') !== false) { + list(,$charset,) = explode(';', $contentType); + $charset = trim(str_replace('charset=', '', $charset)); + } else { + $charset = 'UTF-8'; + } + + $iCal = new Horde_iCalendar; + $iCal->parsevCalendar($vfb_text, 'VCALENDAR', $charset); + + $vfb = &$iCal->findComponent('VFREEBUSY'); + + if ($vfb === false) { + throw new Horde_Kolab_Resource_Exception(sprintf('Invalid or no free/busy information available for %s', + $resource), + Horde_Kolab_Resource_Exception::NO_FREEBUSY); + } + $vfb->simplify(); + + return $vfb; + } +} diff --git a/framework/Kolab_Resource/lib/Horde/Kolab/Resource/Freebusy/Mock.php b/framework/Kolab_Resource/lib/Horde/Kolab/Resource/Freebusy/Mock.php new file mode 100644 index 000000000..9169b051b --- /dev/null +++ b/framework/Kolab_Resource/lib/Horde/Kolab/Resource/Freebusy/Mock.php @@ -0,0 +1,55 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ + +/** + * Retrieves free/busy mockup data. + * + * Copyright 2004-2009 Klarälvdalens Datakonsult AB + * + * See the enclosed file COPYING for license information (LGPL>=2.1). 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 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_Freebusy_Mock extends Horde_Kolab_Resource_Freebusy +{ + /** + * Retrieve Free/Busy URL for the specified resource id. + * + * @param string $resource The id of the resource (usually a mail address). + * + * @return string The Free/Busy URL for that resource. + */ + protected function getUrl($resource) + { + return ''; + } + + /** + * Retrieve Free/Busy data for the specified resource. + * + * @param string $resource Fetch the Free/Busy data for this resource + * (usually a mail address). + * + * @return Horde_iCalendar_vfreebusy The Free/Busy data. + */ + public function get($resource) + { + return $this->_params['data']; + } +} \ No newline at end of file diff --git a/framework/Kolab_Resource/lib/Horde/Kolab/Resource/Itip.php b/framework/Kolab_Resource/lib/Horde/Kolab/Resource/Itip.php new file mode 100644 index 000000000..fea369788 --- /dev/null +++ b/framework/Kolab_Resource/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_Resource/lib/Horde/Kolab/Resource/Reply.php b/framework/Kolab_Resource/lib/Horde/Kolab/Resource/Reply.php new file mode 100644 index 000000000..bebeffb25 --- /dev/null +++ b/framework/Kolab_Resource/lib/Horde/Kolab/Resource/Reply.php @@ -0,0 +1,90 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Server + */ + +/** + * Represents a reply for an iTip inviation. + * + * Copyright 2004-2010 Klarälvdalens Datakonsult AB + * + * See the enclosed file COPYING for license information (LGPL>=2.1). 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 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_Reply +{ + /** + * Sender of the iTip reply. + * + * @var string + */ + protected $_sender; + + /** + * Recipient of the iTip reply. + * + * @var string + */ + protected $_recipient; + + /** + * Reply headers. + * + * @var MIME_Headers + */ + protected $_headers; + + /** + * Reply body. + * + * @var MIME_Message + */ + protected $_body; + + /** + * Constructor. + * + * @param string $sender Sender of the iTip reply. + * @param string $recipient Recipient of the iTip reply. + * @param MIME_Headers $headers Reply headers. + * @param MIME_Message $body Reply body. + */ + public function __construct( + $sender, $recipient, MIME_Headers $headers, MIME_Message $body + ) { + $this->_sender = $sender; + $this->_recipient = MIME::encodeAddress($recipient); + $this->_headers = $headers; + $this->_body = $body; + } + + public function getSender() + { + return $this->_sender; + } + + public function getRecipient() + { + return $this->_recipient; + } + + public function getData() + { + return $this->_headers->toString() . '\r\n\r\n' . $this->_body->toString(); + } +} \ No newline at end of file diff --git a/framework/Kolab_Resource/package.xml b/framework/Kolab_Resource/package.xml new file mode 100644 index 000000000..d6c58266c --- /dev/null +++ b/framework/Kolab_Resource/package.xml @@ -0,0 +1,154 @@ + + + Kolab_Resource + pear.horde.org + Resource management for the Kolab server + This package allows booking of Kolab server resources. + + Gunnar Wrobel + wrobel + p@rdus.de + yes + + + Chuck Hagenbuch + chuck + chuck@horde.org + yes + + + Jan Schneider + jan + jan@horde.org + yes + + 2010-07-06 + + + 0.1.0 + 0.1.0 + + + alpha + alpha + + LGPL + +* Extracted package from Kolab_Filter. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5.0.0 + + + 1.4.0b1 + + + Horde + pear.horde.org + 0.0.2 + + + Horde_iCalendar + pear.horde.org + 0.0.3 + + + Argv + pear.horde.org + + + Horde_MIME + pear.horde.org + 0.0.2 + + + Horde_Util + pear.horde.org + 0.0.2 + + + Kolab_Server + pear.horde.org + 0.2.0 + + + + + Horde_Notification + pear.horde.org + + + Horde_Prefs + pear.horde.org + + + + + + + + + + + + + + + + + + + + + + 0.1.0 + 0.1.0 + + + alpha + alpha + + 2010-07-06 + LGPL + +* Extracted package from Kolab_Filter. + + + + diff --git a/framework/Kolab_Resource/test/Horde/Kolab/Resource/Integration/ResourceTest.php b/framework/Kolab_Resource/test/Horde/Kolab/Resource/Integration/ResourceTest.php new file mode 100644 index 000000000..18c8c94d3 --- /dev/null +++ b/framework/Kolab_Resource/test/Horde/Kolab/Resource/Integration/ResourceTest.php @@ -0,0 +1,41 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Resource + */ + +/** + * Prepare the test setup. + */ +require_once dirname(__FILE__) . '/../Autoload.php'; + +/** + * Test the resource handling. + * + * Copyright 2008-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_Resource + * @subpackage UnitTests + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Resource + */ +class Horde_Kolab_Resource_Integration_ResourceTest +extends Horde_Kolab_Resource_TestCase +{ + public function testSomething() + { + } +} diff --git a/framework/Kolab_Resource/test/Horde/Kolab/Resource/TestCase.php b/framework/Kolab_Resource/test/Horde/Kolab/Resource/TestCase.php new file mode 100644 index 000000000..55cc42633 --- /dev/null +++ b/framework/Kolab_Resource/test/Horde/Kolab/Resource/TestCase.php @@ -0,0 +1,33 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Resource + */ + +/** + * Basic test definition for the package. + * + * Copyright 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_Resource + * @subpackage UnitTests + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Kolab_Resource + */ +class Horde_Kolab_Resource_TestCase +extends PHPUnit_Framework_TestCase +{ +} \ No newline at end of file diff --git a/framework/Kolab_Resource/test/Horde/Kolab/Resource/phpunit.xml b/framework/Kolab_Resource/test/Horde/Kolab/Resource/phpunit.xml new file mode 100644 index 000000000..0148736fe --- /dev/null +++ b/framework/Kolab_Resource/test/Horde/Kolab/Resource/phpunit.xml @@ -0,0 +1,8 @@ + + + + + ../../../../lib + + +