/** * Send an iTip mail message * * @param array Event object to send * @param string iTip method (REQUEST|REPLY|CANCEL) * @param array Hash array with recipient data (name, email) * @param string Mail subject * @param string Mail body text label * @param object Mail_mime object with message data * @param boolean Request RSVP * @return boolean True on success, false on failure */ public function send_itip_message($event, $method, $recipient, $subject, $bodytext, $message = null, $rsvp = true) { if (!$this->sender['name']) { $this->sender['name'] = $this->sender['email']; } if (!$message) { libcalendaring::identify_recurrence_instance($event); $message = $this->compose_itip_message($event, $method, $rsvp); } $mailto = rcube_utils::idn_to_ascii($recipient['email']); $headers = $message->headers(); $headers['To'] = format_email_recipient($mailto, $recipient['name']); $headers['Subject'] = $this->gettext(array('name' => $subject, 'vars' => array('title' => $event['title'], 'name' => $this->sender['name']))); // compose a list of all event attendees $attendees_list = array(); foreach ((array) $event['attendees'] as $attendee) { $attendees_list[] = $attendee['name'] && $attendee['email'] ? $attendee['name'] . ' <' . $attendee['email'] . '>' : ($attendee['name'] ? $attendee['name'] : $attendee['email']); } $recurrence_info = ''; if (!empty($event['recurrence_id'])) { $recurrence_info = "\n\n** " . $this->gettext($event['thisandfuture'] ? 'itipmessagefutureoccurrence' : 'itipmessagesingleoccurrence') . ' **'; } else { if (!empty($event['recurrence'])) { $recurrence_info = sprintf("\n%s: %s", $this->gettext('recurring'), $this->lib->recurrence_text($event['recurrence'])); } } $mailbody = $this->gettext(array('name' => $bodytext, 'vars' => array('title' => $event['title'], 'date' => $this->lib->event_date_text($event, true) . $recurrence_info, 'attendees' => join(",\n ", $attendees_list), 'sender' => $this->sender['name'], 'organizer' => $this->sender['name']))); // if (!empty($event['comment'])) { // $mailbody .= "\n\n" . $this->gettext('itipsendercomment') . $event['comment']; // } // append links for direct invitation replies if ($method == 'REQUEST' && $rsvp && ($token = $this->store_invitation($event, $recipient['email']))) { $mailbody .= "\n\n" . $this->gettext(array('name' => 'invitationattendlinks', 'vars' => array('url' => $this->plugin->get_url(array('action' => 'attend', 't' => $token))))); } else { if ($method == 'CANCEL' && $event['cancelled']) { $this->cancel_itip_invitation($event); } } $message->headers($headers, true); $message->setTXTBody(rcube_mime::format_flowed($mailbody, 79)); if ($this->rc->config->get('libcalendaring_itip_debug', false)) { rcmail::console('iTip ' . $method, $message->txtHeaders() . "\n\r" . $message->get()); } // finally send the message $this->itip_send = true; $sent = $this->rc->deliver_message($message, $headers['X-Sender'], $mailto, $smtp_error); $this->itip_send = false; return $sent; }
/** * Send out an invitation/notification to all event attendees */ private function notify_attendees($event, $old, $action = 'edit', $comment = null, $rsvp = null) { if ($action == 'remove' || $event['status'] == 'CANCELLED' && $old['status'] != $event['status']) { $event['cancelled'] = true; $is_cancelled = true; } if ($rsvp === null) { $rsvp = !$old || $event['sequence'] > $old['sequence']; } $itip = $this->load_itip(); $emails = $this->get_user_emails(); $itip_notify = (int) $this->rc->config->get('calendar_itip_send_option', $this->defaults['calendar_itip_send_option']); // add comment to the iTip attachment $event['comment'] = $comment; // set a valid recurrence-id if this is a recurrence instance libcalendaring::identify_recurrence_instance($event); // compose multipart message using PEAR:Mail_Mime $method = $action == 'remove' ? 'CANCEL' : 'REQUEST'; $message = $itip->compose_itip_message($event, $method, $rsvp); // list existing attendees from $old event $old_attendees = array(); foreach ((array) $old['attendees'] as $attendee) { $old_attendees[] = $attendee['email']; } // send to every attendee $sent = 0; $current = array(); foreach ((array) $event['attendees'] as $attendee) { $current[] = strtolower($attendee['email']); // skip myself for obvious reasons if (!$attendee['email'] || in_array(strtolower($attendee['email']), $emails)) { continue; } // skip if notification is disabled for this attendee if ($attendee['noreply'] && $itip_notify & 2) { continue; } // skip if this attendee has delegated and set RSVP=FALSE if ($attendee['status'] == 'DELEGATED' && $attendee['rsvp'] === false) { continue; } // which template to use for mail text $is_new = !in_array($attendee['email'], $old_attendees); $is_rsvp = $is_new || $event['sequence'] > $old['sequence']; $bodytext = $is_cancelled ? 'eventcancelmailbody' : ($is_new ? 'invitationmailbody' : 'eventupdatemailbody'); $subject = $is_cancelled ? 'eventcancelsubject' : ($is_new ? 'invitationsubject' : ($event['title'] ? 'eventupdatesubject' : 'eventupdatesubjectempty')); $event['comment'] = $comment; // finally send the message if ($itip->send_itip_message($event, $method, $attendee, $subject, $bodytext, $message, $is_rsvp)) { $sent++; } else { $sent = -100; } } // TODO: on change of a recurring (main) event, also send updates to differing attendess of recurrence exceptions // send CANCEL message to removed attendees foreach ((array) $old['attendees'] as $attendee) { if ($attendee['role'] == 'ORGANIZER' || !$attendee['email'] || in_array(strtolower($attendee['email']), $current)) { continue; } $vevent = $old; $vevent['cancelled'] = $is_cancelled; $vevent['attendees'] = array($attendee); $vevent['comment'] = $comment; if ($itip->send_itip_message($vevent, 'CANCEL', $attendee, 'eventcancelsubject', 'eventcancelmailbody')) { $sent++; } else { $sent = -100; } } return $sent; }
/** * Convert sql record into a rcube style event object */ private function _read_postprocess($event) { $free_busy_map = array_flip($this->free_busy_map); $sensitivity_map = array_flip($this->sensitivity_map); $event['id'] = $event['event_id']; $event['start'] = new DateTime($event['start']); $event['end'] = new DateTime($event['end']); $event['allday'] = intval($event['all_day']); $event['created'] = new DateTime($event['created']); $event['changed'] = new DateTime($event['changed']); $event['free_busy'] = $free_busy_map[$event['free_busy']]; $event['sensitivity'] = $sensitivity_map[$event['sensitivity']]; $event['calendar'] = $event['calendar_id']; $event['recurrence_id'] = intval($event['recurrence_id']); $event['isexception'] = intval($event['isexception']); // parse recurrence rule if ($event['recurrence'] && preg_match_all('/([A-Z]+)=([^;]+);?/', $event['recurrence'], $m, PREG_SET_ORDER)) { $event['recurrence'] = array(); foreach ($m as $rr) { if (is_numeric($rr[2])) { $rr[2] = intval($rr[2]); } else { if ($rr[1] == 'UNTIL') { $rr[2] = date_create($rr[2]); } else { if ($rr[1] == 'RDATE') { $rr[2] = array_map('date_create', explode(',', $rr[2])); } else { if ($rr[1] == 'EXDATE') { $rr[2] = array_map('date_create', explode(',', $rr[2])); } } } } $event['recurrence'][$rr[1]] = $rr[2]; } } if ($event['recurrence_id']) { libcalendaring::identify_recurrence_instance($event); } if (strlen($event['instance'])) { $event['_instance'] = $event['instance']; if (empty($event['recurrence_id'])) { $event['recurrence_date'] = rcube_utils::anytodatetime($event['_instance'], $event['start']->getTimezone()); } } if ($event['_attachments'] > 0) { $event['attachments'] = (array) $this->list_attachments($event); } // decode serialized event attendees if (strlen($event['attendees'])) { $event['attendees'] = $this->unserialize_attendees($event['attendees']); } else { $event['attendees'] = array(); } // decode serialized alarms if ($event['alarms']) { $event['valarms'] = $this->unserialize_alarms($event['alarms']); } unset($event['event_id'], $event['calendar_id'], $event['notifyat'], $event['all_day'], $event['instance'], $event['_attachments']); return $event; }