/** * Checks if there has been any significant changes on appointment/meeting item. * Significant changes be: * 1) startdate has been changed * 2) duedate has been changed OR * 3) recurrence pattern has been created, modified or removed * * @param Array oldProps old props before an update * @param Number basedate basedate * @param Boolean isRecurrenceChanged for change in recurrence pattern. * isRecurrenceChanged true means Recurrence pattern has been changed, so clear all attendees response */ function checkSignificantChanges($oldProps, $basedate, $isRecurrenceChanged = false) { $message = null; $attach = null; // If basedate is specified then we need to open exception message to clear recipient responses if ($basedate) { $recurrence = new Recurrence($this->store, $this->message); if ($recurrence->isException($basedate)) { $attach = $recurrence->getExceptionAttachment($basedate); if ($attach) { $message = mapi_attach_openobj($attach, MAPI_MODIFY); } } } else { // use normal message or recurring series message $message = $this->message; } if (!$message) { return; } $newProps = mapi_getprops($message, array($this->proptags['startdate'], $this->proptags['duedate'], $this->proptags['updatecounter'])); // Check whether message is updated or not. if (isset($newProps[$this->proptags['updatecounter']]) && $newProps[$this->proptags['updatecounter']] == 0) { return; } if ($newProps[$this->proptags['startdate']] != $oldProps[$this->proptags['startdate']] || $newProps[$this->proptags['duedate']] != $oldProps[$this->proptags['duedate']] || $isRecurrenceChanged) { $this->clearRecipientResponse($message); mapi_setprops($message, array($this->proptags['owner_critical_change'] => time())); mapi_savechanges($message); if ($attach) { // Also save attachment Object. mapi_savechanges($attach); } } }
/** * Reads recurrence information from MAPI * * @param mixed $mapimessage * @param array $recurprops * @param SyncObject &$syncMessage the message * @param SyncObject &$syncRecurrence the recurrence message * @param array $tz timezone information * * @access private * @return */ private function getRecurrence($mapimessage, $recurprops, &$syncMessage, &$syncRecurrence, $tz) { if ($syncRecurrence instanceof SyncTaskRecurrence) { $recurrence = new TaskRecurrence($this->store, $mapimessage); } else { $recurrence = new Recurrence($this->store, $mapimessage); } switch ($recurrence->recur["type"]) { case 10: // daily switch ($recurrence->recur["subtype"]) { default: $syncRecurrence->type = 0; break; case 1: $syncRecurrence->type = 0; $syncRecurrence->dayofweek = 62; // mon-fri $syncRecurrence->interval = 1; break; } break; case 11: // weekly $syncRecurrence->type = 1; break; case 12: // monthly switch ($recurrence->recur["subtype"]) { default: $syncRecurrence->type = 2; break; case 3: $syncRecurrence->type = 3; break; } break; case 13: // yearly switch ($recurrence->recur["subtype"]) { default: $syncRecurrence->type = 4; break; case 2: $syncRecurrence->type = 5; break; case 3: $syncRecurrence->type = 6; } } // Termination switch ($recurrence->recur["term"]) { case 0x21: $syncRecurrence->until = $recurrence->recur["end"]; // fixes Mantis #350 : recur-end does not consider timezones - use ClipEnd if available if (isset($recurprops[$recurrence->proptags["enddate_recurring"]])) { $syncRecurrence->until = $recurprops[$recurrence->proptags["enddate_recurring"]]; } // add one day (minus 1 sec) to the end time to make sure the last occurrence is covered $syncRecurrence->until += 86399; break; case 0x22: $syncRecurrence->occurrences = $recurrence->recur["numoccur"]; break; case 0x23: // never ends break; } // Correct 'alldayevent' because outlook fails to set it on recurring items of 24 hours or longer if (isset($recurrence->recur["endocc"], $recurrence->recur["startocc"]) && $recurrence->recur["endocc"] - $recurrence->recur["startocc"] >= 1440) { $syncMessage->alldayevent = true; } // Interval is different according to the type/subtype switch ($recurrence->recur["type"]) { case 10: if ($recurrence->recur["subtype"] == 0) { $syncRecurrence->interval = (int) ($recurrence->recur["everyn"] / 1440); } // minutes break; case 11: case 12: $syncRecurrence->interval = $recurrence->recur["everyn"]; break; // months / weeks // months / weeks case 13: $syncRecurrence->interval = (int) ($recurrence->recur["everyn"] / 12); break; // months } if (isset($recurrence->recur["weekdays"])) { $syncRecurrence->dayofweek = $recurrence->recur["weekdays"]; } // bitmask of days (1 == sunday, 128 == saturday if (isset($recurrence->recur["nday"])) { $syncRecurrence->weekofmonth = $recurrence->recur["nday"]; } // N'th {DAY} of {X} (0-5) if (isset($recurrence->recur["month"])) { $syncRecurrence->monthofyear = (int) ($recurrence->recur["month"] / (60 * 24 * 29)) + 1; } // works ok due to rounding. see also $monthminutes below (1-12) if (isset($recurrence->recur["monthday"])) { $syncRecurrence->dayofmonth = $recurrence->recur["monthday"]; } // day of month (1-31) // All changed exceptions are appointments within the 'exceptions' array. They contain the same items as a normal appointment foreach ($recurrence->recur["changed_occurences"] as $change) { $exception = new SyncAppointmentException(); // start, end, basedate, subject, remind_before, reminderset, location, busystatus, alldayevent, label if (isset($change["start"])) { $exception->starttime = $this->getGMTTimeByTZ($change["start"], $tz); } if (isset($change["end"])) { $exception->endtime = $this->getGMTTimeByTZ($change["end"], $tz); } if (isset($change["basedate"])) { $exception->exceptionstarttime = $this->getGMTTimeByTZ($this->getDayStartOfTimestamp($change["basedate"]) + $recurrence->recur["startocc"] * 60, $tz); //open body because getting only property might not work because of memory limit $exceptionatt = $recurrence->getExceptionAttachment($change["basedate"]); if ($exceptionatt) { $exceptionobj = mapi_attach_openobj($exceptionatt, 0); $this->setMessageBodyForType($exceptionobj, SYNC_BODYPREFERENCE_PLAIN, $exception); } } if (isset($change["subject"])) { $exception->subject = w2u($change["subject"]); } if (isset($change["reminder_before"]) && $change["reminder_before"]) { $exception->reminder = $change["remind_before"]; } if (isset($change["location"])) { $exception->location = w2u($change["location"]); } if (isset($change["busystatus"])) { $exception->busystatus = $change["busystatus"]; } if (isset($change["alldayevent"])) { $exception->alldayevent = $change["alldayevent"]; } // set some data from the original appointment if (isset($syncMessage->uid)) { $exception->uid = $syncMessage->uid; } if (isset($syncMessage->organizername)) { $exception->organizername = $syncMessage->organizername; } if (isset($syncMessage->organizeremail)) { $exception->organizeremail = $syncMessage->organizeremail; } if (!isset($syncMessage->exceptions)) { $syncMessage->exceptions = array(); } array_push($syncMessage->exceptions, $exception); } // Deleted appointments contain only the original date (basedate) and a 'deleted' tag foreach ($recurrence->recur["deleted_occurences"] as $deleted) { $exception = new SyncAppointmentException(); $exception->exceptionstarttime = $this->getGMTTimeByTZ($this->getDayStartOfTimestamp($deleted) + $recurrence->recur["startocc"] * 60, $tz); $exception->deleted = "1"; if (!isset($syncMessage->exceptions)) { $syncMessage->exceptions = array(); } array_push($syncMessage->exceptions, $exception); } if (isset($syncMessage->complete) && $syncMessage->complete) { $syncRecurrence->complete = $syncMessage->complete; } }
/** * Function returns exception item based on the basedate passed. * @param MAPIMessage $recurringMessage Resource of Recurring meeting from calendar * @param Unixtime $basedate basedate of exception that needs to be returned. * @param MAPIStore $store store that contains the recurring calendar item. * @return entryid or MAPIMessage resource of exception item. */ function getExceptionItem($recurringMessage, $basedate, $store = false) { $occurItem = false; $props = mapi_getprops($this->message, array(PR_RCVD_REPRESENTING_NAME, $this->proptags['recurring'])); // check if the passed item is recurring series if ($props[$this->proptags['recurring']] !== false) { return false; } if ($store === false) { // If Delegate is processing Meeting Request/Response for Delegator then retrieve Delegator's store and calendar. if (isset($props[PR_RCVD_REPRESENTING_NAME])) { $delegatorStore = $this->getDelegatorStore($props[PR_RCVD_REPRESENTING_NAME]); $store = $delegatorStore['store']; } else { $store = $this->store; } } $recurr = new Recurrence($store, $recurringMessage); $attach = $recurr->getExceptionAttachment($basedate); if ($attach) { $occurItem = mapi_attach_openobj($attach); } return $occurItem; }
/** * Get an exception attachment based on its basedate */ function getExceptionAttachment($base_date) { // Retrieve only embedded messages $attach_res = array(RES_AND, array(array(RES_PROPERTY, array(RELOP => RELOP_EQ, ULPROPTAG => PR_ATTACH_METHOD, VALUE => array(PR_ATTACH_METHOD => 5))))); $attachments = mapi_message_getattachmenttable($this->message); $attachRows = mapi_table_queryallrows($attachments, array(PR_ATTACH_NUM), $attach_res); if (is_array($attachRows)) { foreach ($attachRows as $attachRow) { $tempattach = mapi_message_openattach($this->message, $attachRow[PR_ATTACH_NUM]); $exception = mapi_attach_openobj($tempattach); $data = mapi_message_getprops($exception, array($this->proptags["basedate"])); if (isset($data[$this->proptags["basedate"]]) && $this->isSameDay($this->fromGMT($this->tz, $data[$this->proptags["basedate"]]), $base_date)) { return $tempattach; } } } return false; }
function buildEMLAttachment($attach) { $msgembedded = mapi_attach_openobj($attach); $msgprops = mapi_getprops($msgembedded, array(PR_MESSAGE_CLASS, PR_CLIENT_SUBMIT_TIME, PR_DISPLAY_TO, PR_SUBJECT, PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_EMAIL_ADDRESS)); $msgembeddedrcpttable = mapi_message_getrecipienttable($msgembedded); $msgto = $msgprops[PR_DISPLAY_TO]; if ($msgembeddedrcpttable) { $msgembeddedrecipients = mapi_table_queryrows($msgembeddedrcpttable, array(PR_ADDRTYPE, PR_ENTRYID, PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_SMTP_ADDRESS, PR_RECIPIENT_TYPE, PR_RECIPIENT_FLAGS, PR_PROPOSEDNEWTIME, PR_PROPOSENEWTIME_START, PR_PROPOSENEWTIME_END, PR_RECIPIENT_TRACKSTATUS), 0, 99999999); foreach ($msgembeddedrecipients as $rcpt) { if ($rcpt[PR_DISPLAY_NAME] == $msgprops[PR_DISPLAY_TO]) { $msgto = $rcpt[PR_DISPLAY_NAME]; if (isset($rcpt[PR_EMAIL_ADDRESS]) && $rcpt[PR_EMAIL_ADDRESS] != $msgprops[PR_DISPLAY_TO]) { $msgto .= " <" . $rcpt[PR_EMAIL_ADDRESS] . ">"; } break; } } } $msgsubject = $msgprops[PR_SUBJECT]; $msgfrom = $msgprops[PR_SENT_REPRESENTING_NAME]; if (isset($msgprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS]) && $msgprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] != $msgprops[PR_SENT_REPRESENTING_NAME]) { $msgfrom .= " <" . $msgprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] . ">"; } $msgtime = $msgprops[PR_CLIENT_SUBMIT_TIME]; $msgembeddedbody = eml_ReadMessage($msgembedded); $msgembeddedattachtable = mapi_message_getattachmenttable($msgembedded); $msgembeddedattachtablerows = mapi_table_queryallrows($msgembeddedattachtable, array(PR_ATTACH_NUM, PR_ATTACH_METHOD)); if ($msgembeddedattachtablerows) { $boundary = '=_zpush_static'; $headercontenttype = "multipart/mixed"; $msgembeddedbody['body'] = "Unfortunately your mobile is not able to handle MIME Messages\n" . "--" . $boundary . "\n" . "Content-Type: " . $msgembeddedbody['content'] . "; charset=utf-8\n" . "Content-Transfer-Encoding: quoted-printable\n\n" . $msgembeddedbody['body'] . "\n"; foreach ($msgembeddedattachtablerows as $msgembeddedattachtablerow) { $msgembeddedattach = mapi_message_openattach($msgembedded, $msgembeddedattachtablerow[PR_ATTACH_NUM]); if (!$msgembeddedattach) { debugLog("Unable to open attachment number {$attachnum}"); } else { $msgembeddedattachprops = mapi_getprops($msgembeddedattach, array(PR_ATTACH_MIME_TAG, PR_ATTACH_LONG_FILENAME, PR_ATTACH_FILENAME, PR_DISPLAY_NAME)); if (isset($msgembeddedattachprops[PR_ATTACH_LONG_FILENAME])) { $attachfilename = w2u($msgembeddedattachprops[PR_ATTACH_LONG_FILENAME]); } else { if (isset($msgembeddedattachprops[PR_ATTACH_FILENAME])) { $attachfilename = w2u($msgembeddedattachprops[PR_ATTACH_FILENAME]); } else { if (isset($msgembeddedattachprops[PR_DISPLAY_NAME])) { $attachfilename = w2u($msgembeddedattachprops[PR_DISPLAY_NAME]); } else { $attachfilename = w2u("untitled"); } } } if ($msgembeddedattachtablerow[PR_ATTACH_METHOD] == ATTACH_EMBEDDED_MSG) { $attachfilename .= w2u(".eml"); } $msgembeddedbody['body'] .= "--" . $boundary . "\n" . "Content-Type: " . $msgembeddedattachprops[PR_ATTACH_MIME_TAG] . ";\n" . " name=\"" . $attachfilename . "\"\n" . "Content-Transfer-Encoding: base64\n" . "Content-Disposition: attachment;\n" . " filename=\"" . $attachfilename . "\"\n\n"; $msgembeddedattachstream = mapi_openpropertytostream($msgembeddedattach, PR_ATTACH_DATA_BIN); $msgembeddedattachment = ""; while (1) { $msgembeddedattachdata = mapi_stream_read($msgembeddedattachstream, 4096); if (byte_strlen($msgembeddedattachdata) == 0) { break; } $msgembeddedattachment .= $msgembeddedattachdata; } $msgembeddedbody['body'] .= chunk_split(base64_encode($msgembeddedattachment)) . "\n"; unset($msgembeddedattachment); } } $msgembeddedbody['body'] .= "--" . $boundary . "--\n"; } else { $headercontenttype = $msgembeddedbody['content'] . "; charset=utf-8"; $boundary = ''; } $msgembeddedheader = "Subject: " . $msgsubject . "\n" . "From: " . $msgfrom . "\n" . "To: " . $msgto . "\n" . "Date: " . gmstrftime("%a, %d %b %Y %T +0000", $msgprops[PR_CLIENT_SUBMIT_TIME]) . "\n" . "MIME-Version: 1.0\n" . "Content-Type: " . $headercontenttype . ";\n" . ($boundary ? " boundary=\"" . $boundary . "\"\n" : "") . "\n"; $stream = mapi_stream_create(); mapi_stream_setsize($stream, byte_strlen($msgembeddedheader . $msgembeddedbody['body'])); mapi_stream_write($stream, $msgembeddedheader . $msgembeddedbody['body']); mapi_stream_seek($stream, 0, STREAM_SEEK_SET); return $stream; }
/** * Returns the content of the named attachment as stream * * @param string $attname * @access public * @return SyncItemOperationsAttachment * @throws StatusException */ public function GetAttachmentData($attname) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZarafaBackend->GetAttachmentData('%s')", $attname)); if (!strpos($attname, ":")) { throw new StatusException(sprintf("ZarafaBackend->GetAttachmentData('%s'): Error, attachment requested for non-existing item", $attname), SYNC_ITEMOPERATIONSSTATUS_INVALIDATT); } list($id, $attachnum) = explode(":", $attname); $entryid = hex2bin($id); $message = mapi_msgstore_openentry($this->store, $entryid); if (!$message) { throw new StatusException(sprintf("ZarafaBackend->GetAttachmentData('%s'): Error, unable to open item for attachment data for id '%s' with: 0x%X", $attname, $id, mapi_last_hresult()), SYNC_ITEMOPERATIONSSTATUS_INVALIDATT); } $attach = mapi_message_openattach($message, $attachnum); if (!$attach) { throw new StatusException(sprintf("ZarafaBackend->GetAttachmentData('%s'): Error, unable to open attachment number '%s' with: 0x%X", $attname, $attachnum, mapi_last_hresult()), SYNC_ITEMOPERATIONSSTATUS_INVALIDATT); } // get necessary attachment props $attprops = mapi_getprops($attach, array(PR_ATTACH_MIME_TAG, PR_ATTACH_MIME_TAG_W, PR_ATTACH_METHOD)); $attachment = new SyncItemOperationsAttachment(); // check if it's an embedded message and open it in such a case if (isset($attprops[PR_ATTACH_METHOD]) && $attprops[PR_ATTACH_METHOD] == ATTACH_EMBEDDED_MSG) { $embMessage = mapi_attach_openobj($attach); $addrbook = $this->getAddressbook(); $stream = mapi_inetmapi_imtoinet($this->session, $addrbook, $embMessage, array('use_tnef' => -1)); // set the default contenttype for this kind of messages $attachment->contenttype = "message/rfc822"; } else { $stream = mapi_openpropertytostream($attach, PR_ATTACH_DATA_BIN); } if (!$stream) { throw new StatusException(sprintf("ZarafaBackend->GetAttachmentData('%s'): Error, unable to open attachment data stream: 0x%X", $attname, mapi_last_hresult()), SYNC_ITEMOPERATIONSSTATUS_INVALIDATT); } // put the mapi stream into a wrapper to get a standard stream $attachment->data = MapiStreamWrapper::Open($stream); if (isset($attprops[PR_ATTACH_MIME_TAG])) { $attachment->contenttype = $attprops[PR_ATTACH_MIME_TAG]; } elseif (isset($attprops[PR_ATTACH_MIME_TAG_W])) { $attachment->contenttype = $attprops[PR_ATTACH_MIME_TAG_W]; } //TODO default contenttype return $attachment; }
/** * Get an exception attachment based on its basedate */ function getExceptionAttachment($base_date) { // Retrieve only exceptions which are stored as embedded messages $attach_res = array(RES_AND, array(array(RES_PROPERTY, array(RELOP => RELOP_EQ, ULPROPTAG => PR_ATTACH_METHOD, VALUE => array(PR_ATTACH_METHOD => ATTACH_EMBEDDED_MSG))))); $attachments = mapi_message_getattachmenttable($this->message); $attachRows = mapi_table_queryallrows($attachments, array(PR_ATTACH_NUM), $attach_res); if (is_array($attachRows)) { foreach ($attachRows as $attachRow) { $tempattach = mapi_message_openattach($this->message, $attachRow[PR_ATTACH_NUM]); $exception = mapi_attach_openobj($tempattach); $data = mapi_message_getprops($exception, array($this->proptags["basedate"])); if (!isset($data[$this->proptags["basedate"]])) { // if no basedate found then it could be embedded message so ignore it // we need proper restriction to exclude embedded messages aswell continue; } if ($this->isSameDay($this->fromGMT($this->tz, $data[$this->proptags["basedate"]]), $base_date)) { return $tempattach; } } } return false; }