/** * Sends a message to an email address supposed to be added to the * identity. * * A message is send to this address containing a time-sensitive link to * confirm that the address really belongs to that user. * * @param integer $id The identity's ID. * @param string $old_addr The old From: address. * * @throws Horde_Mime_Exception */ public function verifyIdentity($id, $old_addr) { global $injector, $notification, $registry; $hash = strval(new Horde_Support_Randomid()); $pref = $this->_confirmEmail(); $pref[$hash] = $this->get($id); $pref[$hash][self::EXPIRE] = time() + self::EXPIRE_SECS; $this->_confirmEmail($pref); $new_addr = $this->getValue($this->_prefnames['from_addr'], $id); $confirm = Horde::url($registry->getServiceLink('emailconfirm')->add('h', $hash)->setRaw(true), true); $message = sprintf(Horde_Core_Translation::t("You have requested to add the email address \"%s\" to the list of your personal email addresses.\n\nGo to the following link to confirm that this is really your address:\n%s\n\nIf you don't know what this message means, you can delete it."), $new_addr, $confirm); $msg_headers = new Horde_Mime_Headers(); $msg_headers->addHeaderOb(Horde_Mime_Headers_MessageId::create()); $msg_headers->addHeaderOb(Horde_Mime_Headers_UserAgent::create()); $msg_headers->addHeaderOb(Horde_Mime_Headers_Date::create()); $msg_headers->addHeader('To', $new_addr); $msg_headers->addHeader('From', $old_addr); $msg_headers->addHeader('Subject', Horde_Core_Translation::t("Confirm new email address")); $body = new Horde_Mime_Part(); $body->setType('text/plain'); $body->setContents(Horde_String::wrap($message, 76)); $body->setCharset('UTF-8'); $body->send($new_addr, $msg_headers, $injector->getInstance('Horde_Mail')); $notification->push(sprintf(Horde_Core_Translation::t("A message has been sent to \"%s\" to verify that this is really your address. The new email address is activated as soon as you confirm this message."), $new_addr), 'horde.message'); }
/** * Variables required in form input: * - identity (TODO: ? Code uses it, but it is never set anywhere) * - imple_submit: itip_action(s) * - mime_id * - muid * * @return boolean True on success. */ protected function _handle(Horde_Variables $vars) { global $injector, $notification, $registry; $actions = (array) $vars->imple_submit; $result = false; $vCal = new Horde_Icalendar(); /* Retrieve the calendar data from the message. */ try { $contents = $injector->getInstance('IMP_Factory_Contents')->create(new IMP_Indices_Mailbox($vars)); $mime_part = $contents->getMIMEPart($vars->mime_id); if (empty($mime_part)) { throw new IMP_Exception(_("Cannot retrieve calendar data from message.")); } elseif (!$vCal->parsevCalendar($mime_part->getContents(), 'VCALENDAR', $mime_part->getCharset())) { throw new IMP_Exception(_("The calendar data is invalid")); } $components = $vCal->getComponents(); } catch (Exception $e) { $notification->push($e, 'horde.error'); $actions = array(); } foreach ($actions as $key => $action) { $pos = strpos($key, '['); $key = substr($key, $pos + 1, strlen($key) - $pos - 2); switch ($action) { case 'delete': // vEvent cancellation. if ($registry->hasMethod('calendar/delete')) { $guid = $components[$key]->getAttribute('UID'); $recurrenceId = null; try { // This is a cancellation of a recurring event instance. $recurrenceId = $components[$key]->getAttribute('RECURRENCE-ID'); $atts = $components[$key]->getAttribute('RECURRENCE-ID', true); $range = null; foreach ($atts as $att) { if (array_key_exists('RANGE', $att)) { $range = $att['RANGE']; } } } catch (Horde_Icalendar_Exception $e) { } try { $registry->call('calendar/delete', array($guid, $recurrenceId, $range)); $notification->push(_("Event successfully deleted."), 'horde.success'); $result = true; } catch (Horde_Exception $e) { $notification->push(sprintf(_("There was an error deleting the event: %s"), $e->getMessage()), 'horde.error'); } } else { $notification->push(_("This action is not supported."), 'horde.warning'); } break; case 'update': // vEvent reply. if ($registry->hasMethod('calendar/updateAttendee')) { try { $from = $contents->getHeader()->getOb('from'); $registry->call('calendar/updateAttendee', array($components[$key], $from[0]->bare_address)); $notification->push(_("Respondent Status Updated."), 'horde.success'); $result = true; } catch (Horde_Exception $e) { $notification->push(sprintf(_("There was an error updating the event: %s"), $e->getMessage()), 'horde.error'); } } else { $notification->push(_("This action is not supported."), 'horde.warning'); } break; case 'import': case 'accept-import': // vFreebusy reply. // vFreebusy publish. // vEvent request. // vEvent publish. // vTodo publish. // vJournal publish. switch ($components[$key]->getType()) { case 'vEvent': $result = $this->_handlevEvent($key, $components, $mime_part); // Must check for exceptions. foreach ($components as $k => $component) { try { if ($component->getType() == 'vEvent' && $component->getAttribute('RECURRENCE-ID')) { $uid = $component->getAttribute('UID'); if ($uid == $components[$key]->getAttribute('UID')) { $this->_handlevEvent($k, $components, $mime_part); } } } catch (Horde_Icalendar_Exception $e) { } } break; case 'vFreebusy': // Import into Kronolith. if ($registry->hasMethod('calendar/import_vfreebusy')) { try { $registry->call('calendar/import_vfreebusy', array($components[$key])); $notification->push(_("The user's free/busy information was sucessfully stored."), 'horde.success'); $result = true; } catch (Horde_Exception $e) { $notification->push(sprintf(_("There was an error importing user's free/busy information: %s"), $e->getMessage()), 'horde.error'); } } else { $notification->push(_("This action is not supported."), 'horde.warning'); } break; case 'vTodo': // Import into Nag. if ($registry->hasMethod('tasks/import')) { try { $guid = $registry->call('tasks/import', array($components[$key], $mime_part->getType())); $url = Horde::url($registry->link('tasks/show', array('uid' => $guid))); $notification->push(_("The task has been added to your tasklist.") . ' ' . Horde::link($url, _("View task"), null, '_blank') . Horde_Themes_Image::tag('mime/icalendar.png', array('alt' => _("View task"))) . '</a>', 'horde.success', array('content.raw')); $result = true; } catch (Horde_Exception $e) { $notification->push(sprintf(_("There was an error importing the task: %s"), $e->getMessage()), 'horde.error'); } } else { $notification->push(_("This action is not supported."), 'horde.warning'); } break; case 'vJournal': default: $notification->push(_("This action is not supported."), 'horde.warning'); } if ($action == 'import') { break; } // Fall-through for 'accept-import' // Fall-through for 'accept-import' case 'accept': case 'deny': case 'tentative': // vEvent request. if (isset($components[$key]) && $components[$key]->getType() == 'vEvent') { $vEvent = $components[$key]; $resource = new Horde_Itip_Resource_Identity($injector->getInstance('IMP_Identity'), $vEvent->getAttribute('ATTENDEE'), $vars->identity); switch ($action) { case 'accept': case 'accept-import': $type = new Horde_Itip_Response_Type_Accept($resource); break; case 'deny': $type = new Horde_Itip_Response_Type_Decline($resource); break; case 'tentative': $type = new Horde_Itip_Response_Type_Tentative($resource); break; } try { // Send the reply. Horde_Itip::factory($vEvent, $resource)->sendMultiPartResponse($type, new Horde_Core_Itip_Response_Options_Horde('UTF-8', array()), $injector->getInstance('IMP_Mail')); $notification->push(_("Reply Sent."), 'horde.success'); $result = true; } catch (Horde_Itip_Exception $e) { $notification->push(sprintf(_("Error sending reply: %s."), $e->getMessage()), 'horde.error'); } } else { $notification->push(_("This action is not supported."), 'horde.warning'); } break; case 'send': case 'reply': case 'reply2m': // vfreebusy request. if (isset($components[$key]) && $components[$key]->getType() == 'vFreebusy') { $vFb = $components[$key]; // Get the organizer details. try { $organizer = parse_url($vFb->getAttribute('ORGANIZER')); } catch (Horde_Icalendar_Exception $e) { break; } $organizerEmail = $organizer['path']; $organizer = $vFb->getAttribute('ORGANIZER', true); $organizerFullEmail = new Horde_Mail_Rfc822_Address($organizerEmail); if (isset($organizer['cn'])) { $organizerFullEmail->personal = $organizer['cn']; } if ($action == 'reply2m') { $startStamp = time(); $endStamp = $startStamp + 60 * 24 * 3600; } else { try { $startStamp = $vFb->getAttribute('DTSTART'); } catch (Horde_Icalendar_Exception $e) { $startStamp = time(); } try { $endStamp = $vFb->getAttribute('DTEND'); } catch (Horde_Icalendar_Exception $e) { } if (!$endStamp) { try { $duration = $vFb->getAttribute('DURATION'); $endStamp = $startStamp + $duration; } catch (Horde_Icalendar_Exception $e) { $endStamp = $startStamp + 60 * 24 * 3600; } } } $vfb_reply = $registry->call('calendar/getFreeBusy', array($startStamp, $endStamp)); // Find out who we are and update status. $identity = $injector->getInstance('IMP_Identity'); $email = $identity->getFromAddress(); // Build the reply. $msg_headers = new Horde_Mime_Headers(); $vCal = new Horde_Icalendar(); $vCal->setAttribute('PRODID', '-//The Horde Project//' . $msg_headers->getUserAgent() . '//EN'); $vCal->setAttribute('METHOD', 'REPLY'); $vCal->addComponent($vfb_reply); $message = _("Attached is a reply to a calendar request you sent."); $body = new Horde_Mime_Part(); $body->setType('text/plain'); $body->setCharset('UTF-8'); $body->setContents(Horde_String::wrap($message, 76)); $ics = new Horde_Mime_Part(); $ics->setType('text/calendar'); $ics->setCharset('UTF-8'); $ics->setContents($vCal->exportvCalendar()); $ics->setName('icalendar.ics'); $ics->setContentTypeParameter('METHOD', 'REPLY'); $mime = new Horde_Mime_Part(); $mime->addPart($body); $mime->addPart($ics); // Build the reply headers. $msg_headers->addReceivedHeader(array('dns' => $injector->getInstance('Net_DNS2_Resolver'), 'server' => $conf['server']['name'])); $msg_headers->addMessageIdHeader(); $msg_headers->addHeader('Date', date('r')); $msg_headers->addHeader('From', $email); $msg_headers->addHeader('To', $organizerFullEmail); $identity->setDefault($vars->identity); $replyto = $identity->getValue('replyto_addr'); if (!empty($replyto) && !$email->match($replyto)) { $msg_headers->addHeader('Reply-To', $replyto); } $msg_headers->addHeader('Subject', _("Free/Busy Request Response")); // Send the reply. try { $mime->send($organizerEmail, $msg_headers, $injector->getInstance('IMP_Mail')); $notification->push(_("Reply Sent."), 'horde.success'); $result = true; } catch (Exception $e) { $notification->push(sprintf(_("Error sending reply: %s."), $e->getMessage()), 'horde.error'); } } else { $notification->push(_("Invalid Action selected for this component."), 'horde.warning'); } break; case 'nosup': // vFreebusy request. // vFreebusy request. default: $notification->push(_("This action is not supported."), 'horde.warning'); break; } } return $result; }
/** * Return the response as a MIME message. * * @param Horde_Itip_Response_Type $type The response type. * @param Horde_Itip_Response_Options $options The options for the response. * * @return array A list of two object: The mime headers and the mime * message. */ public function getMultiPartMessage(Horde_Itip_Response_Type $type, Horde_Itip_Response_Options $options) { $message = new Horde_Mime_Part(); $message->setType('multipart/alternative'); list($headers, $ics) = $this->getMessage($type, $options); $body = new Horde_Mime_Part(); $body->setType('text/plain'); $options->prepareMessageMimePart($body); $body->setContents(Horde_String::wrap($type->getMessage(), 76)); $message->addPart($body); $message->addPart($ics); return array($headers, $message); }
/** * @return string A tooltip for quick descriptions of this event. */ public function getTooltip() { $tooltip = $this->getTimeRange() . "\n" . sprintf(_("Owner: %s"), $this->creator == $GLOBALS['registry']->getAuth() ? _("Me") : Kronolith::getUserName($this->creator)); if (!$this->isPrivate()) { if ($this->location) { $tooltip .= "\n" . _("Location") . ': ' . $this->location; } if ($this->description) { $tooltip .= "\n\n" . Horde_String::wrap($this->description); } } return $tooltip; }
/** * Returns a hash with all information necessary to reply to a message. * * @param mixed $message The ID of the parent message to reply to, or arry of its data. * * @return array A hash with all relevant information. * @throws Horde_Exception_NotFound * @throws Agora_Exception */ public function replyMessage($message) { if (!is_array($message)) { $message = $this->getMessage($message); } /* Set up the form subject with the parent subject. */ if (Horde_String::lower(Horde_String::substr($message['message_subject'], 0, 3)) != 're:') { $message['message_subject'] = 'Re: ' . $message['message_subject']; } /* Prepare the message quite body . */ $message['body'] = sprintf(_("Posted by %s on %s"), htmlspecialchars($message['message_author']), strftime($GLOBALS['prefs']->getValue('date_format'), $message['message_timestamp'])) . "\n-------------------------------------------------------\n" . $message['body']; $message['body'] = "\n> " . Horde_String::wrap($message['body'], 60, "\n> "); return $message; }
/** * Returns the provided story as a MIME part. * * @param array $story A data array representing a story. * * @return MIME_Part The MIME message part containing the story parts. * @TODO: Refactor to use new Horde MIME library */ protected function getStoryAsMessage($story) { require_once 'Horde/MIME/Part.php'; /* Add the story to the message based on the story's body type. */ switch ($story['body_type']) { case 'richtext': /* Get a plain text version of a richtext story. */ $body_html = $story['body']; $body_text = $GLOBALS['injector']->getInstance('Horde_Core_Factory_TextFilter')->filter($body_html, 'html2text'); /* Add description. */ $body_html = '<p>' . $GLOBALS['injector']->getInstance('Horde_Core_Factory_TextFilter')->filter($story['desc'], 'text2html', array('parselevel' => Horde_Text_Filter_Text2html::MICRO, 'callback' => null)) . "</p>\n" . $body_html; $body_text = Horde_String::wrap(' ' . $story['description'], 70) . "\n\n" . $body_text; /* Add the text version of the story to the base message. */ $message_text = new MIME_Part('text/plain'); $message_text->setCharset('UTF-8'); $message_text->setContents($message_text->replaceEOL($body_text)); $message_text->setDescription(_("Plaintext Version of Story")); /* Add an HTML version of the story to the base message. */ $message_html = new MIME_Part('text/html', Horde_String::wrap($body_html), 'UTF-8', 'inline'); $message_html->setDescription(_("HTML Version of Story")); /* Add the two parts as multipart/alternative. */ $basepart = new MIME_Part('multipart/alternative'); $basepart->addPart($message_text); $basepart->addPart($message_html); return $basepart; case 'text': /* This is just a plain text story. */ $message_text = new MIME_Part('text/plain'); $message_text->setContents($message_text->replaceEOL($story['description'] . "\n\n" . $story['body'])); $message_text->setCharset('UTF-8'); return $message_text; } }
/** * Sets the message body text. * * @param string $body The message content. * @param string $charset The character set of the message. * @param boolean|integer $wrap If true, wrap the message at column 76; * If an integer wrap the message at that * column. Don't use wrapping if sending * flowed messages. */ public function setBody($body, $charset = null, $wrap = false) { if (!$charset) { $charset = $this->_charset; } $body = Horde_String::convertCharset($body, 'UTF-8', $charset); if ($wrap) { $body = Horde_String::wrap($body, $wrap === true ? 76 : $wrap); } $this->_body = new Horde_Mime_Part(); $this->_body->setType('text/plain'); $this->_body->setCharset($charset); $this->_body->setContents($body); $this->_base = null; }
/** * Add a change log entry to CHANGES * * @param string $entry Change log entry to add. * @param string $changes Path to the CHANGES file. * * @return NULL */ public function addChange($entry, $changes) { $tmp = Horde_Util::getTempFile(); $entry = Horde_String::wrap($entry, 79, "\n "); $oldfp = fopen($changes, 'r'); $newfp = fopen($tmp, 'w'); $counter = 0; while ($line = fgets($oldfp)) { if ($counter == 4) { fwrite($newfp, $entry . "\n"); } $counter++; fwrite($newfp, $line); } fclose($oldfp); fclose($newfp); system("mv -f {$tmp} {$changes}"); }
/** * Returns a plain text representation of a ticket. */ public function toString() { $fields = array('queue' => _("Queue"), 'version' => _("Version"), 'type' => _("Type"), 'state' => _("State"), 'priority' => _("Priority"), 'due' => _("Due")); /* Find longest translated field name. */ $length = 0; foreach (array_merge($fields, array(_("Summary"), _("Owners"))) as $field) { $length = max($length, Horde_String::length($field)); } $wrap_break = "\n" . str_repeat(' ', $length + 2) . '| '; $wrap_width = 73 - $length; /* Ticket properties. */ $message = ' ' . Horde_String::pad(_("Ticket"), $length) . ' | ' . $this->_id . "\n" . ' ' . Horde_String::pad(_("Summary"), $length) . ' | ' . Horde_String::wrap($this->get('summary'), $wrap_width, $wrap_break) . "\n"; foreach ($fields as $field => $label) { if ($name = $this->get($field . '_name')) { $message .= ' ' . Horde_String::pad($label, $length) . ' | ' . Horde_String::wrap($name, $wrap_width, $wrap_break) . "\n"; } } $message .= ' ' . Horde_String::pad(_("Owners"), $length) . ' | ' . Horde_String::wrap(Whups::getOwners($this->_id, false, true), $wrap_width, $wrap_break) . "\n"; return $message; }
/** * Send notification to attachment owner. */ public function sendNotification() { global $conf, $injector, $registry; if (empty($conf['compose']['link_attachments_notify'])) { return; } try { $identity = $injector->getInstance('Horde_Core_Factory_Identity')->create($this->_user); $address = $identity->getDefaultFromAddress(); /* Ignore missing addresses, which are returned as <>. */ if (strlen($address) < 3 || $this->_getDeleteToken()) { return; } $address_full = $identity->getDefaultFromAddress(true); /* Load user prefs to correctly translate gettext strings. */ if (!$registry->getAuth()) { $prefs = $injector->getInstance('Horde_Core_Factory_Prefs')->create('imp', array('user' => $this->_user)); $registry->setLanguageEnvironment($prefs->getValue('language')); } $h = new Horde_Mime_Headers(); $h->addReceivedHeader(array('dns' => $injector->getInstance('Net_DNS2_Resolver'), 'server' => $conf['server']['name'])); $h->addMessageIdHeader(); $h->addUserAgentHeader(); $h->addHeader('Date', date('r')); $h->addHeader('From', $address_full); $h->addHeader('To', $address_full); $h->addHeader('Subject', _("Notification: Linked attachment downloaded")); $h->addHeader('Auto-Submitted', 'auto-generated'); $msg = new Horde_Mime_Part(); $msg->setType('text/plain'); $msg->setCharset('UTF-8'); $md = $this->_atc->getMetadata(); $msg->setContents(Horde_String::wrap(_("Your linked attachment has been downloaded by at least one user.") . "\n\n" . sprintf(_("Name: %s"), $md->filename) . "\n" . sprintf(_("Type: %s"), $md->type) . "\n" . sprintf(_("Sent Date: %s"), date('r', $md->time)) . "\n\n" . _("Click on the following link to permanently delete the attachment:") . "\n" . strval($this->_atc->link_url->add('d', $this->_getDeleteToken(true))))); $msg->send($address, $h, $injector->getInstance('Horde_Mail')); } catch (Exception $e) { Horde::log($e, 'ERR'); } }
/** * @return string A tooltip for quick descriptions of this event. */ public function getTooltip() { return Horde_String::wrap($this->description); }
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::log(sprintf('Action for %s is %s', $sender, $action), 'DEBUG'); // Get out as early as possible if manual if ($action == RM_ACT_MANUAL) { Horde::log(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::log(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::log(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 = Horde_String::upper($iCalendar->getAttributeDefault('METHOD', $itip->getMethod())); // What resource are we managing? Horde::log(sprintf('Processing %s method for %s', $method, $id), 'DEBUG'); // This is assumed to be constant across event creation/modification/deletipn $uid = $itip->getUid(); Horde::log(sprintf('Event has UID %s', $uid), 'DEBUG'); // Who is the organiser? $organiser = $itip->getOrganizer(); Horde::log(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::log(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::log(sprintf('Rejecting %s method', $method), 'INFO'); return $this->sendITipReply($cn, $resource, $itip, RM_ITIP_DECLINE, $organiser, $uid, $is_update); } else { Horde::log(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::log(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::log(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::log(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::log(sprintf('Busyperiods: %s', print_r($busyperiods, true)), 'DEBUG'); $extraparams = $vfb->getExtraParams(); Horde::log(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::log(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::log(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::log('Request overlaps', 'DEBUG'); $conflict = true; break; } } if ($conflict) { break; } } if ($conflict) { if ($action == RM_ACT_MANUAL_IF_CONFLICTS) { //sendITipReply(RM_ITIP_TENTATIVE); Horde::log('Conflict detected; Passing mail through', 'INFO'); return true; } else { if ($action == RM_ACT_REJECT_IF_CONFLICTS) { Horde::log('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::log('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::log(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_exists($lockfile)) { sleep(1); $counter++; } if ($counter == $timeout) { Horde::log(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::log(sprintf('Failed creating lock file %s.', $lockfile), 'ERR'); } else { $this->lockfile = $lockfile; } } else { Horde::log(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::log('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::log(sprintf('Removing event %s', $uid), 'INFO'); if (is_a($imap_error, 'PEAR_Error')) { $body = sprintf(Horde_Kolab_Resource_Translation::t("Unable to access %s's calendar:"), $resource) . "\n\n" . $summary; $subject = sprintf(Horde_Kolab_Resource_Translation::t("Error processing \"%s\""), $summary); } else { if (!$data->objectUidExists($uid)) { Horde::log(sprintf('Canceled event %s is not present in %s\'s calendar', $uid, $resource), 'WARNING'); $body = sprintf(Horde_Kolab_Resource_Translation::t("The following event that was canceled is not present in %s's calendar:"), $resource) . "\n\n" . $summary; $subject = sprintf(Horde_Kolab_Resource_Translation::t("Error processing \"%s\""), $summary); } else { /** * Delete the messages from IMAP * Delete any old events that we updated */ Horde::log(sprintf('Deleting %s because of cancel', $uid), 'DEBUG'); $result = $data->delete($uid); if (is_a($result, 'PEAR_Error')) { Horde::log(sprintf('Deleting %s failed with %s', $uid, $result->getMessage()), 'DEBUG'); } $body = Horde_Kolab_Resource_Translation::t("The following event has been successfully removed:") . "\n\n" . $summary; $subject = sprintf(Horde_Kolab_Resource_Translation::t("%s has been cancelled"), $summary); } } Horde::log(sprintf('Sending confirmation of cancelation to %s', $organiser), 'WARNING'); $body = new MIME_Part('text/plain', Horde_String::wrap($body, 76)); $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::log('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::log(sprintf('Ignoring %s method and passing message through to %s', $method, $resource), 'INFO'); return true; } }