Skip to content

Commit

Permalink
Merge pull request #116 from matidau/more-as16
Browse files Browse the repository at this point in the history
More AS16.1 Changes
  • Loading branch information
matidau authored Nov 8, 2024
2 parents 446e046 + b143956 commit 7f29b07
Show file tree
Hide file tree
Showing 34 changed files with 1,156 additions and 236 deletions.
2 changes: 1 addition & 1 deletion src/backend/ipcsharedmemory/ipcsharedmemoryprovider.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public function __construct($type, $allocate, $class, $serverKey) {
$this->type = $type;
$this->allocate = $allocate;

if ($this->initSharedMem())
if ($this->initSharedMem())
ZLog::Write(LOGLEVEL_DEBUG, sprintf("%s(): Initialized.", $class));
}

Expand Down
23 changes: 18 additions & 5 deletions src/backend/kopano/importer.php
Original file line number Diff line number Diff line change
Expand Up @@ -440,15 +440,17 @@ public function ImportMessageChange($id, $message) {
$flags = SYNC_NEW_MESSAGE;

if(mapi_importcontentschanges_importmessagechange($this->importer, $props, $flags, $mapimessage)) {
$this->mapiprovider->SetMessage($mapimessage, $message);
$response = $this->mapiprovider->SetMessage($mapimessage, $message);
mapi_savechanges($mapimessage);

if (mapi_last_hresult())
throw new StatusException(sprintf("ImportChangesICS->ImportMessageChange('%s','%s'): Error, mapi_savechanges() failed: 0x%X", $id, get_class($message), mapi_last_hresult()), SYNC_STATUS_SYNCCANNOTBECOMPLETED);

$sourcekeyprops = mapi_getprops($mapimessage, array (PR_SOURCE_KEY));

return $this->prefix . bin2hex($sourcekeyprops[PR_SOURCE_KEY]);
$response->serverid = $this->prefix . bin2hex($sourcekeyprops[PR_SOURCE_KEY]);

return $response;
}
else
throw new StatusException(sprintf("ImportChangesICS->ImportMessageChange('%s','%s'): Error updating object: 0x%X", $id, get_class($message), mapi_last_hresult()), SYNC_STATUS_OBJECTNOTFOUND);
Expand Down Expand Up @@ -480,9 +482,19 @@ public function ImportMessageDeletion($id, $asSoftDelete = false) {
return true;
}

// check if we need to do actions before deleting this message (e.g. send meeting cancellations to attendees)
$entryid = mapi_msgstore_entryidfromsourcekey($this->store, $this->folderid, hex2bin($sk));
if ($entryid) {
// open the source message
$mapimessage = mapi_msgstore_openentry($this->store, $entryid);
$this->mapiprovider->PreDeleteMessage($mapimessage);
}

// do a 'soft' delete so people can un-delete if necessary
if(mapi_importcontentschanges_importmessagedeletion($this->importer, 1, array(hex2bin($sk))))
mapi_importcontentschanges_importmessagedeletion($this->importer, 1, [hex2bin($sk)]);
if (mapi_last_hresult()) {
throw new StatusException(sprintf("ImportChangesICS->ImportMessageDeletion('%s'): Error updating object: 0x%X", $sk, mapi_last_hresult()), SYNC_STATUS_OBJECTNOTFOUND);
}

return true;
}
Expand All @@ -496,10 +508,11 @@ public function ImportMessageDeletion($id, $asSoftDelete = false) {
* @param array $categories
*
* @access public
* @return boolean
* @return SyncObject
* @throws StatusException
*/
public function ImportMessageReadFlag($id, $flags, $categories = array()) {
$response = new SyncMailResponse();
list($fsk,$sk) = Utils::SplitMessageId($id);

// if $fsk is set, we convert it into a backend id.
Expand Down Expand Up @@ -545,7 +558,7 @@ public function ImportMessageReadFlag($id, $flags, $categories = array()) {
$p = mapi_message_setreadflag($realMessage, $flag);
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ImportChangesICS->ImportMessageReadFlag('%s','%d'): setting readflag on message: 0x%X", $id, $flags, mapi_last_hresult()));
}
return true;
return $response;
}

/**
Expand Down
102 changes: 70 additions & 32 deletions src/backend/kopano/kopano.php
Original file line number Diff line number Diff line change
Expand Up @@ -805,7 +805,7 @@ public function GetAttachmentData($attname) {
if(!strpos($attname, ":"))
throw new StatusException(sprintf("KopanoBackend->GetAttachmentData('%s'): Error, attachment requested for non-existing item", $attname), SYNC_ITEMOPERATIONSSTATUS_INVALIDATT);

list($id, $attachnum, $parentEntryid) = explode(":", $attname);
list($id, $attachnum, $parentEntryid, $exceptionBasedate) = explode(":", $attname);
if (isset($parentEntryid)) {
$this->Setup(ZPush::GetAdditionalSyncFolderStore($parentEntryid));
}
Expand All @@ -820,6 +820,14 @@ public function GetAttachmentData($attname) {
if(!$attach)
throw new StatusException(sprintf("KopanoBackend->GetAttachmentData('%s'): Error, unable to open attachment number '%s' with: 0x%X", $attname, $attachnum, mapi_last_hresult()), SYNC_ITEMOPERATIONSSTATUS_INVALIDATT);

// attachment of a recurring appointment execption
if(strlen($exceptionBasedate) > 1) {
$recurrence = new Recurrence($this->store, $message);
$exceptionatt = $recurrence->getExceptionAttachment(hex2bin($exceptionBasedate));
$exceptionobj = mapi_attach_openobj($exceptionatt, 0);
$attach = mapi_message_openattach($exceptionobj, $attachnum);
}

// get necessary attachment props
$attprops = mapi_getprops($attach, array(PR_ATTACH_MIME_TAG, PR_ATTACH_MIME_TAG_W, PR_ATTACH_METHOD));
$attachment = new SyncItemOperationsAttachment();
Expand Down Expand Up @@ -895,7 +903,9 @@ public function EmptyFolder($folderid, $includeSubfolders = true) {
* @return string id of the created/updated calendar obj
* @throws StatusException
*/
public function MeetingResponse($requestid, $folderid, $response) {
public function MeetingResponse($requestid, $folderid, $request) {
$requestid = $calendarid = $request['requestid'];
$response = $request['response'];
// Use standard meeting response code to process meeting request
list($fid, $requestid) = Utils::SplitMessageId($requestid);
$reqentryid = mapi_msgstore_entryidfromsourcekey($this->store, hex2bin($folderid), hex2bin($requestid));
Expand All @@ -908,9 +918,15 @@ public function MeetingResponse($requestid, $folderid, $response) {

// ios sends calendar item in MeetingResponse
// @see https://jira.z-hub.io/browse/ZP-1524
$searchForResultCalendarItem = false;
$folderClass = ZPush::GetDeviceManager()->GetFolderClassFromCacheByID($fid);
// find the corresponding meeting request
if ($folderClass != 'Email') {
if ($folderClass == 'Email') {
// The mobile requested this on a MR, when finishing we need to search for the resulting calendar item!
$searchForResultCalendarItem = true;
}
// we are operating on the calendar item - try searching for the corresponding meeting request first
else {
$props = MAPIMapping::GetMeetingRequestProperties();
$props = getPropIdsFromStrings($this->store, $props);

Expand All @@ -934,18 +950,23 @@ public function MeetingResponse($requestid, $folderid, $response) {

$inboxcontents = mapi_folder_getcontentstable($folder);

$rows = mapi_table_queryallrows($inboxcontents, array(PR_ENTRYID), $restrict);
if (empty($rows)) {
throw new StatusException(sprintf("BackendKopano->MeetingResponse('%s','%s', '%s'): Error, meeting request not found in the inbox", $requestid, $folderid, $response), SYNC_MEETRESPSTATUS_INVALIDMEETREQ);
$rows = mapi_table_queryallrows($inboxcontents, [PR_ENTRYID, PR_SOURCE_KEY], $restrict);
// AS 14.0 and older can only respond to a MR in the Inbox!
if (empty($rows) && Request::GetProtocolVersion() <= 14.0) {
throw new StatusException(sprintf("BackendKopano->MeetingResponse('%s','%s', '%s'): Error, meeting request not found in the inbox. Can't proceed, aborting!", $requestid, $folderid, $response), SYNC_MEETRESPSTATUS_INVALIDMEETREQ);
}
if (!empty($rows)) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendKopano->MeetingResponse found meeting request in the inbox with ID: %s", bin2hex($rows[0][PR_SOURCE_KEY])));
$reqentryid = $rows[0][PR_ENTRYID];
$mapimessage = mapi_msgstore_openentry($this->store, $reqentryid);
// As we are using an MR from the inbox, when finishing we need to search for the resulting calendar item!
$searchForResultCalendarItem = true;
}
ZLog::Write(LOGLEVEL_DEBUG, "BackendKopano->MeetingResponse found meeting request in the inbox");
$mapimessage = mapi_msgstore_openentry($this->store, $rows[0][PR_ENTRYID]);
$reqentryid = $rows[0][PR_ENTRYID];
}

$meetingrequest = new Meetingrequest($this->store, $mapimessage, $this->session);

if(!$meetingrequest->isMeetingRequest())
if (Request::GetProtocolVersion() <= 14.0 && !$meetingrequest->isMeetingRequest() && !$meetingrequest->isMeetingRequestResponse() && !$meetingrequest->isMeetingCancellation()) {
throw new StatusException(sprintf("BackendKopano->MeetingResponse('%s','%s', '%s'): Error, attempt to respond to non-meeting request", $requestid, $folderid, $response), SYNC_MEETRESPSTATUS_INVALIDMEETREQ);

if($meetingrequest->isLocalOrganiser())
Expand All @@ -956,22 +977,37 @@ public function MeetingResponse($requestid, $folderid, $response) {
// anymore for the ios devices since at least version 12.4. Z-Push will send the
// accepted email in such a case.
// @see https://jira.z-hub.io/browse/ZP-1524
$sendresponse = false;
$deviceType = strtolower(Request::GetDeviceType());
if ($deviceType == 'iphone' || $deviceType == 'ipad' || $deviceType == 'ipod') {
$matches = array();
if (preg_match("/^Apple-.*?\/(\d{4})\./", Request::GetUserAgent(), $matches) && isset($matches[1]) && $matches[1] >= 1607 && $matches[1] <= 1707) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendKopano->MeetingResponse: iOS device %s->%s", Request::GetDeviceType(), Request::GetUserAgent()));
$sendresponse = true;
}
// AS-16.1: did the attendee propose a new time ?
if (!empty($request['proposedstarttime'])) {
$request['proposedstarttime'] = Utils::parseDate($request['proposedstarttime']);
}
else {
$request['proposedstarttime'] = false;
}
if (!empty($request['proposedendtime'])) {
$request['proposedendtime'] = Utils::parseDate($request['proposedendtime']);
}
else {
$request['proposedendtime'] = false;
}
if (!isset($request['body'])) {
$request['body'] = false;
}
// from AS-14.0 we have to take care of sending out meeting request responses
if (Request::GetProtocolVersion() >= 14.0) {
$sendresponse = true;
}
else {
// Old AS versions send MR updates by themselves - so our MR processing doesn't need to do this
$sendresponse = false;
}
switch($response) {
case 1: // accept
default:
$entryid = $meetingrequest->doAccept(false, $sendresponse, false, false, false, false, true); // last true is the $userAction
break;
case 2: // tentative
$entryid = $meetingrequest->doAccept(true, $sendresponse, false, false, false, false, true); // last true is the $userAction
$entryid = $meetingrequest->doAccept(true, $sendresponse, false, $request['proposedstarttime'], $request['proposedendtime'], $request['body'], true); // last true is the $userAction
break;
case 3: // decline
$meetingrequest->doDecline(false);
Expand All @@ -980,19 +1016,21 @@ public function MeetingResponse($requestid, $folderid, $response) {

// F/B will be updated on logoff

// We have to return the ID of the new calendar item, so do that here
$calendarid = "";
$calFolderId = "";
if (isset($entryid)) {
$newitem = mapi_msgstore_openentry($this->store, $entryid);
// new item might be in a delegator's store. ActiveSync does not support accepting them.
if (!$newitem) {
throw new StatusException(sprintf("BackendKopano->MeetingResponse('%s','%s', '%s'): Object with entryid '%s' was not found in user's store (0x%X). It might be in a delegator's store.", $requestid, $folderid, $response, bin2hex($entryid), mapi_last_hresult()), SYNC_MEETRESPSTATUS_SERVERERROR, null, LOGLEVEL_WARN);
}
// We have to return the ID of the new calendar item if it was created from an email
if ($searchForResultCalendarItem) {
$calendarid = "";
$calFolderId = "";
if (isset($entryid)) {
$newitem = mapi_msgstore_openentry($this->store, $entryid);
// new item might be in a delegator's store. ActiveSync does not support accepting them.
if (!$newitem) {
throw new StatusException(sprintf("Grommunio->MeetingResponse('%s','%s', '%s'): Object with entryid '%s' was not found in user's store (0x%X). It might be in a delegator's store.", $requestid, $folderid, $response, bin2hex($entryid), mapi_last_hresult()), SYNC_MEETRESPSTATUS_SERVERERROR, null, LOGLEVEL_WARN);
}

$newprops = mapi_getprops($newitem, array(PR_SOURCE_KEY, PR_PARENT_SOURCE_KEY));
$calendarid = bin2hex($newprops[PR_SOURCE_KEY]);
$calFolderId = bin2hex($newprops[PR_PARENT_SOURCE_KEY]);
$newprops = mapi_getprops($newitem, array(PR_SOURCE_KEY, PR_PARENT_SOURCE_KEY));
$calendarid = bin2hex($newprops[PR_SOURCE_KEY]);
$calFolderId = bin2hex($newprops[PR_PARENT_SOURCE_KEY]);
}
}

// on recurring items, the MeetingRequest class responds with a wrong entryid
Expand Down Expand Up @@ -1499,7 +1537,7 @@ public function TerminateSearch($pid) {
}

$storeProps = mapi_getprops($this->store, array(PR_STORE_SUPPORT_MASK, PR_FINDER_ENTRYID));
if (($storeProps[PR_STORE_SUPPORT_MASK] & STORE_SEARCH_OK) != STORE_SEARCH_OK) {
if (isset($storeProps[PR_STORE_SUPPORT_MASK]) && (($storeProps[PR_STORE_SUPPORT_MASK] & STORE_SEARCH_OK) != STORE_SEARCH_OK)) {
ZLog::Write(LOGLEVEL_WARN, "Store doesn't support search folders. Public store doesn't have FINDER_ROOT folder");
return false;
}
Expand Down
6 changes: 3 additions & 3 deletions src/backend/kopano/mapi/class.meetingrequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -612,7 +612,7 @@ function isInCalendar()
* @param string $newProposedStartTime contains starttime if user has proposed other time
* @param string $newProposedEndTime contains endtime if user has proposed other time
* @param string $basedate start of day of occurrence for which user has accepted the recurrent meeting
* @param boolean $isImported true to indicate that MR is imported from .ics or .vcs file else it false.
* @param boolean $isImported true to indicate that MR is imported from .ics or .vcs file else it false.
* @return string $entryid entryid of item which created/updated in calendar
*/
function doAccept($tentative, $sendresponse, $move, $newProposedStartTime=false, $newProposedEndTime=false, $body=false, $userAction = false, $store=false, $basedate = false, $isImported = false)
Expand Down Expand Up @@ -1830,7 +1830,7 @@ function checkCalendarWriteAccess($store = false)

/**
* Function will resolve the user and open its store
* @param String $ownerentryid the entryid of the user
* @param String $ownerentryid the entryid of the user
* @return MAPIStore store of the user
*/
function openCustomUserStore($ownerentryid)
Expand Down Expand Up @@ -3742,7 +3742,7 @@ function getLocalCategories($calendarItem, $store, $calFolder)
}

return $localCategories;
}
}

/**
* Helper function which is use to apply local categories on respective occurrences.
Expand Down
5 changes: 5 additions & 0 deletions src/backend/kopano/mapimapping.php
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ public static function GetEmailProperties() {
"rtfinsync" => PR_RTF_IN_SYNC,
"processed" => PR_PROCESSED,
"messageflags" => PR_MESSAGE_FLAGS,
"clientsubmittime" => PR_CLIENT_SUBMIT_TIME,
);
}

Expand Down Expand Up @@ -341,6 +342,10 @@ public static function GetAppointmentProperties() {
"rtfcompressed" => PR_RTF_COMPRESSED,
"html" => PR_HTML,
"rtfinsync" => PR_RTF_IN_SYNC,
"entryid" => PR_ENTRYID,
"parentsourcekey" => PR_PARENT_SOURCE_KEY,
"location" => "PT_STRING8:PSETID_Appointment:0x8208",
"locations" => "PT_STRING8:PSETID_CustomerLocation:Locations",
);
}

Expand Down
Loading

0 comments on commit 7f29b07

Please sign in to comment.