function renamefolder($store, $entryid, $name) { if (!$entryid) { print "Unable to find {$name} folder\n"; return; } $folder = mapi_msgstore_openentry($store, $entryid); if (!$folder) { print "Unable to open folder " . bin2hex($entryid) . "\n"; return; } mapi_setprops($folder, array(PR_DISPLAY_NAME => $name)); if (mapi_last_hresult() != 0) { print "Unable to rename " . bin2hex($entryid) . " to '{$name}'\n"; } else { print "Renamed " . bin2hex($entryid) . " to '{$name}'\n"; } }
/** * Copies attachments from one message to another. * * @param MAPIMessage $toMessage * @param MAPIMessage $fromMessage * * @return void */ private function copyAttachments(&$toMessage, $fromMessage) { $attachtable = mapi_message_getattachmenttable($fromMessage); $rows = mapi_table_queryallrows($attachtable, array(PR_ATTACH_NUM)); foreach ($rows as $row) { if (isset($row[PR_ATTACH_NUM])) { $attach = mapi_message_openattach($fromMessage, $row[PR_ATTACH_NUM]); $newattach = mapi_message_createattach($toMessage); // Copy all attachments from old to new attachment $attachprops = mapi_getprops($attach); mapi_setprops($newattach, $attachprops); if (isset($attachprops[mapi_prop_tag(PT_ERROR, mapi_prop_id(PR_ATTACH_DATA_BIN))])) { // Data is in a stream $srcstream = mapi_openpropertytostream($attach, PR_ATTACH_DATA_BIN); $dststream = mapi_openpropertytostream($newattach, PR_ATTACH_DATA_BIN, MAPI_MODIFY | MAPI_CREATE); while (1) { $data = mapi_stream_read($srcstream, 4096); if (strlen($data) == 0) { break; } mapi_stream_write($dststream, $data); } mapi_stream_commit($dststream); } mapi_savechanges($newattach); } } }
/** * Generates and stores recurrence pattern string to recurring_pattern property. */ function saveRecurrencePattern() { // Start formatting the properties in such a way we can apply // them directly into the recurrence pattern. $type = $this->recur['type']; $everyn = $this->recur['everyn']; $start = $this->recur['start']; $end = $this->recur['end']; $term = $this->recur['term']; $numocc = isset($this->recur['numoccur']) ? $this->recur['numoccur'] : false; $startocc = $this->recur['startocc']; $endocc = $this->recur['endocc']; $pattern = ''; $occSingleDayRank = false; $occTimeRange = $startocc != 0 && $endocc != 0; switch ($type) { // Daily case 0xa: if ($everyn == 1) { $type = _('workday'); $occSingleDayRank = true; } else { if ($everyn == 24 * 60) { $type = _('day'); $occSingleDayRank = true; } else { $everyn /= 24 * 60; $type = _('days'); $occSingleDayRank = false; } } break; // Weekly // Weekly case 0xb: if ($everyn == 1) { $type = _('week'); $occSingleDayRank = true; } else { $type = _('weeks'); $occSingleDayRank = false; } break; // Monthly // Monthly case 0xc: if ($everyn == 1) { $type = _('month'); $occSingleDayRank = true; } else { $type = _('months'); $occSingleDayRank = false; } break; // Yearly // Yearly case 0xd: if ($everyn <= 12) { $everyn = 1; $type = _('year'); $occSingleDayRank = true; } else { $everyn = $everyn / 12; $type = _('years'); $occSingleDayRank = false; } break; } // get timings of the first occurence $firstoccstartdate = isset($startocc) ? $start + (int) $startocc * 60 : $start; $firstoccenddate = isset($endocc) ? $end + (int) $endocc * 60 : $end; $start = gmdate(_('d-m-Y'), $firstoccstartdate); $end = gmdate(_('d-m-Y'), $firstoccenddate); $startocc = gmdate(_('G:i'), $firstoccstartdate); $endocc = gmdate(_('G:i'), $firstoccenddate); // Based on the properties, we need to generate the recurrence pattern string. // This is obviously very easy since we can simply concatenate a bunch of strings, // however this messes up translations for languages which order their words // differently. // To improve translation quality we create a series of default strings, in which // we only have to fill in the correct variables. The base string is thus selected // based on the available properties. if ($term == 0x23) { // Never ends if ($occTimeRange) { if ($occSingleDayRank) { $pattern = sprintf(_('Occurs every %s effective %s from %s to %s.'), $type, $start, $startocc, $endocc); } else { $pattern = sprintf(_('Occurs every %s %s effective %s from %s to %s.'), $everyn, $type, $start, $startocc, $endocc); } } else { if ($occSingleDayRank) { $pattern = sprintf(_('Occurs every %s effective %s.'), $type, $start); } else { $pattern = sprintf(_('Occurs every %s %s effective %s.'), $everyn, $type, $start); } } } else { if ($term == 0x22) { // After a number of times if ($occTimeRange) { if ($occSingleDayRank) { $pattern = sprintf(ngettext('Occurs every %s effective %s for %s occurence from %s to %s.', 'Occurs every %s effective %s for %s occurences from %s to %s.', $numocc), $type, $start, $numocc, $startocc, $endocc); } else { $pattern = sprintf(ngettext('Occurs every %s %s effective %s for %s occurence from %s to %s.', 'Occurs every %s %s effective %s for %s occurences %s to %s.', $numocc), $everyn, $type, $start, $numocc, $startocc, $endocc); } } else { if ($occSingleDayRank) { $pattern = sprintf(ngettext('Occurs every %s effective %s for %s occurence.', 'Occurs every %s effective %s for %s occurences.', $numocc), $type, $start, $numocc); } else { $pattern = sprintf(ngettext('Occurs every %s %s effective %s for %s occurence.', 'Occurs every %s %s effective %s for %s occurences.', $numocc), $everyn, $type, $start, $numocc); } } } else { if ($term == 0x21) { // After the given enddate if ($occTimeRange) { if ($occSingleDayRank) { $pattern = sprintf(_('Occurs every %s effective %s until %s from %s to %s.'), $type, $start, $end, $startocc, $endocc); } else { $pattern = sprintf(_('Occurs every %s %s effective %s until %s from %s to %s.'), $everyn, $type, $start, $end, $startocc, $endocc); } } else { if ($occSingleDayRank) { $pattern = sprintf(_('Occurs every %s effective %s until %s.'), $type, $start, $end); } else { $pattern = sprintf(_('Occurs every %s %s effective %s until %s.'), $everyn, $type, $start, $end); } } } } } if (!empty($pattern)) { mapi_setprops($this->message, array($this->proptags["recurring_pattern"] => $pattern)); } }
function _storeAttachment($mapimessage, $part) { // attachment $attach = mapi_message_createattach($mapimessage); $filename = ""; // Filename is present in both Content-Type: name=.. and in Content-Disposition: filename= if (isset($part->ctype_parameters["name"])) { $filename = $part->ctype_parameters["name"]; } else { if (isset($part->d_parameters["name"])) { $filename = $part->d_parameters["filename"]; } else { if (isset($part->d_parameters["filename"])) { // sending appointment with nokia & android only filename is set $filename = $part->d_parameters["filename"]; } else { if (isset($part->d_parameters["filename*0"])) { for ($i = 0; $i < count($part->d_parameters); $i++) { if (isset($part->d_parameters["filename*" . $i])) { $filename .= $part->d_parameters["filename*" . $i]; } } } else { $filename = "untitled"; } } } } // Android just doesn't send content-type, so mimeDecode doesn't performs base64 decoding // on meeting requests text/calendar somewhere inside content-transfer-encoding if (isset($part->headers['content-transfer-encoding']) && strpos($part->headers['content-transfer-encoding'], 'base64')) { if (strpos($part->headers['content-transfer-encoding'], 'text/calendar') !== false) { $part->ctype_primary = 'text'; $part->ctype_secondary = 'calendar'; } if (!isset($part->headers['content-type'])) { $part->body = base64_decode($part->body); } } // Set filename and attachment type mapi_setprops($attach, array(PR_ATTACH_LONG_FILENAME => u2wi($filename), PR_ATTACH_METHOD => ATTACH_BY_VALUE)); // Set attachment data mapi_setprops($attach, array(PR_ATTACH_DATA_BIN => $part->body)); // Set MIME type mapi_setprops($attach, array(PR_ATTACH_MIME_TAG => $part->ctype_primary . "/" . $part->ctype_secondary)); mapi_savechanges($attach); }
/** * Imports a change on a folder * * @param object $folder SyncFolder * * @access public * @return string id of the folder * @throws StatusException */ public function ImportFolderChange($folder) { $id = isset($folder->serverid) ? $folder->serverid : false; $parent = $folder->parentid; $displayname = u2wi($folder->displayname); $type = $folder->type; if (Utils::IsSystemFolder($type)) { throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Error, system folder can not be created/modified", Utils::PrintAsString($folder->serverid), $folder->parentid, $displayname), SYNC_FSSTATUS_SYSTEMFOLDER); } // create a new folder if $id is not set if (!$id) { // the root folder is "0" - get IPM_SUBTREE if ($parent == "0") { $parentprops = mapi_getprops($this->store, array(PR_IPM_SUBTREE_ENTRYID)); if (isset($parentprops[PR_IPM_SUBTREE_ENTRYID])) { $parentfentryid = $parentprops[PR_IPM_SUBTREE_ENTRYID]; } } else { $parentfentryid = mapi_msgstore_entryidfromsourcekey($this->store, hex2bin($parent)); } if (!$parentfentryid) { throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Error, unable to open parent folder (no entry id)", Utils::PrintAsString(false), $folder->parentid, $displayname), SYNC_FSSTATUS_PARENTNOTFOUND); } $parentfolder = mapi_msgstore_openentry($this->store, $parentfentryid); if (!$parentfolder) { throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Error, unable to open parent folder (open entry)", Utils::PrintAsString(false), $folder->parentid, $displayname), SYNC_FSSTATUS_PARENTNOTFOUND); } // mapi_folder_createfolder() fails if a folder with this name already exists -> MAPI_E_COLLISION $newfolder = mapi_folder_createfolder($parentfolder, $displayname, ""); if (mapi_last_hresult()) { throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Error, mapi_folder_createfolder() failed: 0x%X", Utils::PrintAsString(false), $folder->parentid, $displayname, mapi_last_hresult()), SYNC_FSSTATUS_FOLDEREXISTS); } mapi_setprops($newfolder, array(PR_CONTAINER_CLASS => MAPIUtils::GetContainerClassFromFolderType($type))); $props = mapi_getprops($newfolder, array(PR_SOURCE_KEY)); if (isset($props[PR_SOURCE_KEY])) { $sourcekey = bin2hex($props[PR_SOURCE_KEY]); ZLog::Write(LOGLEVEL_DEBUG, sprintf("Created folder '%s' with id: '%s'", $displayname, $sourcekey)); return $sourcekey; } else { throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Error, folder created but PR_SOURCE_KEY not available: 0x%X", Utils::PrintAsString($folder->serverid), $folder->parentid, $displayname, mapi_last_hresult()), SYNC_FSSTATUS_SERVERERROR); } return false; } // open folder for update $entryid = mapi_msgstore_entryidfromsourcekey($this->store, hex2bin($id)); if (!$entryid) { throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Error, unable to open folder (no entry id): 0x%X", Utils::PrintAsString($folder->serverid), $folder->parentid, $displayname, mapi_last_hresult()), SYNC_FSSTATUS_PARENTNOTFOUND); } // check if this is a MAPI default folder if ($this->mapiprovider->IsMAPIDefaultFolder($entryid)) { throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Error, MAPI default folder can not be created/modified", Utils::PrintAsString($folder->serverid), $folder->parentid, $displayname), SYNC_FSSTATUS_SYSTEMFOLDER); } $mfolder = mapi_msgstore_openentry($this->store, $entryid); if (!$mfolder) { throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Error, unable to open folder (open entry): 0x%X", Utils::PrintAsString($folder->serverid), $folder->parentid, $displayname, mapi_last_hresult()), SYNC_FSSTATUS_PARENTNOTFOUND); } $props = mapi_getprops($mfolder, array(PR_SOURCE_KEY, PR_PARENT_SOURCE_KEY, PR_DISPLAY_NAME, PR_CONTAINER_CLASS)); if (!isset($props[PR_SOURCE_KEY]) || !isset($props[PR_PARENT_SOURCE_KEY]) || !isset($props[PR_DISPLAY_NAME]) || !isset($props[PR_CONTAINER_CLASS])) { throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Error, folder data not available: 0x%X", Utils::PrintAsString($folder->serverid), $folder->parentid, $displayname, mapi_last_hresult()), SYNC_FSSTATUS_SERVERERROR); } // get the real parent source key from mapi if ($parent == "0") { $parentprops = mapi_getprops($this->store, array(PR_IPM_SUBTREE_ENTRYID)); $parentfentryid = $parentprops[PR_IPM_SUBTREE_ENTRYID]; $mapifolder = mapi_msgstore_openentry($this->store, $parentfentryid); $rootfolderprops = mapi_getprops($mapifolder, array(PR_SOURCE_KEY)); $parent = bin2hex($rootfolderprops[PR_SOURCE_KEY]); ZLog::Write(LOGLEVEL_DEBUG, sprintf("ImportChangesICS->ImportFolderChange(): resolved AS parent '0' to sourcekey '%s'", $parent)); } // a changed parent id means that the folder should be moved if (bin2hex($props[PR_PARENT_SOURCE_KEY]) !== $parent) { $sourceparentfentryid = mapi_msgstore_entryidfromsourcekey($this->store, $props[PR_PARENT_SOURCE_KEY]); if (!$sourceparentfentryid) { throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Error, unable to open parent source folder (no entry id): 0x%X", Utils::PrintAsString($folder->serverid), $folder->parentid, $displayname, mapi_last_hresult()), SYNC_FSSTATUS_PARENTNOTFOUND); } $sourceparentfolder = mapi_msgstore_openentry($this->store, $sourceparentfentryid); if (!$sourceparentfolder) { throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Error, unable to open parent source folder (open entry): 0x%X", Utils::PrintAsString($folder->serverid), $folder->parentid, $displayname, mapi_last_hresult()), SYNC_FSSTATUS_PARENTNOTFOUND); } $destparentfentryid = mapi_msgstore_entryidfromsourcekey($this->store, hex2bin($parent)); if (!$sourceparentfentryid) { throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Error, unable to open destination folder (no entry id): 0x%X", Utils::PrintAsString($folder->serverid), $folder->parentid, $displayname, mapi_last_hresult()), SYNC_FSSTATUS_SERVERERROR); } $destfolder = mapi_msgstore_openentry($this->store, $destparentfentryid); if (!$destfolder) { throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Error, unable to open destination folder (open entry): 0x%X", Utils::PrintAsString($folder->serverid), $folder->parentid, $displayname, mapi_last_hresult()), SYNC_FSSTATUS_SERVERERROR); } // mapi_folder_copyfolder() fails if a folder with this name already exists -> MAPI_E_COLLISION if (!mapi_folder_copyfolder($sourceparentfolder, $entryid, $destfolder, $displayname, FOLDER_MOVE)) { throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Error, unable to move folder: 0x%X", Utils::PrintAsString($folder->serverid), $folder->parentid, $displayname, mapi_last_hresult()), SYNC_FSSTATUS_FOLDEREXISTS); } $folderProps = mapi_getprops($mfolder, array(PR_SOURCE_KEY)); return $folderProps[PR_SOURCE_KEY]; } // update the display name $props = array(PR_DISPLAY_NAME => $displayname); mapi_setprops($mfolder, $props); mapi_savechanges($mfolder); if (mapi_last_hresult()) { throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Error, mapi_savechanges() failed: 0x%X", Utils::PrintAsString($folder->serverid), $folder->parentid, $displayname, mapi_last_hresult()), SYNC_FSSTATUS_SERVERERROR); } ZLog::Write(LOGLEVEL_DEBUG, "Imported changes for folder: {$id}"); return $id; }
/** * Function will be used to decode smime messages and convert it to normal messages. * * @param MAPISession $session * @param MAPIStore $store * @param MAPIAdressBook $addressBook * @param MAPIMessage $message smime message * * @access public * @return void */ public static function ParseSmime($session, $store, $addressBook, &$mapimessage) { $props = mapi_getprops($mapimessage, array(PR_MESSAGE_CLASS)); if (isset($props[PR_MESSAGE_CLASS]) && stripos($props[PR_MESSAGE_CLASS], 'IPM.Note.SMIME.MultipartSigned') !== false) { // this is a signed message. decode it. $attachTable = mapi_message_getattachmenttable($mapimessage); $rows = mapi_table_queryallrows($attachTable, array(PR_ATTACH_MIME_TAG, PR_ATTACH_NUM)); $attnum = false; foreach ($rows as $row) { if (isset($row[PR_ATTACH_MIME_TAG]) && $row[PR_ATTACH_MIME_TAG] == 'multipart/signed') { $attnum = $row[PR_ATTACH_NUM]; } } if ($attnum !== false) { $att = mapi_message_openattach($mapimessage, $attnum); $data = mapi_openproperty($att, PR_ATTACH_DATA_BIN); mapi_message_deleteattach($mapimessage, $attnum); mapi_inetmapi_imtomapi($session, $store, $addressBook, $mapimessage, $data, array("parse_smime_signed" => 1)); ZLog::Write(LOGLEVEL_DEBUG, "Convert a smime signed message to a normal message."); } mapi_setprops($mapimessage, array(PR_MESSAGE_CLASS => 'IPM.Note.SMIME.MultipartSigned')); } // TODO check if we need to do this for encrypted (and signed?) message as well }
/** * Function which sets reminder on recurring task after existing occurrence has been deleted or marked complete. *@param array $nextOccurrence properties of next occurrence */ function setReminder($nextOccurrence) { $props = array(); if ($nextOccurrence) { // Check if reminder is reset. Default is 'false' $reset_reminder = isset($this->messageprops[$this->proptags['reset_reminder']]) ? $this->messageprops[$this->proptags['reset_reminder']] : false; $reminder = $this->messageprops[$this->proptags['reminder']]; // Either reminder was already set OR reminder was set but was dismissed bty user if ($reminder || $reset_reminder) { // Reminder can be set at any time either before or after the duedate, so get duration between the reminder time and duedate $reminder_time = isset($this->messageprops[$this->proptags['reminder_time']]) ? $this->messageprops[$this->proptags['reminder_time']] : 0; $reminder_difference = isset($this->messageprops[$this->proptags['duedate']]) ? $this->messageprops[$this->proptags['duedate']] : 0; $reminder_difference = $reminder_difference - $reminder_time; // Apply duration to next calculated duedate $next_reminder_time = $nextOccurrence[$this->proptags['duedate']] - $reminder_difference; $props[$this->proptags['reminder_time']] = $next_reminder_time; $props[$this->proptags['flagdueby']] = $next_reminder_time; $this->action['reminder'] = $props[$this->proptags['reminder']] = true; } } else { // Didn't get next occurrence, probably this is the last occurrence $props[$this->proptags['reminder']] = false; $props[$this->proptags['reset_reminder']] = false; } if (!empty($props)) { mapi_setprops($this->message, $props); } }
/** * Sets the out of office settings. * * @param SyncObject $oof * * @access private * @return void */ private function settingsOOFSEt(&$oof) { $oof->Status = SYNC_SETTINGSSTATUS_SUCCESS; $props = array(); if ($oof->oofstate == SYNC_SETTINGSOOF_GLOBAL || $oof->oofstate == SYNC_SETTINGSOOF_TIMEBASED) { $props[PR_EC_OUTOFOFFICE] = true; foreach ($oof->oofmessage as $oofmessage) { if (isset($oofmessage->appliesToInternal)) { $props[PR_EC_OUTOFOFFICE_MSG] = isset($oofmessage->replymessage) ? u2w($oofmessage->replymessage) : ""; $props[PR_EC_OUTOFOFFICE_SUBJECT] = "Out of office"; } } } elseif ($oof->oofstate == SYNC_SETTINGSOOF_DISABLED) { $props[PR_EC_OUTOFOFFICE] = false; } if (!empty($props)) { @mapi_setprops($this->defaultstore, $props); $result = mapi_last_hresult(); if ($result != NOERROR) { ZLog::Write(LOGLEVEL_ERROR, sprintf("Setting oof information failed (%X)", $result)); return false; } } return true; }
function SendMail($rfc822, $forward = false, $reply = false, $parent = false) { $message = Mail_mimeDecode::decode(array('decode_headers' => true, 'decode_bodies' => true, 'include_bodies' => true, 'input' => $rfc822, 'crlf' => "\r\n", 'charset' => 'utf-8')); // Open the outbox and create the message there $storeprops = mapi_getprops($this->_defaultstore, array(PR_IPM_OUTBOX_ENTRYID, PR_IPM_SENTMAIL_ENTRYID)); if (!isset($storeprops[PR_IPM_OUTBOX_ENTRYID])) { debugLog("Outbox not found to create message"); return false; } $outbox = mapi_msgstore_openentry($this->_defaultstore, $storeprops[PR_IPM_OUTBOX_ENTRYID]); if (!$outbox) { debugLog("Unable to open outbox"); return false; } $mapimessage = mapi_folder_createmessage($outbox); mapi_setprops($mapimessage, array(PR_SUBJECT => u2w($message->headers["subject"]), PR_SENTMAIL_ENTRYID => $storeprops[PR_IPM_SENTMAIL_ENTRYID], PR_MESSAGE_CLASS => "IPM.Note", PR_MESSAGE_DELIVERY_TIME => time())); if (isset($message->headers["x-priority"])) { switch ($message->headers["x-priority"]) { case 1: case 2: $priority = PRIO_URGENT; $importance = IMPORTANCE_HIGH; break; case 4: case 5: $priority = PRIO_NONURGENT; $importance = IMPORTANCE_LOW; break; case 3: default: $priority = PRIO_NORMAL; $importance = IMPORTANCE_NORMAL; break; } mapi_setprops($mapimessage, array(PR_IMPORTANCE => $importance, PR_PRIORITY => $priority)); } $addresses = array(); $toaddr = $ccaddr = $bccaddr = array(); if (isset($message->headers["to"])) { $toaddr = Mail_RFC822::parseAddressList($message->headers["to"]); } if (isset($message->headers["cc"])) { $ccaddr = Mail_RFC822::parseAddressList($message->headers["cc"]); } if (isset($message->headers["bcc"])) { $bccaddr = Mail_RFC822::parseAddressList($message->headers["bcc"]); } // Add recipients $recips = array(); if (isset($toaddr)) { foreach (array(MAPI_TO => $toaddr, MAPI_CC => $ccaddr, MAPI_BCC => $bccaddr) as $type => $addrlist) { foreach ($addrlist as $addr) { $mapirecip[PR_ADDRTYPE] = "SMTP"; $mapirecip[PR_EMAIL_ADDRESS] = $addr->mailbox . "@" . $addr->host; if (isset($addr->personal) && strlen($addr->personal) > 0) { $mapirecip[PR_DISPLAY_NAME] = u2w($addr->personal); } else { $mapirecip[PR_DISPLAY_NAME] = $mapirecip[PR_EMAIL_ADDRESS]; } $mapirecip[PR_RECIPIENT_TYPE] = $type; $mapirecip[PR_ENTRYID] = mapi_createoneoff($mapirecip[PR_DISPLAY_NAME], $mapirecip[PR_ADDRTYPE], $mapirecip[PR_EMAIL_ADDRESS]); array_push($recips, $mapirecip); } } } mapi_message_modifyrecipients($mapimessage, 0, $recips); // Loop through subparts. We currently only support single-level // multiparts. The PDA currently only does this because you are adding // an attachment and the type will be multipart/mixed. if ($message->ctype_primary == "multipart" && $message->ctype_secondary == "mixed") { foreach ($message->parts as $part) { if ($part->ctype_primary == "text") { $body = u2w($part->body); } else { // attachment $attach = mapi_message_createattach($mapimessage); // Filename is present in both Content-Type: name=.. and in Content-Disposition: filename= if (isset($part->ctype_parameters["name"])) { $filename = $part->ctype_parameters["name"]; } else { if (isset($part->d_parameters["name"])) { $filename = $part->d_parameters["filename"]; } else { $filename = "untitled"; } } // Set filename and attachment type mapi_setprops($attach, array(PR_ATTACH_LONG_FILENAME => u2w($filename), PR_ATTACH_METHOD => ATTACH_BY_VALUE)); // Set attachment data mapi_setprops($attach, array(PR_ATTACH_DATA_BIN => $part->body)); // Set MIME type mapi_setprops($attach, array(PR_ATTACH_MIME_TAG => $part->ctype_primary . "/" . $part->ctype_secondary)); mapi_savechanges($attach); } } } else { $body = u2w($message->body); } if ($forward) { $orig = $forward; } if ($reply) { $orig = $reply; } if (isset($orig) && $orig) { // Append the original text body for reply/forward $entryid = mapi_msgstore_entryidfromsourcekey($this->_defaultstore, hex2bin($parent), hex2bin($orig)); $fwmessage = mapi_msgstore_openentry($this->_defaultstore, $entryid); if ($fwmessage) { $messageprops = mapi_getprops($fwmessage, array(PR_BODY)); if (isset($messageprops[PR_BODY])) { if ($forward) { // During a forward, we have to add the forward header ourselves. This is because // normally the forwarded message is added as an attachment. However, we don't want this // because it would be rather complicated to copy over the entire original message due // to the lack of IMessage::CopyTo .. $fwmessageprops = mapi_getprops($fwmessage, array(PR_SENT_REPRESENTING_NAME, PR_DISPLAY_TO, PR_DISPLAY_CC, PR_SUBJECT, PR_CLIENT_SUBMIT_TIME)); $body .= "\r\n\r\n"; $body .= "-----Original Message-----\r\n"; if (isset($fwmessageprops[PR_SENT_REPRESENTING_NAME])) { $body .= "From: " . $fwmessageprops[PR_SENT_REPRESENTING_NAME] . "\r\n"; } if (isset($fwmessageprops[PR_DISPLAY_TO]) && strlen($fwmessageprops[PR_DISPLAY_TO]) > 0) { $body .= "To: " . $fwmessageprops[PR_DISPLAY_TO] . "\r\n"; } if (isset($fwmessageprops[PR_DISPLAY_CC]) && strlen($fwmessageprops[PR_DISPLAY_CC]) > 0) { $body .= "Cc: " . $fwmessageprops[PR_DISPLAY_CC] . "\r\n"; } if (isset($fwmessageprops[PR_CLIENT_SUBMIT_TIME])) { $body .= "Sent: " . strftime("%x %X", $fwmessageprops[PR_CLIENT_SUBMIT_TIME]) . "\r\n"; } if (isset($fwmessageprops[PR_SUBJECT])) { $body .= "Subject: " . $fwmessageprops[PR_SUBJECT] . "\r\n"; } $body .= "\r\n"; } $body .= $messageprops[PR_BODY]; } } else { debugLog("Unable to open item with id {$orig} for forward/reply"); } } if ($forward) { // Add attachments from the original message in a forward $entryid = mapi_msgstore_entryidfromsourcekey($this->_defaultstore, hex2bin($parent), hex2bin($orig)); $fwmessage = mapi_msgstore_openentry($this->_defaultstore, $entryid); $attachtable = mapi_message_getattachmenttable($fwmessage); $rows = mapi_table_queryallrows($attachtable, array(PR_ATTACH_NUM)); foreach ($rows as $row) { if (isset($row[PR_ATTACH_NUM])) { $attach = mapi_message_openattach($fwmessage, $row[PR_ATTACH_NUM]); $newattach = mapi_message_createattach($mapimessage); // Copy all attachments from old to new attachment $attachprops = mapi_getprops($attach); mapi_setprops($newattach, $attachprops); if (isset($attachprops[mapi_prop_tag(PT_ERROR, mapi_prop_id(PR_ATTACH_DATA_BIN))])) { // Data is in a stream $srcstream = mapi_openpropertytostream($attach, PR_ATTACH_DATA_BIN); $dststream = mapi_openpropertytostream($newattach, PR_ATTACH_DATA_BIN, MAPI_MODIFY | MAPI_CREATE); while (1) { $data = mapi_stream_read($srcstream, 4096); if (strlen($data) == 0) { break; } mapi_stream_write($dststream, $data); } mapi_stream_commit($dststream); } mapi_savechanges($newattach); } } } mapi_setprops($mapimessage, array(PR_BODY => $body)); mapi_savechanges($mapimessage); mapi_message_submitmessage($mapimessage); return true; }
/** * Assign a contact picture to a contact * @param entryId contact entry id * @param contactPicture must be a valid jpeg file. If contactPicture is NULL will remove contact picture from contact if exists */ public function setContactPicture(&$contact, $contactPicture) { $this->logger->trace("setContactPicture"); // Find if contact picture is already set $contactAttachment = -1; $hasattachProp = mapi_getprops($contact, array(PR_HASATTACH)); if ($hasattachProp) { $attachmentTable = mapi_message_getattachmenttable($contact); $attachments = mapi_table_queryallrows($attachmentTable, array(PR_ATTACH_NUM, PR_ATTACH_SIZE, PR_ATTACH_LONG_FILENAME, PR_ATTACH_FILENAME, PR_ATTACHMENT_HIDDEN, PR_DISPLAY_NAME, PR_ATTACH_METHOD, PR_ATTACH_CONTENT_ID, PR_ATTACH_MIME_TAG, PR_ATTACHMENT_CONTACTPHOTO, PR_EC_WA_ATTACHMENT_HIDDEN_OVERRIDE)); foreach ($attachments as $attachmentRow) { if (isset($attachmentRow[PR_ATTACHMENT_CONTACTPHOTO]) && $attachmentRow[PR_ATTACHMENT_CONTACTPHOTO]) { $contactAttachment = $attachmentRow[PR_ATTACH_NUM]; break; } } } // Remove existing attachment if necessary if ($contactAttachment != -1) { $this->logger->trace("removing existing contact picture"); $attach = mapi_message_deleteattach($contact, $contactAttachment); } if ($contactPicture !== NULL) { $this->logger->debug("Saving contact picture as attachment"); // Create attachment $attach = mapi_message_createattach($contact); // Update contact attachment properties $properties = array(PR_ATTACH_SIZE => strlen($contactPicture), PR_ATTACH_LONG_FILENAME => 'ContactPicture.jpg', PR_ATTACHMENT_HIDDEN => false, PR_DISPLAY_NAME => 'ContactPicture.jpg', PR_ATTACH_METHOD => ATTACH_BY_VALUE, PR_ATTACH_MIME_TAG => 'image/jpeg', PR_ATTACHMENT_CONTACTPHOTO => true, PR_ATTACH_DATA_BIN => $contactPicture, PR_ATTACHMENT_FLAGS => 1, PR_ATTACH_EXTENSION_A => '.jpg', PR_ATTACH_NUM => 1); mapi_setprops($attach, $properties); mapi_savechanges($attach); } // Test if (mapi_last_hresult() > 0) { $this->logger->warn("Error saving contact picture: " . get_mapi_error_name()); } else { $this->logger->trace("contact picture done"); } }
if (isset($props[$properties["business_address"]])) { $props[$properties["mailing_address"]] = 2; setMailingAdress($props[$properties["business_address_street"]], $props[$properties["business_address_postal_code"]], $props[$properties["business_address_city"]], $props[$properties["business_address_state"]], $props[$properties["business_address_country"]], $props[$properties["business_address"]], $props, $properties); } elseif (isset($props[$properties["home_address"]])) { $props[$properties["mailing_address"]] = 1; setMailingAdress($props[$properties["home_address_street"]], $props[$properties["home_address_postal_code"]], $props[$properties["home_address_city"]], $props[$properties["home_address_state"]], $props[$properties["home_address_country"]], $props[$properties["home_address"]], $props, $properties); } elseif (isset($props[$properties["other_address"]])) { $props[$properties["mailing_address"]] = 3; setMailingAdress($props[$properties["other_address_street"]], $props[$properties["other_address_postal_code"]], $props[$properties["other_address_city"]], $props[$properties["other_address_state"]], $props[$properties["other_address_country"]], $props[$properties["other_address"]], $props, $properties); } // if the display name is set, then it is a valid contact: save it to the folder if (isset($props[$properties["display_name"]])) { $props[$properties["message_class"]] = "IPM.Contact"; $props[$properties["icon_index"]] = "512"; $message = mapi_folder_createmessage($folder); mapi_setprops($message, $props); mapi_savechanges($message); printf("New contact added \"%s\".\n", $props[$properties["display_name"]]); } $i++; } // EOF function getPropIdsFromStrings($store, $mapping) { $props = array(); $ids = array("name" => array(), "id" => array(), "guid" => array(), "type" => array()); // this array stores all the information needed to retrieve a named property $num = 0; // caching $guids = array(); foreach ($mapping as $name => $val) {
function clearSuggestionList($session, $store, $userName) { // create entryid of user's store $userStoreEntryId = mapi_msgstore_createentryid($store, $userName); if (!$userStoreEntryId) { print "Error in creating entryid for user's store - " . $userName . "\n"; return false; } // open user's store $userStore = mapi_openmsgstore($session, $userStoreEntryId); if (!$userStore) { print "Error in opening user's store - " . $userName . "\n"; return false; } // we are not checking here that property exists or not because it could happen that getprops will return // MAPI_E_NOT_ENOUGH_MEMORY for very large property, if property does not exists then it will be created // remove property data, overwirte existing data with a blank string (PT_STRING8) mapi_setprops($userStore, array(PR_EC_RECIPIENT_HISTORY => "")); $result = mapi_last_hresult(); if ($result == NOERROR) { // Save changes mapi_savechanges($userStore); return mapi_last_hresult() == NOERROR ? true : false; } return false; }
function import($store, $csv_file, $delete_old_items, $categories) { $folder = getContactsFolder($store); // open the csv file and start reading $fh = fopen($csv_file, "r"); if (!$fh) { trigger_error("Can't read CSV file \"" . $csv_file . "\".", E_USER_ERROR); } // Delete all existing items if requested. if ($delete_old_items) { mapi_folder_emptyfolder($folder, DEL_ASSOCIATED); printf("Old items deleted.\n"); } $properties = getProperties(); $properties = replaceStringPropertyTags($store, $properties); //composed properties which require more work $special_properties = getSpecialPropertyNames(); $csv_mapping = getMapping(); $i = 1; while (!feof($fh)) { $line = fgetcsv($fh, CSV_MAX_LENGTH, CSV_DELIMITER, CSV_ENCLOSURE); // print_r($line); if (!$line) { continue; } if ($i == 1 && defined('FIELD_NAMES') && FIELD_NAMES) { $i++; continue; } $propValues = array(); //set "simple" properties foreach ($csv_mapping as $property => $cnt) { if (!in_array($property, $special_properties)) { setProperty($property, $line[$csv_mapping[$property]], $propValues, $properties); } } // set display name if (isset($csv_mapping["display_name"]) && isset($line[$csv_mapping["display_name"]])) { $name = to_windows1252($line[$csv_mapping["display_name"]]); $propValues[$properties["display_name"]] = $propValues[$properties["subject"]] = $propValues[$properties["fileas"]] = $name; $propValues[$properties["fileas_selection"]] = -1; } else { $propValues[$properties["display_name"]] = $propValues[$properties["subject"]] = $propValues[$properties["fileas"]] = ""; if (isset($propValues[$properties["given_name"]])) { $propValues[$properties["display_name"]] .= $propValues[$properties["given_name"]]; $propValues[$properties["subject"]] .= $propValues[$properties["given_name"]]; } if (isset($propValues[$properties["surname"]])) { if (strlen($propValues[$properties["display_name"]]) > 0) { $propValues[$properties["display_name"]] .= " " . $propValues[$properties["surname"]]; $propValues[$properties["subject"]] .= " " . $propValues[$properties["surname"]]; } else { $propValues[$properties["display_name"]] .= $propValues[$properties["surname"]]; $propValues[$properties["subject"]] .= $propValues[$properties["surname"]]; } } if (isset($propValues[$properties["surname"]])) { $propValues[$properties["fileas"]] .= $propValues[$properties["surname"]]; } if (isset($propValues[$properties["given_name"]])) { if (strlen($propValues[$properties["fileas"]]) > 0) { $propValues[$properties["fileas"]] .= ", " . $propValues[$properties["given_name"]]; } else { $propValues[$properties["fileas"]] .= $propValues[$properties["given_name"]]; } } } $nremails = array(); $abprovidertype = 0; if (isset($csv_mapping["email_address_1"]) && isset($line[$csv_mapping["email_address_1"]])) { setEmailAddress($line[$csv_mapping["email_address_1"]], $propValues[$properties["display_name"]], 1, $propValues, $properties, $nremails, $abprovidertype); } if (isset($csv_mapping["email_address_2"]) && isset($line[$csv_mapping["email_address_2"]])) { setEmailAddress($line[$csv_mapping["email_address_2"]], $propValues[$properties["display_name"]], 2, $propValues, $properties, $nremails, $abprovidertype); } if (isset($csv_mapping["email_address_3"]) && isset($line[$csv_mapping["email_address_3"]])) { setEmailAddress($line[$csv_mapping["email_address_3"]], $propValues[$properties["display_name"]], 3, $propValues, $properties, $nremails, $abprovidertype); } if (!empty($nremails)) { $propValues[$properties["address_book_mv"]] = $nremails; } $propValues[$properties["address_book_long"]] = $abprovidertype; //set addresses if (isset($csv_mapping["home_address_street2"])) { mergeStreet("home", $line[$csv_mapping["home_address_street2"]], $propValues, $properties); } if (isset($csv_mapping["home_address_street3"])) { mergeStreet("home", $line[$csv_mapping["home_address_street3"]], $propValues, $properties); } if (!isset($propValues[$properties["home_address"]]) && isset($propValues[$properties["home_address_street"]]) && isset($propValues[$properties["home_address_postal_code"]]) && isset($propValues[$properties["home_address_city"]]) && isset($propValues[$properties["home_address_state"]]) && isset($propValues[$properties["home_address_country"]])) { buildAddressString("home", $propValues[$properties["home_address_street"]], $propValues[$properties["home_address_postal_code"]], $propValues[$properties["home_address_city"]], $propValues[$properties["home_address_state"]], $propValues[$properties["home_address_country"]], $propValues, $properties); } if (isset($csv_mapping["business_address_street2"])) { mergeStreet("business", $line[$csv_mapping["business_address_street2"]], $propValues, $properties); } if (isset($csv_mapping["business_address_street3"])) { mergeStreet("business", $line[$csv_mapping["business_address_street3"]], $propValues, $properties); } if (!isset($propValues[$properties["business_address"]]) && isset($propValues[$properties["business_address_street"]]) && isset($propValues[$properties["business_address_postal_code"]]) && isset($propValues[$properties["business_address_city"]]) && isset($propValues[$properties["business_address_state"]]) && isset($propValues[$properties["business_address_country"]])) { buildAddressString("business", $propValues[$properties["business_address_street"]], $propValues[$properties["business_address_postal_code"]], $propValues[$properties["business_address_city"]], $propValues[$properties["business_address_state"]], $propValues[$properties["business_address_country"]], $propValues, $properties); } if (isset($csv_mapping["other_address_street2"])) { mergeStreet("other", $line[$csv_mapping["other_address_street2"]], $propValues, $properties); } if (isset($csv_mapping["other_address_street3"])) { mergeStreet("other", $line[$csv_mapping["other_address_street3"]], $propValues, $properties); } if (!isset($propValues[$properties["other_address"]]) && isset($propValues[$properties["other_address_street"]]) && isset($propValues[$properties["other_address_postal_code"]]) && isset($propValues[$properties["other_address_city"]]) && isset($propValues[$properties["other_address_state"]]) && isset($propValues[$properties["other_address_country"]])) { buildAddressString("other", $propValues[$properties["other_address_street"]], $propValues[$properties["other_address_postal_code"]], $propValues[$properties["other_address_city"]], $propValues[$properties["other_address_state"]], $propValues[$properties["other_address_country"]], $propValues, $properties); } if (isset($propValues[$properties["home_address"]])) { $propValues[$properties["mailing_address"]] = 1; setMailingAdress($propValues[$properties["home_address_street"]], $propValues[$properties["home_address_postal_code"]], $propValues[$properties["home_address_city"]], $propValues[$properties["home_address_state"]], $propValues[$properties["home_address_country"]], $propValues[$properties["home_address"]], $propValues, $properties); } elseif (isset($propValues[$properties["business_address"]])) { $propValues[$properties["mailing_address"]] = 2; setMailingAdress($propValues[$properties["business_address_street"]], $propValues[$properties["business_address_postal_code"]], $propValues[$properties["business_address_city"]], $propValues[$properties["business_address_state"]], $propValues[$properties["business_address_country"]], $propValues[$properties["business_address"]], $propValues, $properties); } elseif (isset($propValues[$properties["other_address"]])) { $propValues[$properties["mailing_address"]] = 3; setMailingAdress($propValues[$properties["other_address_street"]], $propValues[$properties["other_address_postal_code"]], $propValues[$properties["other_address_city"]], $propValues[$properties["other_address_state"]], $propValues[$properties["other_address_country"]], $propValues[$properties["other_address"]], $propValues, $properties); } if (isset($categories) && !empty($categories)) { setProperty("categories", $categories, $propValues, $properties); } // if the display name is set, then it is a valid contact: save it to the folder if (isset($propValues[$properties["display_name"]])) { $propValues[$properties["message_class"]] = "IPM.Contact"; $propValues[$properties["icon_index"]] = "512"; $message = mapi_folder_createmessage($folder); mapi_setprops($message, $propValues); mapi_savechanges($message); printf("New contact added: \"%s\".\n", $propValues[$properties["display_name"]]); } $i++; } }
/** * Updates a card * * @param mixed $addressBookId * @param string $cardUri * @param string $cardData * @return bool */ public function updateCard($addressBookId, $cardUri, $cardData) { $this->logger->info("updateCard - {$cardUri}"); if (READ_ONLY) { $this->logger->warn("Cannot update card: read-only"); return false; } // Update object properties $entryId = $this->getContactEntryId($addressBookId, $cardUri); if ($entryId === 0) { $this->logger->warn("Cannot find contact"); return false; } $mapiProperties = $this->bridge->vcardToMapiProperties($cardData); $contact = mapi_msgstore_openentry($this->bridge->getStore($addressBookId), $entryId); if (SAVE_RAW_VCARD) { // Save RAW vCard $this->logger->debug("Saving raw vcard"); $mapiProperties[PR_CARDDAV_RAW_DATA] = $cardData; $mapiProperties[PR_CARDDAV_RAW_DATA_GENERATION_TIME] = time(); } else { $this->logger->trace("Saving raw vcard skiped by config"); } // Handle contact picture if (array_key_exists('ContactPicture', $mapiProperties)) { $this->logger->debug("Updating contact picture"); $contactPicture = $mapiProperties['ContactPicture']; unset($mapiProperties['ContactPicture']); $this->bridge->setContactPicture($contact, $contactPicture); } // Remove NULL properties if (CLEAR_MISSING_PROPERTIES) { $this->logger->debug("Clearing missing properties"); $nullProperties = array(); foreach ($mapiProperties as $p => $v) { if ($v == NULL) { $nullProperties[] = $p; unset($mapiProperties[$p]); } } $dump = print_r($nullProperties, true); $this->logger->trace("Removing properties\n{$dump}"); mapi_deleteprops($contact, $nullProperties); } // Set properties $mapiProperties[PR_LAST_MODIFICATION_TIME] = time(); mapi_setprops($contact, $mapiProperties); // Save changes to backend mapi_savechanges($contact); return mapi_last_hresult() == 0; }
function zpa_remove_device($adminStore, $session, $user, $deviceid) { $userEntryId = @mapi_msgstore_createentryid($adminStore, $user); $userStore = @mapi_openmsgstore($session, $userEntryId); $hresult = mapi_last_hresult(); if ($hresult != NOERROR) { echo "Could not open store for {$user}. The script will exit.\n"; exit(1); } $devicesprops = mapi_getprops($userStore, array(0x6880101e, 0x6881101e, 0x6882101e, 0x6883101e, 0x68841003, 0x6885101e, 0x6886101e, 0x6887101e, 0x68881040, 0x68891040)); if (isset($devicesprops[0x6881101e]) && is_array($devicesprops[0x6881101e])) { $ak = array_search($deviceid, $devicesprops[0x6881101e]); if ($ak !== false) { if (count($devicesprops[0x6880101e]) == 1) { mapi_deleteprops($userStore, array(0x6880101e, 0x6881101e, 0x6882101e, 0x6883101e, 0x68841003, 0x6885101e, 0x6886101e, 0x6887101e, 0x68881040, 0x68891040)); } else { unset($devicesprops[0x6880101e][$ak], $devicesprops[0x6881101e][$ak], $devicesprops[0x6882101e][$ak], $devicesprops[0x6883101e][$ak], $devicesprops[0x68841003][$ak], $devicesprops[0x6885101e][$ak], $devicesprops[0x6886101e][$ak], $devicesprops[0x6887101e][$ak], $devicesprops[0x68881040][$ak], $devicesprops[0x68891040][$ak]); mapi_setprops($userStore, array(0x6880101e => isset($devicesprops[0x6880101e]) ? $devicesprops[0x6880101e] : array(), 0x6881101e => isset($devicesprops[0x6881101e]) ? $devicesprops[0x6881101e] : array(), 0x6882101e => isset($devicesprops[0x6882101e]) ? $devicesprops[0x6882101e] : array(), 0x6883101e => isset($devicesprops[0x6883101e]) ? $devicesprops[0x6883101e] : array(), 0x68841003 => isset($devicesprops[0x68841003]) ? $devicesprops[0x68841003] : array(), 0x6885101e => isset($devicesprops[0x6885101e]) ? $devicesprops[0x6885101e] : array(), 0x6886101e => isset($devicesprops[0x6886101e]) ? $devicesprops[0x6886101e] : array(), 0x6887101e => isset($devicesprops[0x6887101e]) ? $devicesprops[0x6887101e] : array(), 0x68881040 => isset($devicesprops[0x68881040]) ? $devicesprops[0x68881040] : array(), 0x68891040 => isset($devicesprops[0x68891040]) ? $devicesprops[0x68891040] : array())); } $hresult = mapi_last_hresult(); if ($hresult != NOERROR) { echo "Could not remove device from list for {$user}. Errorcode 0x" . sprintf("%x", $hresult) . ". The script will exit.\n"; exit(1); } else { echo "Removed device from list.\n"; } } else { echo "No device found with the given id.\n"; exit(1); } } else { echo "No devices found for the user {$user}.\n"; exit(1); } }
/** * Saves the recurrence data to the recurrence property * @param array $properties the recurrence data. * @return string binary string */ function saveRecurrence() { // Only save if a message was passed if (!isset($this->message)) { return; } // Abort if no recurrence was set if (!isset($this->recur["type"]) && !isset($this->recur["subtype"])) { return; } if (!isset($this->recur["start"]) && !isset($this->recur["end"])) { return; } if (!isset($this->recur["startocc"]) && !isset($this->recur["endocc"])) { return; } $rdata = pack("CCCCCCV", 0x4, 0x30, 0x4, 0x30, (int) $this->recur["type"], 0x20, (int) $this->recur["subtype"]); $weekstart = 1; //monday $forwardcount = 0; $restocc = 0; $dayofweek = (int) gmdate("w", (int) $this->recur["start"]); //0 (for Sunday) through 6 (for Saturday) $term = (int) $this->recur["type"]; switch ($term) { case 0xa: // Daily if (!isset($this->recur["everyn"])) { return; } if ($this->recur["subtype"] == 1) { // Daily every workday $rdata .= pack("VVVV", 6 * 24 * 60, 1, 0, 0x3e); } else { // Daily every N days (everyN in minutes) $everyn = (int) $this->recur["everyn"] / 1440; // Calc first occ $firstocc = $this->unixDataToRecurData($this->recur["start"]) % (int) $this->recur["everyn"]; $rdata .= pack("VVV", $firstocc, (int) $this->recur["everyn"], $this->recur["regen"] ? 1 : 0); } break; case 0xb: // Weekly if (!isset($this->recur["everyn"])) { return; } if (!$this->recur["regen"] && !isset($this->recur["weekdays"])) { return; } // No need to calculate startdate if sliding flag was set. if (!$this->recur['regen']) { // Calculate start date of recurrence // Find the first day that matches one of the weekdays selected $daycount = 0; $dayskip = -1; for ($j = 0; $j < 7; $j++) { if ((int) $this->recur["weekdays"] & 1 << ($dayofweek + $j) % 7) { if ($dayskip == -1) { $dayskip = $j; } $daycount++; } } // $dayskip is the number of days to skip from the startdate until the first occurrence // $daycount is the number of days per week that an occurrence occurs $weekskip = 0; if ($dayofweek < $weekstart && $dayskip > 0 || $dayofweek + $dayskip > 6) { $weekskip = 1; } // Check if the recurrence ends after a number of occurences, in that case we must calculate the // remaining occurences based on the start of the recurrence. if ((int) $this->recur["term"] == 0x22) { // $weekskip is the amount of weeks to skip from the startdate before the first occurence // $forwardcount is the maximum number of week occurrences we can go ahead after the first occurrence that // is still inside the recurrence. We subtract one to make sure that the last week is never forwarded over // (eg when numoccur = 2, and daycount = 1) $forwardcount = floor((int) ($this->recur["numoccur"] - 1) / $daycount); // $restocc is the number of occurrences left after $forwardcount whole weeks of occurrences, minus one // for the occurrence on the first day $restocc = (int) $this->recur["numoccur"] - $forwardcount * $daycount - 1; // $forwardcount is now the number of weeks we can go forward and still be inside the recurrence $forwardcount *= (int) $this->recur["everyn"]; } // The real start is start + dayskip + weekskip-1 (since dayskip will already bring us into the next week) $this->recur["start"] = (int) $this->recur["start"] + $dayskip * 24 * 60 * 60 + $weekskip * ((int) $this->recur["everyn"] - 1) * 7 * 24 * 60 * 60; } // Calc first occ $firstocc = $this->unixDataToRecurData($this->recur["start"]) % ((int) $this->recur["everyn"] * 7 * 24 * 60); $firstocc -= ((int) gmdate("w", (int) $this->recur["start"]) - 1) * 24 * 60; if ($this->recur["regen"]) { $rdata .= pack("VVV", $firstocc, (int) $this->recur["everyn"], 1); } else { $rdata .= pack("VVVV", $firstocc, (int) $this->recur["everyn"], 0, (int) $this->recur["weekdays"]); } break; case 0xc: // Monthly // Monthly case 0xd: // Yearly if (!isset($this->recur["everyn"])) { return; } if ($term == 0xd && !isset($this->recur["month"])) { return; } if ($term == 0xc) { $everyn = (int) $this->recur["everyn"]; } else { $everyn = $this->recur["regen"] ? (int) $this->recur["everyn"] * 12 : 12; } // Get montday/month/year of original start $curmonthday = gmdate("j", (int) $this->recur["start"]); $curyear = gmdate("Y", (int) $this->recur["start"]); $curmonth = gmdate("n", (int) $this->recur["start"]); // Check if the recurrence ends after a number of occurences, in that case we must calculate the // remaining occurences based on the start of the recurrence. if ((int) $this->recur["term"] == 0x22) { // $forwardcount is the number of occurrences we can skip and still be inside the recurrence range (minus // one to make sure there are always at least one occurrence left) $forwardcount = ((int) $this->recur["numoccur"] - 1) * $everyn; } // Get month for yearly on D'th day of month M if ($term == 0xd) { $selmonth = floor((int) $this->recur["month"] / (24 * 60 * 29)) + 1; // 1=jan, 2=feb, eg } switch ((int) $this->recur["subtype"]) { // on D day of every M month case 2: if (!isset($this->recur["monthday"])) { return; } // Recalc startdate // Set on the right begin day // Go the beginning of the month $this->recur["start"] -= ($curmonthday - 1) * 24 * 60 * 60; // Go the the correct month day $this->recur["start"] += ((int) $this->recur["monthday"] - 1) * 24 * 60 * 60; // If the previous calculation gave us a start date *before* the original start date, then we need to skip to the next occurrence if ($term == 0xc && (int) $this->recur["monthday"] < $curmonthday || $term == 0xd && ($selmonth < $curmonth || $selmonth == $curmonth && (int) $this->recur["monthday"] < $curmonthday)) { if ($term == 0xd) { $count = $everyn - ($curmonth - $selmonth); } else { $count = $everyn; } // Monthly, go to next occurrence in 'everyn' months // Forward by $count months. This is done by getting the number of days in that month and forwarding that many days for ($i = 0; $i < $count; $i++) { $this->recur["start"] += $this->getMonthInSeconds($curyear, $curmonth); if ($curmonth == 12) { $curyear++; $curmonth = 0; } $curmonth++; } } // "start" is now pointing to the first occurrence, except that it will overshoot if the // month in which it occurs has less days than specified as the day of the month. So 31st // of each month will overshoot in february (29 days). We compensate for that by checking // if the day of the month we got is wrong, and then back up to the last day of the previous // month. if ((int) $this->recur["monthday"] >= 28 && (int) $this->recur["monthday"] <= 31 && gmdate("j", (int) $this->recur["start"]) < (int) $this->recur["monthday"]) { $this->recur["start"] -= gmdate("j", (int) $this->recur["start"]) * 24 * 60 * 60; } // "start" is now the first occurrence if ($term == 0xc) { // Calc first occ $monthIndex = (12 % $everyn * (((int) gmdate("Y", $this->recur["start"]) - 1601) % $everyn) % $everyn + ((int) gmdate("n", $this->recur["start"]) - 1)) % $everyn; $firstocc = 0; for ($i = 0; $i < $monthIndex; $i++) { $firstocc += $this->getMonthInSeconds(1601 + floor($i / 12), $i % 12 + 1) / 60; } $rdata .= pack("VVVV", $firstocc, $everyn, $this->recur["regen"], (int) $this->recur["monthday"]); } else { // Calc first occ $firstocc = 0; $monthIndex = (int) gmdate("n", $this->recur["start"]); for ($i = 1; $i < $monthIndex; $i++) { $firstocc += $this->getMonthInSeconds(1601 + floor($i / 12), $i) / 60; } $rdata .= pack("VVVV", $firstocc, $everyn, $this->recur["regen"], (int) $this->recur["monthday"]); } break; case 3: // monthly: on Nth weekday of every M month // yearly: on Nth weekday of M month if (!isset($this->recur["weekdays"]) && !isset($this->recur["nday"])) { return; } $weekdays = (int) $this->recur["weekdays"]; $nday = (int) $this->recur["nday"]; // Calc startdate $monthbegindow = (int) $this->recur["start"]; if ($nday == 5) { // Set date on the last day of the last month $monthbegindow += (gmdate("t", $monthbegindow) - gmdate("j", $monthbegindow)) * 24 * 60 * 60; } else { // Set on the first day of the month $monthbegindow -= (gmdate("j", $monthbegindow) - 1) * 24 * 60 * 60; } if ($term == 0xd) { // Set on right month if ($selmonth < $curmonth) { $tmp = 12 - $curmonth + $selmonth; } else { $tmp = $selmonth - $curmonth; } for ($i = 0; $i < $tmp; $i++) { $monthbegindow += $this->getMonthInSeconds($curyear, $curmonth); if ($curmonth == 12) { $curyear++; $curmonth = 0; } $curmonth++; } } else { // Check or you exist in the right month for ($i = 0; $i < 7; $i++) { if ($nday == 5 && 1 << (gmdate("w", $monthbegindow) - $i) % 7 & $weekdays) { $day = gmdate("j", $monthbegindow) - $i; break; } else { if ($nday != 5 && 1 << (gmdate("w", $monthbegindow) + $i) % 7 & $weekdays) { $day = ($nday - 1) * 7 + ($i + 1); break; } } } // Goto the next X month if (isset($day) && $day < gmdate("j", (int) $this->recur["start"])) { if ($nday == 5) { $monthbegindow += 24 * 60 * 60; if ($curmonth == 12) { $curyear++; $curmonth = 0; } $curmonth++; } for ($i = 0; $i < $everyn; $i++) { $monthbegindow += $this->getMonthInSeconds($curyear, $curmonth); if ($curmonth == 12) { $curyear++; $curmonth = 0; } $curmonth++; } if ($nday == 5) { $monthbegindow -= 24 * 60 * 60; } } } //FIXME: weekstart? $day = 0; // Set start on the right day for ($i = 0; $i < 7; $i++) { if ($nday == 5 && 1 << (gmdate("w", $monthbegindow) - $i) % 7 & $weekdays) { $day = $i; break; } else { if ($nday != 5 && 1 << (gmdate("w", $monthbegindow) + $i) % 7 & $weekdays) { $day = ($nday - 1) * 7 + ($i + 1); break; } } } if ($nday == 5) { $monthbegindow -= $day * 24 * 60 * 60; } else { $monthbegindow += ($day - 1) * 24 * 60 * 60; } $firstocc = 0; if ($term == 0xc) { // Calc first occ $monthIndex = (12 % $everyn * (((int) gmdate("Y", $this->recur["start"]) - 1601) % $everyn) % $everyn + ((int) gmdate("n", $this->recur["start"]) - 1)) % $everyn; for ($i = 0; $i < $monthIndex; $i++) { $firstocc += $this->getMonthInSeconds(1601 + floor($i / 12), $i % 12 + 1) / 60; } $rdata .= pack("VVVVV", $firstocc, $everyn, 0, $weekdays, $nday); } else { // Calc first occ $monthIndex = (int) gmdate("n", $this->recur["start"]); for ($i = 1; $i < $monthIndex; $i++) { $firstocc += $this->getMonthInSeconds(1601 + floor($i / 12), $i) / 60; } $rdata .= pack("VVVVV", $firstocc, $everyn, 0, $weekdays, $nday); } break; } break; } if (!isset($this->recur["term"])) { return; } // Terminate $term = (int) $this->recur["term"]; $rdata .= pack("CCCC", $term, 0x20, 0x0, 0x0); switch ($term) { // After the given enddate case 0x21: $rdata .= pack("V", 10); break; // After a number of times // After a number of times case 0x22: if (!isset($this->recur["numoccur"])) { return; } $rdata .= pack("V", (int) $this->recur["numoccur"]); break; // Never ends // Never ends case 0x23: $rdata .= pack("V", 0); break; } // Strange little thing for the recurrence type "every workday" if ((int) $this->recur["type"] == 0xb && (int) $this->recur["subtype"] == 1) { $rdata .= pack("V", 1); } else { // Other recurrences $rdata .= pack("V", 0); } // Exception data // Get all exceptions $deleted_items = $this->recur["deleted_occurences"]; $changed_items = $this->recur["changed_occurences"]; // Merge deleted and changed items into one list $items = $deleted_items; foreach ($changed_items as $changed_item) { array_push($items, $changed_item["basedate"]); } sort($items); // Add the merged list in to the rdata $rdata .= pack("V", count($items)); foreach ($items as $item) { $rdata .= pack("V", $this->unixDataToRecurData($item)); } // Loop through the changed exceptions (not deleted) $rdata .= pack("V", count($changed_items)); $items = array(); foreach ($changed_items as $changed_item) { $items[] = $this->dayStartOf($changed_item["start"]); } sort($items); // Add the changed items list int the rdata foreach ($items as $item) { $rdata .= pack("V", $this->unixDataToRecurData($item)); } // Set start date $rdata .= pack("V", $this->unixDataToRecurData((int) $this->recur["start"])); // Set enddate switch ($term) { // After the given enddate case 0x21: $rdata .= pack("V", $this->unixDataToRecurData((int) $this->recur["end"])); break; // After a number of times // After a number of times case 0x22: // @todo: calculate enddate with intval($this->recur["startocc"]) + intval($this->recur["duration"]) > 24 hour $occenddate = (int) $this->recur["start"]; switch ((int) $this->recur["type"]) { case 0xa: //daily if ($this->recur["subtype"] == 1) { // Daily every workday $restocc = (int) $this->recur["numoccur"]; // Get starting weekday $nowtime = $this->gmtime($occenddate); $j = $nowtime["tm_wday"]; while (1) { if ($j % 7 > 0 && $j % 7 < 6) { $restocc--; } $j++; if ($restocc <= 0) { break; } $occenddate += 24 * 60 * 60; } } else { // -1 because the first day already counts (from 1-1-1980 to 1-1-1980 is 1 occurrence) $occenddate += (int) $this->recur["everyn"] * 60 * ((int) $this->recur["numoccur"] - 1); } break; case 0xb: //weekly // Needed values // $forwardcount - number of weeks we can skip forward // $restocc - number of remaning occurrences after the week skip // Add the weeks till the last item $occenddate += $forwardcount * 7 * 24 * 60 * 60; $dayofweek = gmdate("w", $occenddate); // Loop through the last occurrences until we have had them all for ($j = 1; $restocc > 0; $j++) { // Jump to the next week (which may be N weeks away) when going over the week boundary if (($dayofweek + $j) % 7 == $weekstart) { $occenddate += ((int) $this->recur["everyn"] - 1) * 7 * 24 * 60 * 60; } // If this is a matching day, once less occurrence to process if ((int) $this->recur["weekdays"] & 1 << ($dayofweek + $j) % 7) { $restocc--; } // Next day $occenddate += 24 * 60 * 60; } break; case 0xc: //monthly //monthly case 0xd: //yearly $curyear = gmdate("Y", (int) $this->recur["start"]); $curmonth = gmdate("n", (int) $this->recur["start"]); // $forwardcount = months switch ((int) $this->recur["subtype"]) { case 2: // on D day of every M month while ($forwardcount > 0) { $occenddate += $this->getMonthInSeconds($curyear, $curmonth); if ($curmonth >= 12) { $curmonth = 1; $curyear++; } else { $curmonth++; } $forwardcount--; } // compensation between 28 and 31 if ((int) $this->recur["monthday"] >= 28 && (int) $this->recur["monthday"] <= 31 && gmdate("j", $occenddate) < (int) $this->recur["monthday"]) { if (gmdate("j", $occenddate) < 28) { $occenddate -= gmdate("j", $occenddate) * 24 * 60 * 60; } else { $occenddate += (gmdate("t", $occenddate) - gmdate("j", $occenddate)) * 24 * 60 * 60; } } break; case 3: // on Nth weekday of every M month $nday = (int) $this->recur["nday"]; //1 tot 5 $weekdays = (int) $this->recur["weekdays"]; while ($forwardcount > 0) { $occenddate += $this->getMonthInSeconds($curyear, $curmonth); if ($curmonth >= 12) { $curmonth = 1; $curyear++; } else { $curmonth++; } $forwardcount--; } if ($nday == 5) { // Set date on the last day of the last month $occenddate += (gmdate("t", $occenddate) - gmdate("j", $occenddate)) * 24 * 60 * 60; } else { // Set date on the first day of the last month $occenddate -= (gmdate("j", $occenddate) - 1) * 24 * 60 * 60; } for ($i = 0; $i < 7; $i++) { if ($nday == 5 && 1 << (gmdate("w", $occenddate) - $i) % 7 & $weekdays) { $occenddate -= $i * 24 * 60 * 60; break; } else { if ($nday != 5 && 1 << (gmdate("w", $occenddate) + $i) % 7 & $weekdays) { $occenddate += ($i + ($nday - 1) * 7) * 24 * 60 * 60; break; } } } break; //case 3: } break; } if (defined("PHP_INT_MAX") && $occenddate > PHP_INT_MAX) { $occenddate = PHP_INT_MAX; } $this->recur["end"] = $occenddate; $rdata .= pack("V", $this->unixDataToRecurData((int) $this->recur["end"])); break; // Never ends // Never ends case 0x23: default: $this->recur["end"] = 0x7fffffff; // max date -> 2038 $rdata .= pack("V", 0x5ae980df); break; } // UTC date $utcstart = $this->toGMT($this->tz, (int) $this->recur["start"]); $utcend = $this->toGMT($this->tz, (int) $this->recur["end"]); //utc date+time $utcfirstoccstartdatetime = isset($this->recur["startocc"]) ? $utcstart + (int) $this->recur["startocc"] * 60 : $utcstart; $utcfirstoccenddatetime = isset($this->recur["endocc"]) ? $utcstart + (int) $this->recur["endocc"] * 60 : $utcstart; // update reminder time mapi_setprops($this->message, array($this->proptags["reminder_time"] => $utcfirstoccstartdatetime)); // update first occurrence date mapi_setprops($this->message, array($this->proptags["startdate"] => $utcfirstoccstartdatetime)); mapi_setprops($this->message, array($this->proptags["duedate"] => $utcfirstoccenddatetime)); mapi_setprops($this->message, array($this->proptags["commonstart"] => $utcfirstoccstartdatetime)); mapi_setprops($this->message, array($this->proptags["commonend"] => $utcfirstoccenddatetime)); // Set Outlook properties, if it is an appointment if (isset($this->recur["message_class"]) && $this->recur["message_class"] == "IPM.Appointment") { // update real begin and real end date mapi_setprops($this->message, array($this->proptags["startdate_recurring"] => $utcstart)); mapi_setprops($this->message, array($this->proptags["enddate_recurring"] => $utcend)); // recurrencetype // Strange enough is the property recurrencetype, (type-0x9) and not the CDO recurrencetype mapi_setprops($this->message, array($this->proptags["recurrencetype"] => (int) $this->recur["type"] - 0x9)); // set named prop 'side_effects' to 369, needed for Outlook to ask for single or total recurrence when deleting mapi_setprops($this->message, array($this->proptags["side_effects"] => 369)); } else { mapi_setprops($this->message, array($this->proptags["side_effects"] => 3441)); } // FlagDueBy is datetime of the first reminder occurrence. Outlook gives on this time a reminder popup dialog // Any change of the recurrence (including changing and deleting exceptions) causes the flagdueby to be reset // to the 'next' occurrence; this makes sure that deleting the next ocurrence will correctly set the reminder to // the occurrence after that. The 'next' occurrence is defined as being the first occurrence that starts at moment X (server time) // with the reminder flag set. $reminderprops = mapi_getprops($this->message, array($this->proptags["reminder_minutes"])); if (isset($reminderprops[$this->proptags["reminder_minutes"]])) { $occ = false; $occurrences = $this->getItems(time(), 0x7ff00000, 3, true); for ($i = 0, $len = count($occurrences); $i < $len; $i++) { // This will actually also give us appointments that have already started, but not yet ended. Since we want the next // reminder that occurs after time(), we may have to skip the first few entries. We get 3 entries since that is the maximum // number that would be needed (assuming reminder for item X cannot be before the previous occurrence starts). Worst case: // time() is currently after start but before end of item, but reminder of next item has already passed (reminder for next item // can be DURING the previous item, eg daily allday events). In that case, the first and second items must be skipped. if ($occurrences[$i][$this->proptags["startdate"]] - $reminderprops[$this->proptags["reminder_minutes"]] * 60 > time()) { $occ = $occurrences[$i]; break; } } if ($occ) { mapi_setprops($this->message, array($this->proptags["flagdueby"] => $occ[$this->proptags["startdate"]] - $reminderprops[$this->proptags["reminder_minutes"]] * 60)); } else { // Last reminder passed, no reminders any more. mapi_setprops($this->message, array($this->proptags["reminder"] => false, $this->proptags["flagdueby"] => 0x7ff00000)); } } // Default data // Second item (0x08) indicates the Outlook version (see documentation at the bottom of this file for more information) $rdata .= pack("VCCCC", 0x3006, 0x8, 0x30, 0x0, 0x0); if (isset($this->recur["startocc"]) && isset($this->recur["endocc"])) { // Set start and endtime in minutes $rdata .= pack("VV", (int) $this->recur["startocc"], (int) $this->recur["endocc"]); } // Detailed exception data $changed_items = $this->recur["changed_occurences"]; $rdata .= pack("v", count($changed_items)); foreach ($changed_items as $changed_item) { // Set start and end time of exception $rdata .= pack("V", $this->unixDataToRecurData($changed_item["start"])); $rdata .= pack("V", $this->unixDataToRecurData($changed_item["end"])); $rdata .= pack("V", $this->unixDataToRecurData($changed_item["basedate"])); //Bitmask $bitmask = 0; // Check for changed strings if (isset($changed_item["subject"])) { $bitmask |= 1 << 0; } if (isset($changed_item["remind_before"])) { $bitmask |= 1 << 2; } if (isset($changed_item["reminder_set"])) { $bitmask |= 1 << 3; } if (isset($changed_item["location"])) { $bitmask |= 1 << 4; } if (isset($changed_item["busystatus"])) { $bitmask |= 1 << 5; } if (isset($changed_item["alldayevent"])) { $bitmask |= 1 << 7; } if (isset($changed_item["label"])) { $bitmask |= 1 << 8; } $rdata .= pack("v", $bitmask); // Set "subject" if (isset($changed_item["subject"])) { // convert utf-8 to non-unicode blob string (us-ascii?) $subject = iconv("UTF-8", "windows-1252//TRANSLIT", $changed_item["subject"]); $length = strlen($subject); $rdata .= pack("vv", $length + 1, $length); $rdata .= pack("a" . $length, $subject); } if (isset($changed_item["remind_before"])) { $rdata .= pack("V", $changed_item["remind_before"]); } if (isset($changed_item["reminder_set"])) { $rdata .= pack("V", $changed_item["reminder_set"]); } if (isset($changed_item["location"])) { $location = iconv("UTF-8", "windows-1252//TRANSLIT", $changed_item["location"]); $length = strlen($location); $rdata .= pack("vv", $length + 1, $length); $rdata .= pack("a" . $length, $location); } if (isset($changed_item["busystatus"])) { $rdata .= pack("V", $changed_item["busystatus"]); } if (isset($changed_item["alldayevent"])) { $rdata .= pack("V", $changed_item["alldayevent"]); } if (isset($changed_item["label"])) { $rdata .= pack("V", $changed_item["label"]); } } $rdata .= pack("V", 0); // write extended data foreach ($changed_items as $changed_item) { $rdata .= pack("V", 0); if (isset($changed_item["subject"]) || isset($changed_item["location"])) { $rdata .= pack("V", $this->unixDataToRecurData($changed_item["start"])); $rdata .= pack("V", $this->unixDataToRecurData($changed_item["end"])); $rdata .= pack("V", $this->unixDataToRecurData($changed_item["basedate"])); } if (isset($changed_item["subject"])) { $subject = iconv("UTF-8", "UCS-2LE", $changed_item["subject"]); $length = iconv_strlen($subject, "UCS-2LE"); $rdata .= pack("v", $length); $rdata .= pack("a" . $length * 2, $subject); } if (isset($changed_item["location"])) { $location = iconv("UTF-8", "UCS-2LE", $changed_item["location"]); $length = iconv_strlen($location, "UCS-2LE"); $rdata .= pack("v", $length); $rdata .= pack("a" . $length * 2, $location); } if (isset($changed_item["subject"]) || isset($changed_item["location"])) { $rdata .= pack("V", 0); } } $rdata .= pack("V", 0); // Set props mapi_setprops($this->message, array($this->proptags["recurring_data"] => $rdata, $this->proptags["recurring"] => true)); if (isset($this->tz) && $this->tz) { $timezone = "GMT"; if ($this->tz["timezone"] != 0) { // Create user readable timezone information $timezone = sprintf("(GMT %s%02d:%02d)", -$this->tz["timezone"] > 0 ? "+" : "-", abs($this->tz["timezone"] / 60), abs($this->tz["timezone"] % 60)); } mapi_setprops($this->message, array($this->proptags["timezone_data"] => $this->getTimezoneData($this->tz), $this->proptags["timezone"] => $timezone)); } }
/** * Imports a change on a folder * * @param object $folder SyncFolder * * @access public * @return string id of the folder * @throws StatusException */ public function ImportFolderChange($folder) { $id = isset($folder->serverid) ? $folder->serverid : false; $parent = $folder->parentid; $displayname = u2wi($folder->displayname); $type = $folder->type; if (Utils::IsSystemFolder($type)) { throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Error, system folder can not be created/modified", Utils::PrintAsString($folder->serverid), $folder->parentid, $displayname), SYNC_FSSTATUS_SYSTEMFOLDER); } // create a new folder if $id is not set if (!$id) { // the root folder is "0" - get IPM_SUBTREE if ($parent == "0") { $parentprops = mapi_getprops($this->store, array(PR_IPM_SUBTREE_ENTRYID)); if (isset($parentprops[PR_IPM_SUBTREE_ENTRYID])) { $parentfentryid = $parentprops[PR_IPM_SUBTREE_ENTRYID]; } } else { $parentfentryid = mapi_msgstore_entryidfromsourcekey($this->store, hex2bin($parent)); } if (!$parentfentryid) { throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Error, unable to open parent folder (no entry id)", Utils::PrintAsString(false), $folder->parentid, $displayname), SYNC_FSSTATUS_PARENTNOTFOUND); } $parentfolder = mapi_msgstore_openentry($this->store, $parentfentryid); if (!$parentfolder) { throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Error, unable to open parent folder (open entry)", Utils::PrintAsString(false), $folder->parentid, $displayname), SYNC_FSSTATUS_PARENTNOTFOUND); } // mapi_folder_createfolder() fails if a folder with this name already exists -> MAPI_E_COLLISION $newfolder = mapi_folder_createfolder($parentfolder, $displayname, ""); if (mapi_last_hresult()) { throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Error, mapi_folder_createfolder() failed: 0x%X", Utils::PrintAsString(false), $folder->parentid, $displayname, mapi_last_hresult()), SYNC_FSSTATUS_FOLDEREXISTS); } mapi_setprops($newfolder, array(PR_CONTAINER_CLASS => MAPIUtils::GetContainerClassFromFolderType($type))); $props = mapi_getprops($newfolder, array(PR_SOURCE_KEY)); if (isset($props[PR_SOURCE_KEY])) { $sourcekey = bin2hex($props[PR_SOURCE_KEY]); ZLog::Write(LOGLEVEL_DEBUG, sprintf("Created folder '%s' with id: '%s'", $displayname, $sourcekey)); return $sourcekey; } else { throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Error, folder created but PR_SOURCE_KEY not available: 0x%X", Utils::PrintAsString($folder->serverid), $folder->parentid, $displayname, mapi_last_hresult()), SYNC_FSSTATUS_SERVERERROR); } return false; } // update folder $entryid = mapi_msgstore_entryidfromsourcekey($this->store, hex2bin($id)); if (!$entryid) { throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Error, unable to open folder (no entry id): 0x%X", Utils::PrintAsString($folder->serverid), $folder->parentid, $displayname, mapi_last_hresult()), SYNC_FSSTATUS_PARENTNOTFOUND); } $folder = mapi_msgstore_openentry($this->store, $entryid); if (!$folder) { throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Error, unable to open folder (open entry): 0x%X", Utils::PrintAsString($folder->serverid), $folder->parentid, $displayname, mapi_last_hresult()), SYNC_FSSTATUS_PARENTNOTFOUND); } $props = mapi_getprops($folder, array(PR_SOURCE_KEY, PR_PARENT_SOURCE_KEY, PR_DISPLAY_NAME, PR_CONTAINER_CLASS)); if (!isset($props[PR_SOURCE_KEY]) || !isset($props[PR_PARENT_SOURCE_KEY]) || !isset($props[PR_DISPLAY_NAME]) || !isset($props[PR_CONTAINER_CLASS])) { throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Error, folder data not available: 0x%X", Utils::PrintAsString($folder->serverid), $folder->parentid, $displayname, mapi_last_hresult()), SYNC_FSSTATUS_SERVERERROR); } if ($parent == "0") { $parentprops = mapi_getprops($this->store, array(PR_IPM_SUBTREE_ENTRYID)); $parentfentryid = $parentprops[PR_IPM_SUBTREE_ENTRYID]; $mapifolder = mapi_msgstore_openentry($this->store, $parentfentryid); $rootfolderprops = mapi_getprops($mapifolder, array(PR_SOURCE_KEY)); $parent = bin2hex($rootfolderprops[PR_SOURCE_KEY]); ZLog::Write(LOGLEVEL_DEBUG, sprintf("ImportChangesICS->ImportFolderChange(): resolved AS parent '0' to sourcekey '%s'", $parent)); } // In theory the parent id could change, which means that the folder was moved. // It is unknown if any device supports this, so we do currently not implement it (no known device is able to do this) if (bin2hex($props[PR_PARENT_SOURCE_KEY]) !== $parent) { throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Folder was moved to another location, which is currently not supported. Please report this to the Z-Push dev team together with the WBXML log and your device details (model, firmware etc).", Utils::PrintAsString($folder->serverid), $folder->parentid, $displayname, mapi_last_hresult()), SYNC_FSSTATUS_UNKNOWNERROR); } $props = array(PR_DISPLAY_NAME => $displayname); mapi_setprops($folder, $props); mapi_savechanges($folder); if (mapi_last_hresult()) { throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Error, mapi_savechanges() failed: 0x%X", Utils::PrintAsString($folder->serverid), $folder->parentid, $displayname, mapi_last_hresult()), SYNC_FSSTATUS_SERVERERROR); } ZLog::Write(LOGLEVEL_DEBUG, "Imported changes for folder: {$id}"); return $id; }
function _storeAttachment($mapimessage, $part) { // attachment $attach = mapi_message_createattach($mapimessage); // Filename is present in both Content-Type: name=.. and in Content-Disposition: filename= if (isset($part->ctype_parameters["name"])) { $filename = $part->ctype_parameters["name"]; } else { if (isset($part->d_parameters["name"])) { $filename = $part->d_parameters["filename"]; } else { if (isset($part->d_parameters["filename"])) { //sending appointment with nokia only filename is set $filename = $part->d_parameters["filename"]; } else { $filename = "untitled"; } } } // Set filename and attachment type mapi_setprops($attach, array(PR_ATTACH_LONG_FILENAME => u2w($filename), PR_ATTACH_METHOD => ATTACH_BY_VALUE)); // Set attachment data mapi_setprops($attach, array(PR_ATTACH_DATA_BIN => $part->body)); // Set MIME type mapi_setprops($attach, array(PR_ATTACH_MIME_TAG => $part->ctype_primary . "/" . $part->ctype_secondary)); mapi_savechanges($attach); }
/** * 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); } } }
function sendCompleteUpdate($prefix, $action, $prefixComplete) { $messageprops = mapi_getprops($this->message, array($this->props['taskstate'])); if (!isset($messageprops[$this->props['taskstate']]) || $messageprops[$this->props['taskstate']] != tdsOWN) { return false; } // Can only decline assignee task mapi_setprops($this->message, array($this->props['complete'] => true, $this->props['datecompleted'] => $action["dateCompleted"], $this->props['status'] => 2, $this->props['percent_complete'] => 1)); $this->doUpdate($prefix, $prefixComplete); }
/** * Sets the properties one by one in a MAPI object * * @param mixed &$mapimessage * @param array &$propsToSet * @param array &$mapiprops * * @access private * @return */ private function setPropsIndividually(&$mapimessage, &$propsToSet, &$mapiprops) { foreach ($propsToSet as $prop => $value) { mapi_setprops($mapimessage, array($prop => $value)); if (mapi_last_hresult()) { Zlog::Write(LOGLEVEL_ERROR, sprintf("Failed setting property [%s] with value [%s], error code was:%x", array_search($prop, $mapiprops), $value, mapi_last_hresult())); } } }
/** * Updates the chunk data in the hidden folder if it changed. * If the chunkId is not available, it's created. * * @param string $folderid * @param string $chunkName The name of the chunk (used to find/update the chunk message). * The name is to be saved in the 'subject' of the chunk message. * @param int $amountEntries Amount of entries in the chunkdata. * @param string $chunkData The data containing all the data. * @param string $chunkCRC A checksum of the chunk data. To be saved in the 'location' of * the chunk message. Used to identify changed chunks. * @param string $gabId Id that uniquely identifies the GAB. If not set or null the default GAB is assumed. * @param string $gabName String that uniquely identifies the GAB. If not set the default GAB is assumed. * * @access protected * @return boolean */ protected function setChunkData($folderid, $chunkName, $amountEntries, $chunkData, $chunkCRC, $gabId = null, $gabName = 'default') { $log = sprintf("Kopano->setChunkData: %s\tEntries: %d\t Size: %d B\tCRC: %s - ", $chunkName, $amountEntries, strlen($chunkData), $chunkCRC); // find the chunk message in the folder $store = $this->getStore($gabId, $gabName); if (!$store) { return false; } $chunkdata = $this->findChunk($store, $folderid, $chunkName); $message = false; // message not found, create it if (empty($chunkdata)) { $folder = $this->getFolder($store, $folderid); $message = mapi_folder_createmessage($folder); mapi_setprops($message, array(PR_MESSAGE_CLASS => "IPM.Appointment", $this->mapiprops['chunktype'] => $this->chunkType, PR_SUBJECT => $chunkName, $this->mapiprops['createtime'] => time(), $this->mapiprops['reminderset'] => 0, $this->mapiprops['isrecurring'] => 0, $this->mapiprops['busystatus'] => 0)); $log .= "creating - "; } else { // we need to update the chunk if the CRC does not match! if ($chunkdata[$this->mapiprops['chunkCRC']] != $chunkCRC) { $message = mapi_msgstore_openentry($store, $chunkdata[PR_ENTRYID]); $log .= "opening - "; } else { $log .= "unchanged"; } } // update chunk if necessary if ($message) { mapi_setprops($message, array($this->mapiprops['chunkCRC'] => $chunkCRC, PR_BODY => $chunkData, $this->mapiprops['updatetime'] => time())); @mapi_savechanges($message); if (mapi_last_hresult()) { $log .= sprintf("error saving: 0x%08X", mapi_last_hresult()); } else { $log .= "saved"; } } // output log $this->log($log); return true; }