/** * Returns a list of folders of the Request::GetGETUser(). * If the user has not enough permissions an empty result is returned. * * @access public * @return array */ public function ListUserFolders() { $user = Request::GetGETUser(); $output = array(); $hasRights = ZPush::GetBackend()->Setup($user); ZLog::Write(LOGLEVEL_INFO, sprintf("WebserviceInfo::ListUserFolders(): permissions to open store '%s': %s", $user, Utils::PrintAsString($hasRights))); if ($hasRights) { $folders = ZPush::GetBackend()->GetHierarchy(); ZPush::GetTopCollector()->AnnounceInformation(sprintf("Retrieved details of %d folders", count($folders)), true); foreach ($folders as $folder) { $folder->StripData(); unset($folder->Store, $folder->flags, $folder->content, $folder->NoBackendFolder); $output[] = $folder; } } return $output; }
/** * Indicates if a exporter run is required. This is the case if the given folderstat is different from the saved one * or when the expiration time expired. * * @param string $currentFolderStat * @param boolean $doLog * * @access public * @return boolean */ public function IsExporterRunRequired($currentFolderStat, $doLog = false) { // if the backend returned false as folderstat, we have to run the exporter if ($currentFolderStat === false || $this->confirmationChanged) { $run = true; } else { // check if the folderstat differs from the saved one or expired $run = !($this->HasFolderStat() && $currentFolderStat === $this->GetFolderStat() && time() < $this->GetFolderStatTimeout()); } if ($doLog) { $expDate = $this->HasFolderStatTimeout() ? date('Y-m-d H:i:s', $this->GetFolderStatTimeout()) : "not set"; ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncParameters->IsExporterRunRequired(): %s - current: %s - saved: %s - expiring: %s", Utils::PrintAsString($run), Utils::PrintAsString($currentFolderStat), Utils::PrintAsString($this->GetFolderStat()), $expDate)); } return $run; }
/** * Called when the user moves an item on the PDA from one folder to another * * @param string $folderid id of the source folder * @param string $id id of the message * @param string $newfolderid id of the destination folder * * @access public * @return boolean status of the operation * @throws StatusException could throw specific SYNC_MOVEITEMSSTATUS_* exceptions */ public function MoveMessage($folderid, $id, $newfolderid) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->MoveMessage('%s','%s','%s')", $folderid, $id, $newfolderid)); $folderImapid = $this->getImapIdFromFolderId($folderid); $newfolderImapid = $this->getImapIdFromFolderId($newfolderid); $this->imap_reopenFolder($folderImapid); // TODO this should throw a StatusExceptions on errors like SYNC_MOVEITEMSSTATUS_SAMESOURCEANDDEST,SYNC_MOVEITEMSSTATUS_INVALIDSOURCEID,SYNC_MOVEITEMSSTATUS_CANNOTMOVE // read message flags $overview = @imap_fetch_overview($this->mbox, $id, FT_UID); if (!$overview) { throw new StatusException(sprintf("BackendIMAP->MoveMessage('%s','%s','%s'): Error, unable to retrieve overview of source message: %s", $folderid, $id, $newfolderid, imap_last_error()), SYNC_MOVEITEMSSTATUS_INVALIDSOURCEID); } else { // get next UID for destination folder // when moving a message we have to announce through ActiveSync the new messageID in the // destination folder. This is a "guessing" mechanism as IMAP does not inform that value. // when lots of simultaneous operations happen in the destination folder this could fail. // in the worst case the moved message is displayed twice on the mobile. $destStatus = imap_status($this->mbox, $this->server . $newfolderImapid, SA_ALL); if (!$destStatus) { throw new StatusException(sprintf("BackendIMAP->MoveMessage('%s','%s','%s'): Error, unable to open destination folder: %s", $folderid, $id, $newfolderid, imap_last_error()), SYNC_MOVEITEMSSTATUS_INVALIDDESTID); } $newid = $destStatus->uidnext; // move message $s1 = imap_mail_move($this->mbox, $id, $newfolderImapid, CP_UID); if (!$s1) { throw new StatusException(sprintf("BackendIMAP->MoveMessage('%s','%s','%s'): Error, copy to destination folder failed: %s", $folderid, $id, $newfolderid, imap_last_error()), SYNC_MOVEITEMSSTATUS_CANNOTMOVE); } // delete message in from-folder $s2 = imap_expunge($this->mbox); // open new folder $stat = $this->imap_reopenFolder($newfolderImapid); if (!$s1) { throw new StatusException(sprintf("BackendIMAP->MoveMessage('%s','%s','%s'): Error, openeing the destination folder: %s", $folderid, $id, $newfolderid, imap_last_error()), SYNC_MOVEITEMSSTATUS_CANNOTMOVE); } // remove all flags $s3 = @imap_clearflag_full($this->mbox, $newid, "\\Seen \\Answered \\Flagged \\Deleted \\Draft", FT_UID); $newflags = ""; if ($overview[0]->seen) { $newflags .= "\\Seen"; } if ($overview[0]->flagged) { $newflags .= " \\Flagged"; } if ($overview[0]->answered) { $newflags .= " \\Answered"; } $s4 = @imap_setflag_full($this->mbox, $newid, $newflags, FT_UID); ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->MoveMessage('%s','%s','%s'): result s-move: '%s' s-expunge: '%s' unset-Flags: '%s' set-Flags: '%s'", $folderid, $id, $newfolderid, Utils::PrintAsString($s1), Utils::PrintAsString($s2), Utils::PrintAsString($s3), Utils::PrintAsString($s4))); // return the new id "as string"" return $newid . ""; } }
/** * Called when the user moves an item on the PDA from one folder to another * * @param string $folderid id of the source folder * @param string $id id of the message * @param string $newfolderid id of the destination folder * @param ContentParameters $contentparameters * * @access public * @return boolean status of the operation * @throws StatusException could throw specific SYNC_MOVEITEMSSTATUS_* exceptions */ public function MoveMessage($folderid, $id, $newfolderid, $contentparameters) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->MoveMessage('%s','%s','%s')", $folderid, $id, $newfolderid)); $folderImapid = $this->getImapIdFromFolderId($folderid); $newfolderImapid = $this->getImapIdFromFolderId($newfolderid); if ($folderImapid == $newfolderImapid) { throw new StatusException(sprintf("BackendIMAP->MoveMessage('%s','%s','%s'): Error, destination folder is source folder. Canceling the move.", $folderid, $id, $newfolderid), SYNC_MOVEITEMSSTATUS_SAMESOURCEANDDEST); } $this->imap_reopen_folder($folderImapid); if ($this->imap_inside_cutoffdate(Utils::GetCutOffDate($contentparameters->GetFilterType()), $id)) { // read message flags $overview = @imap_fetch_overview($this->mbox, $id, FT_UID); if (!is_array($overview)) { throw new StatusException(sprintf("BackendIMAP->MoveMessage('%s','%s','%s'): Error, unable to retrieve overview of source message: %s", $folderid, $id, $newfolderid, imap_last_error()), SYNC_MOVEITEMSSTATUS_INVALIDSOURCEID); } else { // get next UID for destination folder // when moving a message we have to announce through ActiveSync the new messageID in the // destination folder. This is a "guessing" mechanism as IMAP does not inform that value. // when lots of simultaneous operations happen in the destination folder this could fail. // in the worst case the moved message is displayed twice on the mobile. $destStatus = imap_status($this->mbox, $this->server . $newfolderImapid, SA_ALL); if (!$destStatus) { throw new StatusException(sprintf("BackendIMAP->MoveMessage('%s','%s','%s'): Error, unable to open destination folder: %s", $folderid, $id, $newfolderid, imap_last_error()), SYNC_MOVEITEMSSTATUS_INVALIDDESTID); } $newid = $destStatus->uidnext; // move message $s1 = imap_mail_move($this->mbox, $id, $newfolderImapid, CP_UID); if (!$s1) { throw new StatusException(sprintf("BackendIMAP->MoveMessage('%s','%s','%s'): Error, copy to destination folder failed: %s", $folderid, $id, $newfolderid, imap_last_error()), SYNC_MOVEITEMSSTATUS_CANNOTMOVE); } // delete message in from-folder $s2 = imap_expunge($this->mbox); // open new folder $stat = $this->imap_reopen_folder($newfolderImapid); if (!$stat) { throw new StatusException(sprintf("BackendIMAP->MoveMessage('%s','%s','%s'): Error, opening the destination folder: %s", $folderid, $id, $newfolderid, imap_last_error()), SYNC_MOVEITEMSSTATUS_CANNOTMOVE); } // remove all flags $s3 = @imap_clearflag_full($this->mbox, $newid, "\\Seen \\Answered \\Flagged \\Deleted \\Draft", FT_UID); $newflags = ""; $move_to_trash = strcasecmp($newfolderImapid, $this->create_name_folder(IMAP_FOLDER_TRASH)) == 0; if ($overview[0]->seen || $move_to_trash && defined('IMAP_AUTOSEEN_ON_DELETE') && IMAP_AUTOSEEN_ON_DELETE == true) { $newflags .= "\\Seen"; } if ($overview[0]->flagged) { $newflags .= " \\Flagged"; } if ($overview[0]->answered) { $newflags .= " \\Answered"; } $s4 = @imap_setflag_full($this->mbox, $newid, $newflags, FT_UID); ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->MoveMessage('%s','%s','%s'): result s-move: '%s' s-expunge: '%s' unset-Flags: '%s' set-Flags: '%s'", $folderid, $id, $newfolderid, Utils::PrintAsString($s1), Utils::PrintAsString($s2), Utils::PrintAsString($s3), Utils::PrintAsString($s4))); // return the new id "as string" return $newid . ""; } } else { throw new StatusException(sprintf("BackendIMAP->MoveMessage(): Message is outside the sync range"), SYNC_MOVEITEMSSTATUS_INVALIDSOURCEID); } }
/** * Called when the user moves an item on the PDA from one folder to another * * @param string $folderid id of the source folder * @param string $id id of the message * @param string $newfolderid id of the destination folder * @param ContentParameters $contentparameters * * @access public * @return boolean status of the operation * @throws StatusException could throw specific SYNC_MOVEITEMSSTATUS_* exceptions */ public function MoveMessage($folderid, $id, $newfolderid, $contentparameters) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->MoveMessage('%s','%s','%s')", $folderid, $id, $newfolderid)); $folderImapid = $this->getImapIdFromFolderId($folderid); // SDB: When iOS is configured to "Archive Message" (instead of Delete Message), it will send a "MoveItems" // command to the Exchange server and attempt to move it to a folder called "0/Archive". Instead, we trap that // and send it to "[Gmail]/All Mail" folder instead. Note, that when on iOS device and you trigger a move from // folder A to B, it will correctly move that email, including to all folders with "[Gmail]...". if ($newfolderid == "0/Archive") { $newfolderImapid = "[Gmail]/All Mail"; } else { $newfolderImapid = $this->getImapIdFromFolderId($newfolderid); } if ($folderImapid == $newfolderImapid) { throw new StatusException(sprintf("BackendIMAP->MoveMessage('%s','%s','%s'): Error, destination folder is source folder. Canceling the move.", $folderid, $id, $newfolderid), SYNC_MOVEITEMSSTATUS_SAMESOURCEANDDEST); } $this->imap_reopen_folder($folderImapid); if ($this->imap_inside_cutoffdate(Utils::GetCutOffDate($contentparameters->GetFilterType()), $id)) { // read message flags $overview = @imap_fetch_overview($this->mbox, $id, FT_UID); if (!is_array($overview)) { throw new StatusException(sprintf("BackendIMAP->MoveMessage('%s','%s','%s'): Error, unable to retrieve overview of source message: %s", $folderid, $id, $newfolderid, imap_last_error()), SYNC_MOVEITEMSSTATUS_INVALIDSOURCEID); } else { // get next UID for destination folder // when moving a message we have to announce through ActiveSync the new messageID in the // destination folder. This is a "guessing" mechanism as IMAP does not inform that value. // when lots of simultaneous operations happen in the destination folder this could fail. // in the worst case the moved message is displayed twice on the mobile. $destStatus = imap_status($this->mbox, $this->server . $newfolderImapid, SA_ALL); if (!$destStatus) { throw new StatusException(sprintf("BackendIMAP->MoveMessage('%s','%s','%s'): Error, unable to open destination folder: %s", $folderid, $id, $newfolderid, imap_last_error()), SYNC_MOVEITEMSSTATUS_INVALIDDESTID); } $newid = $destStatus->uidnext; // move message $s1 = imap_mail_move($this->mbox, $id, $newfolderImapid, CP_UID); if (!$s1) { throw new StatusException(sprintf("BackendIMAP->MoveMessage('%s','%s','%s'): Error, copy to destination folder failed: %s", $folderid, $id, $newfolderid, imap_last_error()), SYNC_MOVEITEMSSTATUS_CANNOTMOVE); } // delete message in from-folder $s2 = imap_expunge($this->mbox); // open new folder $stat = $this->imap_reopen_folder($newfolderImapid); if (!$s1) { throw new StatusException(sprintf("BackendIMAP->MoveMessage('%s','%s','%s'): Error, opening the destination folder: %s", $folderid, $id, $newfolderid, imap_last_error()), SYNC_MOVEITEMSSTATUS_CANNOTMOVE); } // remove all flags $s3 = @imap_clearflag_full($this->mbox, $newid, "\\Seen \\Answered \\Flagged \\Deleted \\Draft", FT_UID); $newflags = ""; if ($overview[0]->seen) { $newflags .= "\\Seen"; } if ($overview[0]->flagged) { $newflags .= " \\Flagged"; } if ($overview[0]->answered) { $newflags .= " \\Answered"; } $s4 = @imap_setflag_full($this->mbox, $newid, $newflags, FT_UID); ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->MoveMessage('%s','%s','%s'): result s-move: '%s' s-expunge: '%s' unset-Flags: '%s' set-Flags: '%s'", $folderid, $id, $newfolderid, Utils::PrintAsString($s1), Utils::PrintAsString($s2), Utils::PrintAsString($s3), Utils::PrintAsString($s4))); // return the new id "as string" return $newid . ""; } } else { throw new StatusException(sprintf("BackendIMAP->MoveMessage(): Message is outside the sync range"), SYNC_MOVEITEMSSTATUS_INVALIDSOURCEID); } }
/** * Constructor * * @param mapisession $session * @param mapistore $store * @param string (opt) * * @access public * @throws StatusException */ public function ExportChangesICS($session, $store, $folderid = false) { // Open a hierarchy or a contents exporter depending on whether a folderid was specified $this->session = $session; $this->folderid = $folderid; $this->store = $store; $this->restriction = false; try { if ($folderid) { $entryid = mapi_msgstore_entryidfromsourcekey($store, $folderid); } else { $storeprops = mapi_getprops($this->store, array(PR_IPM_SUBTREE_ENTRYID)); $entryid = $storeprops[PR_IPM_SUBTREE_ENTRYID]; } $folder = false; if ($entryid) { $folder = mapi_msgstore_openentry($this->store, $entryid); } // Get the actual ICS exporter if ($folderid) { if ($folder) { $this->exporter = mapi_openproperty($folder, PR_CONTENTS_SYNCHRONIZER, IID_IExchangeExportChanges, 0, 0); } else { $this->exporter = false; } } else { $this->exporter = mapi_openproperty($folder, PR_HIERARCHY_SYNCHRONIZER, IID_IExchangeExportChanges, 0, 0); } } catch (MAPIException $me) { $this->exporter = false; // We return the general error SYNC_FSSTATUS_CODEUNKNOWN (12) which is also SYNC_STATUS_FOLDERHIERARCHYCHANGED (12) // if this happened while doing content sync, the mobile will try to resync the folderhierarchy throw new StatusException(sprintf("ExportChangesICS('%s','%s','%s'): Error, unable to open folder: 0x%X", $session, $store, Utils::PrintAsString($folderid), mapi_last_hresult()), SYNC_FSSTATUS_CODEUNKNOWN); } }
/** * Gets the AS folderid for a backendFolderId. * If there is no known AS folderId a new one is being created. * * @param string $backendid Backend folder id * @param boolean $generateNewIdIfNew Generates a new AS folderid for the case the backend folder is not known yet. * @param string $folderOrigin Folder type is one of 'U' (user) * 'C' (configured) * 'S' (shared) * 'G' (global address book) * @param string $folderName Folder name of the backend folder * * @access public * @return string */ public function GetFolderIdForBackendId($backendid, $generateNewIdIfNew, $folderOrigin, $folderName) { // build the backend-to-folderId backwards cache once if ($this->backend2folderidCache === false) { $this->backend2folderidCache = array(); foreach ($this->contentData as $folderid => $data) { if (isset($data[self::FOLDERBACKENDID])) { $this->backend2folderidCache[$data[self::FOLDERBACKENDID]] = $folderid; } } // if we couldn't find any backend-folderids but there is data in contentdata, then this is an old profile. // do not generate new folderids in this case if (empty($this->backend2folderidCache) && !empty($this->contentData)) { ZLog::Write(LOGLEVEL_DEBUG, "ASDevice->GetFolderIdForBackendId(): this is a profile without backend-folderid mapping. Returning folderids as is."); $this->backend2folderidCache = true; } } if (is_array($this->backend2folderidCache) && isset($this->backend2folderidCache[$backendid])) { return $this->backend2folderidCache[$backendid]; } // nothing found? Then it's a new one, get and add it if (is_array($this->backend2folderidCache) && $generateNewIdIfNew) { if ($folderName == null) { ZLog::Write(LOGLEVEL_INFO, "ASDevice->GetFolderIdForBackendId(): generating a new folder id for the folder without a name"); } $newHash = $this->generateFolderHash($backendid, $folderOrigin, $folderName); ZLog::Write(LOGLEVEL_DEBUG, sprintf("ASDevice->GetFolderIdForBackendId(): generated new folderid '%s' for backend-folderid '%s'", $newHash, $backendid)); // temporarily save the new hash also in the cache (new folders will only be saved at the end of request and could be requested before that $this->backend2folderidCache[$backendid] = $newHash; return $newHash; } ZLog::Write(LOGLEVEL_DEBUG, sprintf("ASDevice->GetFolderIdForBackendId(): no valid condition found for determining folderid for backendid '%s'. Returning as is!", Utils::PrintAsString($backendid))); return $backendid; }
/** * 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; }
/** * Checks if the sent policykey matches the latest policykey * saved for the device * * @param string $policykey * @param boolean $noDebug (opt) by default, debug message is shown * * @access public * @return boolean */ public function ProvisioningRequired($policykey, $noDebug = false) { $this->loadDeviceData(); // check if a remote wipe is required if ($this->device->GetWipeStatus() > SYNC_PROVISION_RWSTATUS_OK) { ZLog::Write(LOGLEVEL_INFO, sprintf("DeviceManager->ProvisioningRequired('%s'): YES, remote wipe requested", $policykey)); return true; } $p = $this->device->GetWipeStatus() != SYNC_PROVISION_RWSTATUS_NA && $policykey != $this->device->GetPolicyKey() || Request::WasPolicyKeySent() && $this->device->GetPolicyKey() == ASDevice::UNDEFINED; if (!$noDebug || $p) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->ProvisioningRequired('%s') saved device key '%s': %s", $policykey, $this->device->GetPolicyKey(), Utils::PrintAsString($p))); } return $p; }
/** * Indicates if the comand to be executed operates on the hierarchy * * @param int $commandCode * @access public * @return boolean */ public static function HierarchyCommand($commandCode) { $stat = self::checkCommandOptions($commandCode, self::HIERARCHYCOMMAND); ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZPush::HierarchyCommand(%d): %s", $commandCode, Utils::PrintAsString($stat))); return $stat; }
/** * Cleans up all older states. * If called with a $counter, all states previous state counter can be removed. * If additionally the $thisCounterOnly flag is true, only that specific counter will be removed. * If called without $counter, all keys (independently from the counter) can be removed. * * @param string $devid the device id * @param string $type the state type * @param string $key * @param string $counter (opt) * @param string $thisCounterOnly (opt) if provided, the exact counter only will be removed * * @access public * @return * @throws StateInvalidException */ public function CleanStates($devid, $type, $key, $counter = false, $thisCounterOnly = false) { $key = $this->returnNullified($key); ZLog::Write(LOGLEVEL_DEBUG, sprintf("SqlStateMachine->CleanStates(): devid:'%s' type:'%s' key:'%s' counter:'%s' thisCounterOnly:'%s'", $devid, $type, Utils::PrintAsString($key), Utils::PrintAsString($counter), Utils::PrintAsString($thisCounterOnly))); $params = $this->getParams($devid, $type, $key, $counter); if ($counter === false) { // Remove all the states. Counter are 0 or >0, then deleting >= 0 deletes all $sql = "DELETE FROM {$this->states_table} WHERE device_id = :devid AND state_type = :type AND uuid" . $this->getSQLOp($params, ':key') . " AND counter >= :counter"; } else { if ($counter !== false && $thisCounterOnly === true) { $sql = "DELETE FROM {$this->states_table} WHERE device_id = :devid AND state_type = :type AND uuid" . $this->getSQLOp($params, ':key') . " AND counter = :counter"; } else { $sql = "DELETE FROM {$this->states_table} WHERE device_id = :devid AND state_type = :type AND uuid" . $this->getSQLOp($params, ':key') . " AND counter < :counter"; } } $sth = null; try { $sth = $this->getDbh()->prepare($sql); $sth->execute($params); } catch (PDOException $ex) { ZLog::Write(LOGLEVEL_ERROR, sprintf("SqlStateMachine->CleanStates(): Error deleting states: %s", $ex->getMessage())); } }
/** * Imports a move of a message. This occurs when a user moves an item to another folder. * * @param string $id * @param string $newfolder destination folder * * @access public * @return boolean * @throws StatusException */ public function ImportMessageMove($id, $newfolder) { $this->didMove = true; // When we setup the $current importer, we didn't know what we needed to do, so we look only at the src folder for permissions. // Now the $newfolder could be read only as well. So we need to check it's permissions and then switch to a ReplyBackImExporter if it's r/o. if (!$this->isReplyBackExporter()) { // check if the user has permissions on the destination folder $dststore = self::$backend->GetMAPIStoreForFolderId(ZPush::GetAdditionalSyncFolderStore($newfolder), $newfolder); if (!self::$backend->HasSecretaryACLs($dststore, $newfolder)) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("KopanoChangesWrapper->ImportMessageMove(): destination folderid '%s' is missing permissions. Switching to ReplyBackImExporter.", Utils::PrintAsString($newfolder))); $this->replyback = $this->getReplyBackImExporter(); $this->current = $this->replyback; $this->current->SetMoveStates($this->moveSrcState, $this->moveDstState); if (isset($this->state)) { $this->current->Config($this->state->GetReplyBackState()); } } } return $this->current->ImportMessageMove($id, $newfolder); }
/** * Execute the migration. * * @access public * @return true */ public function DoMigration() { print "StateMigratorFileToDB->DoMigration(): Starting migration routine." . PHP_EOL; $starttime = time(); $deviceCount = 0; $stateCount = 0; try { // Fix hierarchy folder data before starting the migration ZPushAdmin::FixStatesHierarchyFolderData(); $this->fsm = new FileStateMachine(); if (!$this->fsm instanceof FileStateMachine) { throw new FatalNotImplementedException("This conversion script is only able to convert states from the FileStateMachine"); } // get all state information for all devices $alldevices = $this->fsm->GetAllDevices(false); foreach ($alldevices as $devid) { $deviceCount++; $lowerDevid = strtolower($devid); $allStates = $this->fsm->GetAllStatesForDevice($lowerDevid); printf("Processing device: %s with %s states\t", str_pad($devid, 35), str_pad(count($allStates), 4, ' ', STR_PAD_LEFT)); $migrated = 0; foreach ($allStates as $stateInfo) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("StateMigratorFileToDB->DoMigration(): Migrating state type:'%s' uuid:'%s' counter:'%s'", Utils::PrintAsString($stateInfo['type']), Utils::PrintAsString($stateInfo['uuid']), Utils::PrintAsString($stateInfo['counter']))); $state = $this->fsm->GetState($lowerDevid, $stateInfo['type'], $stateInfo['uuid'], (int) $stateInfo['counter'], false); $this->dbsm->SetState($state, $lowerDevid, $stateInfo['type'], empty($stateInfo['uuid']) ? NULL : $stateInfo['uuid'], (int) $stateInfo['counter']); $migrated++; } // link devices to users $devState = $this->fsm->GetState($lowerDevid, IStateMachine::DEVICEDATA); foreach ($devState->devices as $user => $dev) { $this->dbsm->LinkUserDevice($user, $dev->deviceid); } print " completed migration of {$migrated} states" . PHP_EOL; $stateCount += $migrated; } } catch (ZPushException $ex) { print PHP_EOL . "Something went wrong during the migration. The script will now exit." . PHP_EOL; die(get_class($ex) . ": " . $ex->getMessage() . PHP_EOL); } $timeSpent = gmdate("H:i:s", time() - $starttime); printf(PHP_EOL . "StateMigratorFileToDB->DoMigration(): Migration completed successfuly. Migrated %d devices with %d states in %s." . PHP_EOL . PHP_EOL, $deviceCount, $stateCount, $timeSpent); }
/** * Removes an additional folder from the given device and the Request::GetGETUser(). * * @param string $deviceId device id of where the folder should be removed. * @param string $add_folderid the folder id of the additional folder. * * @access public * @return boolean */ public function AdditionalFolderRemove($deviceId, $add_folderid) { $user = Request::GetGETUser(); $deviceId = preg_replace("/[^A-Za-z0-9]/", "", $deviceId); $add_folderid = preg_replace("/[^A-Za-z0-9]/", "", $add_folderid); $status = ZPushAdmin::AdditionalFolderRemove($user, $deviceId, $add_folderid); if (!$status) { ZPush::GetTopCollector()->AnnounceInformation(ZLog::GetLastMessage(LOGLEVEL_ERROR), true); throw new SoapFault("ERROR", ZLog::GetLastMessage(LOGLEVEL_ERROR)); } ZLog::Write(LOGLEVEL_INFO, sprintf("WebserviceDevice::AdditionalFolderRemove(): removed folder for device '%s' of user '%s': %s", $deviceId, $user, Utils::PrintAsString($status))); ZPush::GetTopCollector()->AnnounceInformation("Removed additional folder", true); return $status; }
/** * 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; }
public function Load($policies = array()) { if (empty($policies)) { $this->LoadDefaultPolicies(); } else { foreach ($policies as $p => $v) { if (!isset($this->mapping[$p])) { ZLog::Write(LOGLEVEL_INFO, sprintf("Policy '%s' not supported by the device, ignoring", substr($p, strpos($p, ':') + 1))); continue; } ZLog::Write(LOGLEVEL_INFO, sprintf("Policy '%s' enforced with: %s", substr($p, strpos($p, ':') + 1), Utils::PrintAsString($v))); $var = $this->mapping[$p][self::STREAMER_VAR]; $this->{$var} = $v; } } }
/** * Deletes all contents of the specified folder. * This is generally used to empty the trash (wastebasked), but could also be used on any * other folder. * * @param string $folderid * @param boolean $includeSubfolders (opt) also delete sub folders, default true * * @access public * @return boolean * @throws StatusException */ public function EmptyFolder($folderid, $includeSubfolders = true) { $folderentryid = mapi_msgstore_entryidfromsourcekey($this->store, hex2bin($folderid)); if (!$folderentryid) { throw new StatusException(sprintf("BackendZarafa->EmptyFolder('%s','%s'): Error, unable to open folder (no entry id)", $folderid, Utils::PrintAsString($includeSubfolders)), SYNC_ITEMOPERATIONSSTATUS_SERVERERROR); } $folder = mapi_msgstore_openentry($this->store, $folderentryid); if (!$folder) { throw new StatusException(sprintf("BackendZarafa->EmptyFolder('%s','%s'): Error, unable to open parent folder (open entry)", $folderid, Utils::PrintAsString($includeSubfolders)), SYNC_ITEMOPERATIONSSTATUS_SERVERERROR); } $flags = 0; if ($includeSubfolders) { $flags = DEL_ASSOCIATED; } ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendZarafa->EmptyFolder('%s','%s'): emptying folder", $folderid, Utils::PrintAsString($includeSubfolders))); // empty folder! mapi_folder_emptyfolder($folder, $flags); if (mapi_last_hresult()) { throw new StatusException(sprintf("BackendZarafa->EmptyFolder('%s','%s'): Error, mapi_folder_emptyfolder() failed: 0x%X", $folderid, Utils::PrintAsString($includeSubfolders), mapi_last_hresult()), SYNC_ITEMOPERATIONSSTATUS_SERVERERROR); } return true; }
/** * String representation of the object * * @return String */ public function __toString() { $str = get_class($this) . " (\n"; $streamerVars = array(); foreach ($this->mapping as $k => $v) { $streamerVars[$v[self::STREAMER_VAR]] = isset($v[self::STREAMER_TYPE]) ? $v[self::STREAMER_TYPE] : false; } foreach (get_object_vars($this) as $k => $v) { if ($k == "mapping") { continue; } if (array_key_exists($k, $streamerVars)) { $strV = "(S) "; } else { $strV = ""; } // self::STREAMER_ARRAY ? if (is_array($v)) { $str .= "\t" . $strV . $k . "(Array) size: " . count($v) . "\n"; foreach ($v as $value) { $str .= "\t\t" . Utils::PrintAsString($value) . "\n"; } } else { if ($v instanceof SyncObject) { $str .= "\t" . $strV . $k . " => " . str_replace("\n", "\n\t\t\t", $v->__toString()) . "\n"; } else { $str .= "\t" . $strV . $k . " => " . (isset($this->{$k}) ? Utils::PrintAsString($this->{$k}) : "null") . "\n"; } } } $str .= ")"; return $str; }
/** * Setup the backend to work on a specific store or checks ACLs there. * If only the $store is submitted, all Import/Export/Fetch/Etc operations should be * performed on this store (switch operations store). * If the ACL check is enabled, this operation should just indicate the ACL status on * the submitted store, without changing the store for operations. * For the ACL status, the currently logged on user MUST have access rights on * - the entire store - admin access if no folderid is sent, or * - on a specific folderid in the store (secretary/full access rights) * * The ACLcheck MUST fail if a folder of the authenticated user is checked! * * @param string $store target store, could contain a "domain\user" value * @param boolean $checkACLonly if set to true, Setup() should just check ACLs * @param string $folderid if set, only ACLs on this folderid are relevant * * @access public * @return boolean */ public function Setup($store, $checkACLonly = false, $folderid = false) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("Combined->Setup('%s', '%s', '%s')", $store, Utils::PrintAsString($checkACLonly), $folderid)); if (!is_array($this->backends)) { return false; } foreach ($this->backends as $i => $b) { $u = $store; if (isset($this->config['backends'][$i]['users']) && isset($this->config['backends'][$i]['users'][$store]['username'])) { $u = $this->config['backends'][$i]['users'][$store]['username']; } if ($this->backends[$i]->Setup($u, $checkACLonly, $folderid) == false) { ZLog::Write(LOGLEVEL_WARN, "Combined->Setup() failed"); return false; } } ZLog::Write(LOGLEVEL_INFO, "Combined->Setup() success"); return true; }
/** * Loads provisioning policies into a SyncProvisioning object. * * @param array $policies array with policies' names and values * @param boolean $logPolicies optional, determines if the policies and values should be logged. Default: false * * @access public * @return void */ public function Load($policies = array(), $logPolicies = false) { $this->LoadDefaultPolicies(); $streamerVars = $this->GetStreamerVars(); foreach ($policies as $p => $v) { if (!in_array($p, $streamerVars)) { ZLog::Write(LOGLEVEL_INFO, sprintf("Policy '%s' not supported by the device, ignoring", $p)); continue; } if ($logPolicies) { ZLog::Write(LOGLEVEL_WBXML, sprintf("Policy '%s' enforced with: %s (%s)", $p, is_array($v) ? Utils::PrintAsString(implode(',', $v)) : Utils::PrintAsString($v), gettype($v))); } $this->{$p} = is_array($v) && empty($v) ? array() : $v; } }