/** * Converts a text/calendar part into SyncMeetingRequest * This is called on received messages, it's not called for events generated from the mobile * * @access private * @param $part MIME part * @param $output SyncMail object * @param $is_sent_folder boolean */ function parse_meeting_calendar($part, &$output, $is_sent_folder) { $ical = new iCalComponent(); $ical->ParseFrom($part->body); ZLog::Write(LOGLEVEL_WBXML, sprintf("BackendIMAP->parse_meeting_calendar(): %s", $part->body)); // Get UID $uid = false; $props = $ical->GetPropertiesByPath("VEVENT/UID"); if (count($props) > 0) { $uid = $props[0]->Value(); } $method = false; $props = $ical->GetPropertiesByPath("VCALENDAR/METHOD"); if (count($props) > 0) { $method = strtolower($props[0]->Value()); ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->parse_meeting_calendar(): Using method from vcalendar object: %s", $method)); } else { if (isset($part->ctype_parameters["method"])) { $method = strtolower($part->ctype_parameters["method"]); ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->parse_meeting_calendar(): Using method from mime part object: %s", $method)); } } if ($method === false) { ZLog::Write(LOGLEVEL_WARN, sprintf("BackendIMAP->parse_meeting_calendar() - No method header, please report it to the developers")); $output->messageclass = "IPM.Appointment"; } else { switch ($method) { case "cancel": $output->messageclass = "IPM.Schedule.Meeting.Canceled"; $output->meetingrequest->disallownewtimeproposal = 1; ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->parse_meeting_calendar(): Event canceled, removing calendar object"); delete_calendar_dav($uid); break; case "counter": ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->parse_meeting_calendar(): Counter received"); $output->messageclass = "IPM.Schedule.Meeting.Resp.Tent"; $output->meetingrequest->disallownewtimeproposal = 0; break; case "reply": ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->parse_meeting_calendar(): Reply received"); $props = $ical->GetPropertiesByPath('VEVENT/ATTENDEE'); for ($i = 0; $i < count($props); $i++) { $mailto = $props[$i]->Value(); $props_params = $props[$i]->Parameters(); $status = strtolower($props_params["PARTSTAT"]); if (!$is_sent_folder) { // Only evaluate received replies, not sent $res = update_calendar_attendee($uid, $mailto, $status); } else { $res = true; } if ($res) { // Only set messageclass for replies changing my calendar object switch ($status) { case "accepted": $output->messageclass = "IPM.Schedule.Meeting.Resp.Pos"; ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->parse_meeting_calendar(): Update attendee -> accepted"); break; case "needs-action": $output->messageclass = "IPM.Schedule.Meeting.Resp.Tent"; ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->parse_meeting_calendar(): Update attendee -> needs-action"); break; case "tentative": $output->messageclass = "IPM.Schedule.Meeting.Resp.Tent"; ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->parse_meeting_calendar(): Update attendee -> tentative"); break; case "declined": $output->messageclass = "IPM.Schedule.Meeting.Resp.Neg"; ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->parse_meeting_calendar(): Update attendee -> declined"); break; default: ZLog::Write(LOGLEVEL_WARN, sprintf("BackendIMAP->parse_meeting_calendar() - Unknown reply status <%s>, please report it to the developers", $status)); $output->messageclass = "IPM.Appointment"; break; } } } $output->meetingrequest->disallownewtimeproposal = 1; break; case "request": $output->messageclass = "IPM.Schedule.Meeting.Request"; $output->meetingrequest->disallownewtimeproposal = 0; ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->parse_meeting_calendar(): New request"); // New meeting, we don't create it now, because we need to confirm it first, but if we don't create it we won't see it in the calendar break; default: ZLog::Write(LOGLEVEL_WARN, sprintf("BackendIMAP->parse_meeting_calendar() - Unknown method <%s>, please report it to the developers", strtolower($part->headers["method"]))); $output->messageclass = "IPM.Appointment"; $output->meetingrequest->disallownewtimeproposal = 0; break; } } $props = $ical->GetPropertiesByPath('VEVENT/DTSTAMP'); if (count($props) == 1) { $output->meetingrequest->dtstamp = Utils::MakeUTCDate($props[0]->Value()); } $props = $ical->GetPropertiesByPath('VEVENT/UID'); if (count($props) == 1) { $output->meetingrequest->globalobjid = $props[0]->Value(); } $props = $ical->GetPropertiesByPath('VEVENT/DTSTART'); if (count($props) == 1) { $output->meetingrequest->starttime = Utils::MakeUTCDate($props[0]->Value()); if (strlen($props[0]->Value()) == 8) { $output->meetingrequest->alldayevent = 1; } } $props = $ical->GetPropertiesByPath('VEVENT/DTEND'); if (count($props) == 1) { $output->meetingrequest->endtime = Utils::MakeUTCDate($props[0]->Value()); if (strlen($props[0]->Value()) == 8) { $output->meetingrequest->alldayevent = 1; } } $props = $ical->GetPropertiesByPath('VEVENT/ORGANIZER'); if (count($props) == 1) { $output->meetingrequest->organizer = str_ireplace("MAILTO:", "", $props[0]->Value()); } $props = $ical->GetPropertiesByPath('VEVENT/LOCATION'); if (count($props) == 1) { $output->meetingrequest->location = $props[0]->Value(); } $props = $ical->GetPropertiesByPath('VEVENT/CLASS'); if (count($props) == 1) { switch ($props[0]->Value()) { case "PUBLIC": $output->meetingrequest->sensitivity = "0"; break; case "PRIVATE": $output->meetingrequest->sensitivity = "2"; break; case "CONFIDENTIAL": $output->meetingrequest->sensitivity = "3"; break; default: ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->parse_meeting_calendar() - No sensitivity class. Using 2")); $output->meetingrequest->sensitivity = "2"; break; } } // Get $tz from first timezone $props = $ical->GetPropertiesByPath("VTIMEZONE/TZID"); if (count($props) > 0) { // TimeZones shouldn't have dots $tzname = str_replace(".", "", $props[0]->Value()); $tz = TimezoneUtil::GetFullTZFromTZName($tzname); } else { $tz = TimezoneUtil::GetFullTZ(); } $output->meetingrequest->timezone = base64_encode(TimezoneUtil::GetSyncBlobFromTZ($tz)); // Fixed values $output->meetingrequest->instancetype = 0; $output->meetingrequest->responserequested = 1; $output->meetingrequest->busystatus = 2; // TODO: reminder $output->meetingrequest->reminder = ""; }
/** * Reads an appointment object from MAPI * * @param mixed $mapimessage * @param ContentParameters $contentparameters * * @access private * @return SyncAppointment */ private function getAppointment($mapimessage, $contentparameters) { $message = new SyncAppointment(); // Standard one-to-one mappings first $this->getPropsFromMAPI($message, $mapimessage, MAPIMapping::GetAppointmentMapping()); // Appointment specific props $appointmentprops = MAPIMapping::GetAppointmentProperties(); $messageprops = $this->getProps($mapimessage, $appointmentprops); //set the body according to contentparameters and supported AS version $this->setMessageBody($mapimessage, $contentparameters, $message); // Set reminder time if reminderset is true if (isset($messageprops[$appointmentprops["reminderset"]]) && $messageprops[$appointmentprops["reminderset"]] == true) { if ($messageprops[$appointmentprops["remindertime"]] == 0x5ae980e1) { $message->reminder = 15; } else { $message->reminder = $messageprops[$appointmentprops["remindertime"]]; } } if (!isset($message->uid)) { $message->uid = bin2hex($messageprops[$appointmentprops["sourcekey"]]); } else { $message->uid = Utils::GetICalUidFromOLUid($message->uid); } // Always set organizer information because some devices do not work properly without it if (isset($messageprops[$appointmentprops["representingentryid"]]) && isset($messageprops[$appointmentprops["representingname"]])) { $message->organizeremail = w2u($this->getSMTPAddressFromEntryID($messageprops[$appointmentprops["representingentryid"]])); $message->organizername = w2u($messageprops[$appointmentprops["representingname"]]); } if (isset($messageprops[$appointmentprops["timezonetag"]])) { $tz = $this->getTZFromMAPIBlob($messageprops[$appointmentprops["timezonetag"]]); } else { // set server default timezone (correct timezone should be configured!) $tz = TimezoneUtil::GetFullTZ(); } $message->timezone = base64_encode($this->getSyncBlobFromTZ($tz)); if (isset($messageprops[$appointmentprops["isrecurring"]]) && $messageprops[$appointmentprops["isrecurring"]]) { // Process recurrence $message->recurrence = new SyncRecurrence(); $this->getRecurrence($mapimessage, $messageprops, $message, $message->recurrence, $tz); } // Do attendees $reciptable = mapi_message_getrecipienttable($mapimessage); // Only get first 256 recipients, to prevent possible load issues. $rows = mapi_table_queryrows($reciptable, array(PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_SMTP_ADDRESS, PR_ADDRTYPE, PR_RECIPIENT_TRACKSTATUS, PR_RECIPIENT_TYPE), 0, 256); // Exception: we do not synchronize appointments with more than 250 attendees if (count($rows) > 250) { $message->id = bin2hex($messageprops[$appointmentprops["sourcekey"]]); $mbe = new SyncObjectBrokenException("Appointment has too many attendees"); $mbe->SetSyncObject($message); throw $mbe; } if (count($rows) > 0) { $message->attendees = array(); } foreach ($rows as $row) { $attendee = new SyncAttendee(); $attendee->name = w2u($row[PR_DISPLAY_NAME]); //smtp address is always a proper email address if (isset($row[PR_SMTP_ADDRESS])) { $attendee->email = w2u($row[PR_SMTP_ADDRESS]); } elseif (isset($row[PR_ADDRTYPE]) && isset($row[PR_EMAIL_ADDRESS])) { //if address type is SMTP, it's also a proper email address if ($row[PR_ADDRTYPE] == "SMTP") { $attendee->email = w2u($row[PR_EMAIL_ADDRESS]); } elseif ($row[PR_ADDRTYPE] == "ZARAFA") { $userinfo = mapi_zarafa_getuser_by_name($this->store, $row[PR_EMAIL_ADDRESS]); if (is_array($userinfo) && isset($userinfo["emailaddress"])) { $attendee->email = w2u($userinfo["emailaddress"]); } } } //set attendee's status and type if they're available if (isset($row[PR_RECIPIENT_TRACKSTATUS])) { $attendee->attendeestatus = $row[PR_RECIPIENT_TRACKSTATUS]; } if (isset($row[PR_RECIPIENT_TYPE])) { $attendee->attendeetype = $row[PR_RECIPIENT_TYPE]; } // Some attendees have no email or name (eg resources), and if you // don't send one of those fields, the phone will give an error ... so // we don't send it in that case. // also ignore the "attendee" if the email is equal to the organizers' email if (isset($attendee->name) && isset($attendee->email) && $attendee->email != "" && (!isset($message->organizeremail) || isset($message->organizeremail) && $attendee->email != $message->organizeremail)) { array_push($message->attendees, $attendee); } } // Status 0 = no meeting, status 1 = organizer, status 2/3/4/5 = tentative/accepted/declined/notresponded if (isset($messageprops[$appointmentprops["meetingstatus"]]) && $messageprops[$appointmentprops["meetingstatus"]] > 1) { // Work around iOS6 cancellation issue when there are no attendees for this meeting. Just add ourselves as the sole attendee. if (count($message->attendees) == 0) { if (!isset($message->attendees) || !is_array($message->attendees)) { $message->attendees = array(); } ZLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->getAppointment: adding ourself as an attendee for iOS6 workaround")); $attendee = new SyncAttendee(); $meinfo = mapi_zarafa_getuser_by_name($this->store, Request::GetAuthUser()); if (is_array($meinfo)) { $attendee->email = w2u($meinfo["emailaddress"]); $attendee->name = w2u($meinfo["fullname"]); $attendee->attendeetype = MAPI_TO; array_push($message->attendees, $attendee); } } } if (!isset($message->nativebodytype)) { $message->nativebodytype = $this->getNativeBodyType($messageprops); } return $message; }
/** * Converts a text/calendar part into SyncMeetingRequest * * @access private * @param $part MIME part * @param $output SyncMail object */ private function parseMeetingCalendar($part, &$output) { $ical = new iCalComponent(); $ical->ParseFrom($part->body); if (isset($part->ctype_parameters["method"])) { switch (strtolower($part->ctype_parameters["method"])) { case "cancel": $output->messageclass = "IPM.Schedule.Meeting.Canceled"; break; case "counter": $output->messageclass = "IPM.Schedule.Meeting.Resp.Tent"; break; case "reply": $props = $ical->GetPropertiesByPath('!VTIMEZONE/ATTENDEE'); if (count($props) == 1) { $props_params = $props[0]->Parameters(); if (isset($props_params["PARTSTAT"])) { switch (strtolower($props_params["PARTSTAT"])) { case "accepted": $output->messageclass = "IPM.Schedule.Meeting.Resp.Pos"; break; case "needs-action": case "tentative": $output->messageclass = "IPM.Schedule.Meeting.Resp.Tent"; break; case "declined": $output->messageclass = "IPM.Schedule.Meeting.Resp.Neg"; break; default: ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->parseMeetingCalendar() - Unknown reply status %s", strtolower($props_params["PARTSTAT"]))); $output->messageclass = "IPM.Appointment"; break; } } else { ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->parseMeetingCalendar() - No reply status found")); $output->messageclass = "IPM.Appointment"; } } else { ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->parseMeetingCalendar() - There are not attendees")); $output->messageclass = "IPM.Appointment"; } break; case "request": $output->messageclass = "IPM.Schedule.Meeting.Request"; break; default: ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->parseMeetingCalendar() - Unknown method %s", strtolower($part->headers["method"]))); $output->messageclass = "IPM.Appointment"; break; } } else { ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->parseMeetingCalendar() - No method header")); $output->messageclass = "IPM.Appointment"; } $props = $ical->GetPropertiesByPath('VEVENT/DTSTAMP'); if (count($props) == 1) { $output->meetingrequest->dtstamp = Utils::MakeUTCDate($props[0]->Value()); } $props = $ical->GetPropertiesByPath('VEVENT/UID'); if (count($props) == 1) { $output->meetingrequest->globalobjid = $props[0]->Value(); } $props = $ical->GetPropertiesByPath('VEVENT/DTSTART'); if (count($props) == 1) { $output->meetingrequest->starttime = Utils::MakeUTCDate($props[0]->Value()); if (strlen($props[0]->Value()) == 8) { $output->meetingrequest->alldayevent = 1; } } $props = $ical->GetPropertiesByPath('VEVENT/DTEND'); if (count($props) == 1) { $output->meetingrequest->endtime = Utils::MakeUTCDate($props[0]->Value()); if (strlen($props[0]->Value()) == 8) { $output->meetingrequest->alldayevent = 1; } } $props = $ical->GetPropertiesByPath('VEVENT/ORGANIZER'); if (count($props) == 1) { $output->meetingrequest->organizer = str_ireplace("MAILTO:", "", $props[0]->Value()); } $props = $ical->GetPropertiesByPath('VEVENT/LOCATION'); if (count($props) == 1) { $output->meetingrequest->location = $props[0]->Value(); } $props = $ical->GetPropertiesByPath('VEVENT/CLASS'); if (count($props) == 1) { switch ($props[0]->Value()) { case "PUBLIC": $output->meetingrequest->sensitivity = "0"; break; case "PRIVATE": $output->meetingrequest->sensitivity = "2"; break; case "CONFIDENTIAL": $output->meetingrequest->sensitivity = "3"; break; default: ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->parseMeetingCalendar() - No sensitivity class. Using 2")); $output->meetingrequest->sensitivity = "2"; break; } } // Get $tz from first timezone $props = $ical->GetPropertiesByPath("VTIMEZONE/TZID"); if (count($props) > 0) { // TimeZones shouldn't have dots $tzname = str_replace(".", "", $props[0]->Value()); $tz = TimezoneUtil::GetFullTZFromTZName($tzname); } else { $tz = TimezoneUtil::GetFullTZ(); } $output->meetingrequest->timezone = base64_encode(TimezoneUtil::getSyncBlobFromTZ($tz)); // Fixed values $output->meetingrequest->instancetype = 0; $output->meetingrequest->responserequested = 1; $output->meetingrequest->busystatus = 2; // TODO: reminder $output->meetingrequest->reminder = ""; }