if ($ex instanceof AuthenticationRequiredException) { ZPush::PrintZPushLegal($exclass, sprintf('<pre>%s</pre>', $ex->getMessage())); // log the failed login attemt e.g. for fail2ban if (defined('LOGAUTHFAIL') && LOGAUTHFAIL != false) { ZLog::Write(LOGLEVEL_WARN, sprintf("IP: %s failed to authenticate user '%s'", Request::GetRemoteAddr(), Request::GetAuthUser() ? Request::GetAuthUser() : Request::GetGETUser())); } } else { if ($ex instanceof WBXMLException) { ZLog::Write(LOGLEVEL_FATAL, "Request could not be processed correctly due to a WBXMLException. Please report this."); } else { if (!$ex instanceof ZPushException || $ex->showLegalNotice()) { $cmdinfo = Request::GetCommand() ? sprintf(" processing command <i>%s</i>", Request::GetCommand()) : ""; $extrace = $ex->getTrace(); $trace = !empty($extrace) ? "\n\nTrace:\n" . print_r($extrace, 1) : ""; ZPush::PrintZPushLegal($exclass . $cmdinfo, sprintf('<pre>%s</pre>', $ex->getMessage() . $trace)); } } } // Announce exception to process loop detection if (ZPush::GetDeviceManager(false)) { ZPush::GetDeviceManager()->AnnounceProcessException($ex); } // Announce exception if the TopCollector if available ZPush::GetTopCollector()->AnnounceInformation(get_class($ex), true); } // save device data if the DeviceManager is available if (ZPush::GetDeviceManager(false)) { ZPush::GetDeviceManager()->Save(); } // end gracefully ZLog::Write(LOGLEVEL_DEBUG, '-------- End');
/** * Saves the permanent and state related storage data of the user and device * if they were loaded previousily * If the backend storage is used this should be called * * @access protected * @return */ protected function SaveStorages() { if (isset($this->permanentStorage)) { try { ZPush::GetDeviceManager()->GetStateManager()->SetBackendStorage($this->permanentStorage, StateManager::BACKENDSTORAGE_PERMANENT); } catch (StateNotYetAvailableException $snyae) { } catch (StateNotFoundException $snfe) { } } if (isset($this->stateStorage)) { try { $this->storage_state = ZPush::GetDeviceManager()->GetStateManager()->SetBackendStorage($this->stateStorage, StateManager::BACKENDSTORAGE_STATE); } catch (StateNotYetAvailableException $snyae) { } catch (StateNotFoundException $snfe) { } } }
/** * Handles the Sync command * Performs the synchronization of messages * * @param int $commandCode * * @access public * @return boolean */ public function Handle($commandCode) { // Contains all requested folders (containers) $sc = new SyncCollections(); $status = SYNC_STATUS_SUCCESS; $wbxmlproblem = false; $emptysync = false; // check if the hierarchySync was fully completed if (USE_PARTIAL_FOLDERSYNC) { if (self::$deviceManager->GetFolderSyncComplete() === false) { ZLog::Write(LOGLEVEL_INFO, "Request->HandleSync(): Sync request aborted, as exporting of folders has not yet completed"); self::$topCollector->AnnounceInformation("Aborted due incomplete folder sync", true); $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED; } else { ZLog::Write(LOGLEVEL_INFO, "Request->HandleSync(): FolderSync marked as complete"); } } // Start Synchronize if (self::$decoder->getElementStartTag(SYNC_SYNCHRONIZE)) { // AS 1.0 sends version information in WBXML if (self::$decoder->getElementStartTag(SYNC_VERSION)) { $sync_version = self::$decoder->getElementContent(); ZLog::Write(LOGLEVEL_DEBUG, sprintf("WBXML sync version: '%s'", $sync_version)); if (!self::$decoder->getElementEndTag()) { return false; } } // Synching specified folders // Android still sends heartbeat sync even if all syncfolders are disabled. // Check if Folders tag is empty (<Folders/>) and only sync if there are // some folders in the request. See ZP-172 $startTag = self::$decoder->getElementStartTag(SYNC_FOLDERS); if (isset($startTag[EN_FLAGS]) && $startTag[EN_FLAGS]) { while (self::$decoder->getElementStartTag(SYNC_FOLDER)) { $actiondata = array(); $actiondata["requested"] = true; $actiondata["clientids"] = array(); $actiondata["modifyids"] = array(); $actiondata["removeids"] = array(); $actiondata["fetchids"] = array(); $actiondata["statusids"] = array(); // read class, synckey and folderid without SyncParameters Object for now $class = $synckey = $folderid = false; //for AS versions < 2.5 if (self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) { $class = self::$decoder->getElementContent(); ZLog::Write(LOGLEVEL_DEBUG, sprintf("Sync folder: '%s'", $class)); if (!self::$decoder->getElementEndTag()) { return false; } } // SyncKey if (self::$decoder->getElementStartTag(SYNC_SYNCKEY)) { $synckey = "0"; if (($synckey = self::$decoder->getElementContent()) !== false) { if (!self::$decoder->getElementEndTag()) { return false; } } } else { return false; } // FolderId if (self::$decoder->getElementStartTag(SYNC_FOLDERID)) { $folderid = self::$decoder->getElementContent(); if (!self::$decoder->getElementEndTag()) { return false; } } // compatibility mode AS 1.0 - get folderid which was sent during GetHierarchy() if (!$folderid && $class) { $folderid = self::$deviceManager->GetFolderIdFromCacheByClass($class); } // folderid HAS TO BE known by now, so we retrieve the correct SyncParameters object for an update try { $spa = self::$deviceManager->GetStateManager()->GetSynchedFolderState($folderid); // TODO remove resync of folders for < Z-Push 2 beta4 users // this forces a resync of all states previous to Z-Push 2 beta4 if (!$spa instanceof SyncParameters) { throw new StateInvalidException("Saved state are not of type SyncParameters"); } // new/resync requested if ($synckey == "0") { $spa->RemoveSyncKey(); } else { if ($synckey !== false) { $spa->SetSyncKey($synckey); } } } catch (StateInvalidException $stie) { $spa = new SyncParameters(); $status = SYNC_STATUS_INVALIDSYNCKEY; self::$topCollector->AnnounceInformation("State invalid - Resync folder", true); self::$deviceManager->ForceFolderResync($folderid); } // update folderid.. this might be a new object $spa->SetFolderId($folderid); if ($class !== false) { $spa->SetContentClass($class); } // Get class for as versions >= 12.0 if (!$spa->HasContentClass()) { try { $spa->SetContentClass(self::$deviceManager->GetFolderClassFromCacheByID($spa->GetFolderId())); ZLog::Write(LOGLEVEL_DEBUG, sprintf("GetFolderClassFromCacheByID from Device Manager: '%s' for id:'%s'", $spa->GetContentClass(), $spa->GetFolderId())); } catch (NoHierarchyCacheAvailableException $nhca) { $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED; self::$deviceManager->ForceFullResync(); } } // done basic SPA initialization/loading -> add to SyncCollection $sc->AddCollection($spa); $sc->AddParameter($spa, "requested", true); if ($spa->HasContentClass()) { self::$topCollector->AnnounceInformation(sprintf("%s request", $spa->GetContentClass()), true); } else { ZLog::Write(LOGLEVEL_WARN, "Not possible to determine class of request. Request did not contain class and apparently there is an issue with the HierarchyCache."); } // SUPPORTED properties if (($se = self::$decoder->getElementStartTag(SYNC_SUPPORTED)) !== false) { // ZP-481: LG phones send an empty supported tag, so only read the contents if available here // if <Supported/> is received, it's as no supported fields would have been sent at all. // unsure if this is the correct approach, or if in this case some default list should be used if ($se[EN_FLAGS] & EN_FLAGS_CONTENT) { $supfields = array(); while (1) { $el = self::$decoder->getElement(); if ($el[EN_TYPE] == EN_TYPE_ENDTAG) { break; } else { $supfields[] = $el[EN_TAG]; } } self::$deviceManager->SetSupportedFields($spa->GetFolderId(), $supfields); } } // Deletes as moves can be an empty tag as well as have value if (self::$decoder->getElementStartTag(SYNC_DELETESASMOVES)) { $spa->SetDeletesAsMoves(true); if (($dam = self::$decoder->getElementContent()) !== false) { $spa->SetDeletesAsMoves((bool) $dam); if (!self::$decoder->getElementEndTag()) { return false; } } } // Get changes can be an empty tag as well as have value // code block partly contributed by dw2412 if (self::$decoder->getElementStartTag(SYNC_GETCHANGES)) { $sc->AddParameter($spa, "getchanges", true); if (($gc = self::$decoder->getElementContent()) !== false) { $sc->AddParameter($spa, "getchanges", $gc); if (!self::$decoder->getElementEndTag()) { return false; } } } if (self::$decoder->getElementStartTag(SYNC_WINDOWSIZE)) { $ws = self::$decoder->getElementContent(); // normalize windowsize - see ZP-477 if ($ws == 0 || $ws > 512) { $ws = 512; } $spa->SetWindowSize($ws); // also announce the currently requested window size to the DeviceManager self::$deviceManager->SetWindowSize($spa->GetFolderId(), $spa->GetWindowSize()); if (!self::$decoder->getElementEndTag()) { return false; } } // conversation mode requested if (self::$decoder->getElementStartTag(SYNC_CONVERSATIONMODE)) { $spa->SetConversationMode(true); if (($conversationmode = self::$decoder->getElementContent()) !== false) { $spa->SetConversationMode((bool) $conversationmode); if (!self::$decoder->getElementEndTag()) { return false; } } } // Do not truncate by default $spa->SetTruncation(SYNC_TRUNCATION_ALL); // use default conflict handling if not specified by the mobile $spa->SetConflict(SYNC_CONFLICT_DEFAULT); while (self::$decoder->getElementStartTag(SYNC_OPTIONS)) { $firstOption = true; while (1) { // foldertype definition if (self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) { $foldertype = self::$decoder->getElementContent(); ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): specified options block with foldertype '%s'", $foldertype)); // switch the foldertype for the next options $spa->UseCPO($foldertype); // set to synchronize all changes. The mobile could overwrite this value $spa->SetFilterType(SYNC_FILTERTYPE_ALL); if (!self::$decoder->getElementEndTag()) { return false; } } else { if ($firstOption) { $spa->UseCPO(); // set to synchronize all changes. The mobile could overwrite this value $spa->SetFilterType(SYNC_FILTERTYPE_ALL); } } $firstOption = false; if (self::$decoder->getElementStartTag(SYNC_FILTERTYPE)) { $spa->SetFilterType(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { return false; } } if (self::$decoder->getElementStartTag(SYNC_TRUNCATION)) { $spa->SetTruncation(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { return false; } } if (self::$decoder->getElementStartTag(SYNC_RTFTRUNCATION)) { $spa->SetRTFTruncation(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { return false; } } if (self::$decoder->getElementStartTag(SYNC_MIMESUPPORT)) { $spa->SetMimeSupport(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { return false; } } if (self::$decoder->getElementStartTag(SYNC_MIMETRUNCATION)) { $spa->SetMimeTruncation(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { return false; } } if (self::$decoder->getElementStartTag(SYNC_CONFLICT)) { $spa->SetConflict(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { return false; } } while (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_BODYPREFERENCE)) { if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TYPE)) { $bptype = self::$decoder->getElementContent(); $spa->BodyPreference($bptype); if (!self::$decoder->getElementEndTag()) { return false; } } if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TRUNCATIONSIZE)) { $spa->BodyPreference($bptype)->SetTruncationSize(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { return false; } } if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_ALLORNONE)) { $spa->BodyPreference($bptype)->SetAllOrNone(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { return false; } } if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_PREVIEW)) { $spa->BodyPreference($bptype)->SetPreview(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { return false; } } if (!self::$decoder->getElementEndTag()) { return false; } } $e = self::$decoder->peek(); if ($e[EN_TYPE] == EN_TYPE_ENDTAG) { self::$decoder->getElementEndTag(); break; } } } // limit items to be synchronized to the mobiles if configured if (defined('SYNC_FILTERTIME_MAX') && SYNC_FILTERTIME_MAX > SYNC_FILTERTYPE_ALL && (!$spa->HasFilterType() || $spa->GetFilterType() == SYNC_FILTERTYPE_ALL || $spa->GetFilterType() > SYNC_FILTERTIME_MAX)) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("SYNC_FILTERTIME_MAX defined. Filter set to value: %s", SYNC_FILTERTIME_MAX)); $spa->SetFilterType(SYNC_FILTERTIME_MAX); } // Check if the hierarchycache is available. If not, trigger a HierarchySync if (self::$deviceManager->IsHierarchySyncRequired()) { $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED; ZLog::Write(LOGLEVEL_DEBUG, "HierarchyCache is also not available. Triggering HierarchySync to device"); } if (($el = self::$decoder->getElementStartTag(SYNC_PERFORM)) && $el[EN_FLAGS] & EN_FLAGS_CONTENT) { // We can not proceed here as the content class is unknown if ($status != SYNC_STATUS_SUCCESS) { ZLog::Write(LOGLEVEL_WARN, "Ignoring all incoming actions as global status indicates problem."); $wbxmlproblem = true; break; } $performaction = true; // unset the importer $this->importer = false; $nchanges = 0; while (1) { // ADD, MODIFY, REMOVE or FETCH $element = self::$decoder->getElement(); if ($element[EN_TYPE] != EN_TYPE_STARTTAG) { self::$decoder->ungetElement($element); break; } if ($status == SYNC_STATUS_SUCCESS) { $nchanges++; } // Foldertype sent when synching SMS if (self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) { $foldertype = self::$decoder->getElementContent(); ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): incoming data with foldertype '%s'", $foldertype)); if (!self::$decoder->getElementEndTag()) { return false; } } else { $foldertype = false; } $serverid = false; if (self::$decoder->getElementStartTag(SYNC_SERVERENTRYID)) { if (($serverid = self::$decoder->getElementContent()) !== false) { if (!self::$decoder->getElementEndTag()) { // end serverid return false; } } } if (self::$decoder->getElementStartTag(SYNC_CLIENTENTRYID)) { $clientid = self::$decoder->getElementContent(); if (!self::$decoder->getElementEndTag()) { // end clientid return false; } } else { $clientid = false; } // Get the SyncMessage if sent if (self::$decoder->getElementStartTag(SYNC_DATA)) { $message = ZPush::getSyncObjectFromFolderClass($spa->GetContentClass()); $message->Decode(self::$decoder); // set Ghosted fields $message->emptySupported(self::$deviceManager->GetSupportedFields($spa->GetFolderId())); if (!self::$decoder->getElementEndTag()) { // end applicationdata return false; } } else { $message = false; } switch ($element[EN_TAG]) { case SYNC_FETCH: array_push($actiondata["fetchids"], $serverid); break; default: // get the importer if ($this->importer == false) { $status = $this->getImporter($sc, $spa, $actiondata); } if ($status == SYNC_STATUS_SUCCESS) { $this->importMessage($spa, $actiondata, $element[EN_TAG], $message, $clientid, $serverid, $foldertype, $nchanges); } else { ZLog::Write(LOGLEVEL_WARN, "Ignored incoming change, global status indicates problem."); } break; } if ($actiondata["fetchids"]) { self::$topCollector->AnnounceInformation(sprintf("Fetching %d", $nchanges)); } else { self::$topCollector->AnnounceInformation(sprintf("Incoming %d", $nchanges)); } if (!self::$decoder->getElementEndTag()) { // end add/change/delete/move return false; } } if ($status == SYNC_STATUS_SUCCESS && $this->importer !== false) { ZLog::Write(LOGLEVEL_INFO, sprintf("Sync->Handle(): Processed %d incoming changes", $nchanges)); if (!$actiondata["fetchids"]) { self::$topCollector->AnnounceInformation(sprintf("%d incoming", $nchanges), true); } try { // Save the updated state, which is used for the exporter later $sc->AddParameter($spa, "state", $this->importer->GetState()); } catch (StatusException $stex) { $status = $stex->getCode(); } } if (!self::$decoder->getElementEndTag()) { // end PERFORM return false; } } // save the failsave state if (!empty($actiondata["statusids"])) { unset($actiondata["failstate"]); $actiondata["failedsyncstate"] = $sc->GetParameter($spa, "state"); self::$deviceManager->GetStateManager()->SetSyncFailState($actiondata); } // save actiondata $sc->AddParameter($spa, "actiondata", $actiondata); if (!self::$decoder->getElementEndTag()) { // end collection return false; } // AS14 does not send GetChanges anymore. We should do it if there were no incoming changes if (!isset($performaction) && !$sc->GetParameter($spa, "getchanges") && $spa->HasSyncKey()) { $sc->AddParameter($spa, "getchanges", true); } } // END FOLDER if (!$wbxmlproblem && !self::$decoder->getElementEndTag()) { // end collections return false; } } // end FOLDERS if (self::$decoder->getElementStartTag(SYNC_HEARTBEATINTERVAL)) { $hbinterval = self::$decoder->getElementContent(); if (!self::$decoder->getElementEndTag()) { // SYNC_HEARTBEATINTERVAL return false; } } if (self::$decoder->getElementStartTag(SYNC_WAIT)) { $wait = self::$decoder->getElementContent(); if (!self::$decoder->getElementEndTag()) { // SYNC_WAIT return false; } // internally the heartbeat interval and the wait time are the same // heartbeat is in seconds, wait in minutes $hbinterval = $wait * 60; } if (self::$decoder->getElementStartTag(SYNC_WINDOWSIZE)) { $sc->SetGlobalWindowSize(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { // SYNC_WINDOWSIZE return false; } } if (self::$decoder->getElementStartTag(SYNC_PARTIAL)) { $partial = true; } else { $partial = false; } if (!$wbxmlproblem && !self::$decoder->getElementEndTag()) { // end sync return false; } } else { $emptysync = true; } // END SYNCHRONIZE // check heartbeat/wait time if (isset($hbinterval)) { if ($hbinterval < 60 || $hbinterval > 3540) { $status = SYNC_STATUS_INVALIDWAITORHBVALUE; ZLog::Write(LOGLEVEL_WARN, sprintf("HandleSync(): Invalid heartbeat or wait value '%s'", $hbinterval)); } } // Partial & Empty Syncs need saved data to proceed with synchronization if ($status == SYNC_STATUS_SUCCESS && ($emptysync === true || $partial === true)) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Partial or Empty sync requested. Retrieving data of synchronized folders.")); // Load all collections - do not overwrite existing (received!), load states and check permissions try { $sc->LoadAllCollections(false, true, true); } catch (StateNotFoundException $snfex) { $status = SYNC_STATUS_INVALIDSYNCKEY; self::$topCollector->AnnounceInformation("StateNotFoundException", true); } catch (StatusException $stex) { $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED; self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), true); } // update a few values foreach ($sc as $folderid => $spa) { // manually set getchanges parameter for this collection $sc->AddParameter($spa, "getchanges", true); // set new global windowsize without marking the SPA as changed if ($sc->GetGlobalWindowSize()) { $spa->SetWindowSize($sc->GetGlobalWindowSize(), false); } // announce WindowSize to DeviceManager self::$deviceManager->SetWindowSize($folderid, $spa->GetWindowSize()); } if (!$sc->HasCollections()) { $status = SYNC_STATUS_SYNCREQUESTINCOMPLETE; } } // HEARTBEAT & Empty sync if ($status == SYNC_STATUS_SUCCESS && (isset($hbinterval) || $emptysync == true)) { $interval = defined('PING_INTERVAL') && PING_INTERVAL > 0 ? PING_INTERVAL : 30; if (isset($hbinterval)) { $sc->SetLifetime($hbinterval); } // states are lazy loaded - we have to make sure that they are there! $loadstatus = SYNC_STATUS_SUCCESS; foreach ($sc as $folderid => $spa) { // some androids do heartbeat on the OUTBOX folder, with weird results - ZP-362 // we do not load the state so we will never get relevant changes on the OUTBOX folder if (self::$deviceManager->GetFolderTypeFromCacheById($folderid) == SYNC_FOLDER_TYPE_OUTBOX) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Heartbeat on Outbox folder not allowed")); continue; } $fad = array(); // if loading the states fails, we do not enter heartbeat, but we keep $status on SYNC_STATUS_SUCCESS // so when the changes are exported the correct folder gets an SYNC_STATUS_INVALIDSYNCKEY if ($loadstatus == SYNC_STATUS_SUCCESS) { $loadstatus = $this->loadStates($sc, $spa, $fad); } } if ($loadstatus == SYNC_STATUS_SUCCESS) { $foundchanges = false; try { // always check for changes ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Entering Heartbeat mode")); $foundchanges = $sc->CheckForChanges($sc->GetLifetime(), $interval); } catch (StatusException $stex) { if ($stex->getCode() == SyncCollections::OBSOLETE_CONNECTION) { $status = SYNC_COMMONSTATUS_SYNCSTATEVERSIONINVALID; } else { $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED; self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), true); } } // in case there are no changes and no other request has synchronized while we waited, we can reply with an empty response if (!$foundchanges && $status == SYNC_STATUS_SUCCESS) { // if there were changes to the SPA or CPOs we need to save this before we terminate // only save if the state was not modified by some other request, if so, return state invalid status foreach ($sc as $folderid => $spa) { if (self::$deviceManager->CheckHearbeatStateIntegrity($spa->GetFolderId(), $spa->GetUuid(), $spa->GetUuidCounter())) { $status = SYNC_COMMONSTATUS_SYNCSTATEVERSIONINVALID; } else { $sc->SaveCollection($spa); } } if ($status == SYNC_STATUS_SUCCESS) { ZLog::Write(LOGLEVEL_DEBUG, "No changes found and no other process changed states. Replying with empty response and closing connection."); self::$specialHeaders = array(); self::$specialHeaders[] = "Connection: close"; return true; } } if ($foundchanges) { foreach ($sc->GetChangedFolderIds() as $folderid => $changecount) { // check if there were other sync requests for a folder during the heartbeat $spa = $sc->GetCollection($folderid); if ($changecount > 0 && $sc->WaitedForChanges() && self::$deviceManager->CheckHearbeatStateIntegrity($spa->GetFolderId(), $spa->GetUuid(), $spa->GetUuidCounter())) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): heartbeat: found %d changes in '%s' which was already synchronized. Heartbeat aborted!", $changecount, $folderid)); $status = SYNC_COMMONSTATUS_SYNCSTATEVERSIONINVALID; } else { ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): heartbeat: found %d changes in '%s'", $changecount, $folderid)); } } } } } ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Start Output")); // Start the output self::$encoder->startWBXML(); self::$encoder->startTag(SYNC_SYNCHRONIZE); // global status // SYNC_COMMONSTATUS_* start with values from 101 if ($status != SYNC_COMMONSTATUS_SUCCESS && $status > 100) { self::$encoder->startTag(SYNC_STATUS); self::$encoder->content($status); self::$encoder->endTag(); } else { self::$encoder->startTag(SYNC_FOLDERS); foreach ($sc as $folderid => $spa) { // get actiondata $actiondata = $sc->GetParameter($spa, "actiondata"); if ($status == SYNC_STATUS_SUCCESS && (!$spa->GetContentClass() || !$spa->GetFolderId())) { ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): no content class or folderid found for collection.")); continue; } if (!$sc->GetParameter($spa, "requested")) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): partial sync for folder class '%s' with id '%s'", $spa->GetContentClass(), $spa->GetFolderId())); } // initialize exporter to get changecount $changecount = false; if (isset($exporter)) { unset($exporter); } // TODO we could check against $sc->GetChangedFolderIds() on heartbeat so we do not need to configure all exporter again if ($status == SYNC_STATUS_SUCCESS && ($sc->GetParameter($spa, "getchanges") || !$spa->HasSyncKey())) { //make sure the states are loaded $status = $this->loadStates($sc, $spa, $actiondata); if ($status == SYNC_STATUS_SUCCESS) { try { // if this is an additional folder the backend has to be setup correctly if (!self::$backend->Setup(ZPush::GetAdditionalSyncFolderStore($spa->GetFolderId()))) { throw new StatusException(sprintf("HandleSync() could not Setup() the backend for folder id '%s'", $spa->GetFolderId()), SYNC_STATUS_FOLDERHIERARCHYCHANGED); } // Use the state from the importer, as changes may have already happened $exporter = self::$backend->GetExporter($spa->GetFolderId()); if ($exporter === false) { throw new StatusException(sprintf("HandleSync() could not get an exporter for folder id '%s'", $spa->GetFolderId()), SYNC_STATUS_FOLDERHIERARCHYCHANGED); } } catch (StatusException $stex) { $status = $stex->getCode(); } try { // Stream the messages directly to the PDA $streamimporter = new ImportChangesStream(self::$encoder, ZPush::getSyncObjectFromFolderClass($spa->GetContentClass())); if ($exporter !== false) { $exporter->Config($sc->GetParameter($spa, "state")); $exporter->ConfigContentParameters($spa->GetCPO()); $exporter->InitializeExporter($streamimporter); $changecount = $exporter->GetChangeCount(); } } catch (StatusException $stex) { if ($stex->getCode() === SYNC_FSSTATUS_CODEUNKNOWN && $spa->HasSyncKey()) { $status = SYNC_STATUS_INVALIDSYNCKEY; } else { $status = $stex->getCode(); } } if (!$spa->HasSyncKey()) { self::$topCollector->AnnounceInformation(sprintf("Exporter registered. %d objects queued.", $changecount), true); // update folder status as initialized $spa->SetFolderSyncTotal($changecount); $spa->SetFolderSyncRemaining($changecount); if ($changecount > 0) { self::$deviceManager->SetFolderSyncStatus($folderid, DeviceManager::FLD_SYNC_INITIALIZED); } } else { if ($status != SYNC_STATUS_SUCCESS) { self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), true); } } } } if (isset($hbinterval) && $changecount == 0 && $status == SYNC_STATUS_SUCCESS) { ZLog::Write(LOGLEVEL_DEBUG, "No changes found for heartbeat folder. Omitting empty output."); continue; } // Get a new sync key to output to the client if any changes have been send or will are available if (!empty($actiondata["modifyids"]) || !empty($actiondata["clientids"]) || !empty($actiondata["removeids"]) || $changecount > 0 || !$spa->HasSyncKey() && $status == SYNC_STATUS_SUCCESS) { $spa->SetNewSyncKey(self::$deviceManager->GetStateManager()->GetNewSyncKey($spa->GetSyncKey())); } self::$encoder->startTag(SYNC_FOLDER); if ($spa->HasContentClass()) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("Folder type: %s", $spa->GetContentClass())); // AS 12.0 devices require content class if (Request::GetProtocolVersion() < 12.1) { self::$encoder->startTag(SYNC_FOLDERTYPE); self::$encoder->content($spa->GetContentClass()); self::$encoder->endTag(); } } self::$encoder->startTag(SYNC_SYNCKEY); if ($status == SYNC_STATUS_SUCCESS && $spa->HasNewSyncKey()) { self::$encoder->content($spa->GetNewSyncKey()); } else { self::$encoder->content($spa->GetSyncKey()); } self::$encoder->endTag(); self::$encoder->startTag(SYNC_FOLDERID); self::$encoder->content($spa->GetFolderId()); self::$encoder->endTag(); self::$encoder->startTag(SYNC_STATUS); self::$encoder->content($status); self::$encoder->endTag(); // announce failing status to the process loop detection if ($status !== SYNC_STATUS_SUCCESS) { self::$deviceManager->AnnounceProcessStatus($spa->GetFolderId(), $status); } // Output IDs and status for incoming items & requests if ($status == SYNC_STATUS_SUCCESS && (!empty($actiondata["clientids"]) || !empty($actiondata["modifyids"]) || !empty($actiondata["removeids"]) || !empty($actiondata["fetchids"]))) { self::$encoder->startTag(SYNC_REPLIES); // output result of all new incoming items foreach ($actiondata["clientids"] as $clientid => $serverid) { self::$encoder->startTag(SYNC_ADD); self::$encoder->startTag(SYNC_CLIENTENTRYID); self::$encoder->content($clientid); self::$encoder->endTag(); if ($serverid) { self::$encoder->startTag(SYNC_SERVERENTRYID); self::$encoder->content($serverid); self::$encoder->endTag(); } self::$encoder->startTag(SYNC_STATUS); self::$encoder->content(isset($actiondata["statusids"][$clientid]) ? $actiondata["statusids"][$clientid] : SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR); self::$encoder->endTag(); self::$encoder->endTag(); } // loop through modify operations which were not a success, send status foreach ($actiondata["modifyids"] as $serverid) { if (isset($actiondata["statusids"][$serverid]) && $actiondata["statusids"][$serverid] !== SYNC_STATUS_SUCCESS) { self::$encoder->startTag(SYNC_MODIFY); self::$encoder->startTag(SYNC_SERVERENTRYID); self::$encoder->content($serverid); self::$encoder->endTag(); self::$encoder->startTag(SYNC_STATUS); self::$encoder->content($actiondata["statusids"][$serverid]); self::$encoder->endTag(); self::$encoder->endTag(); } } // loop through remove operations which were not a success, send status foreach ($actiondata["removeids"] as $serverid) { if (isset($actiondata["statusids"][$serverid]) && $actiondata["statusids"][$serverid] !== SYNC_STATUS_SUCCESS) { self::$encoder->startTag(SYNC_REMOVE); self::$encoder->startTag(SYNC_SERVERENTRYID); self::$encoder->content($serverid); self::$encoder->endTag(); self::$encoder->startTag(SYNC_STATUS); self::$encoder->content($actiondata["statusids"][$serverid]); self::$encoder->endTag(); self::$encoder->endTag(); } } if (!empty($actiondata["fetchids"])) { self::$topCollector->AnnounceInformation(sprintf("Fetching %d objects ", count($actiondata["fetchids"])), true); } foreach ($actiondata["fetchids"] as $id) { $data = false; try { $fetchstatus = SYNC_STATUS_SUCCESS; // if this is an additional folder the backend has to be setup correctly if (!self::$backend->Setup(ZPush::GetAdditionalSyncFolderStore($spa->GetFolderId()))) { throw new StatusException(sprintf("HandleSync(): could not Setup() the backend to fetch in folder id '%s'", $spa->GetFolderId()), SYNC_STATUS_OBJECTNOTFOUND); } $data = self::$backend->Fetch($spa->GetFolderId(), $id, $spa->GetCPO()); // check if the message is broken if (ZPush::GetDeviceManager(false) && ZPush::GetDeviceManager()->DoNotStreamMessage($id, $data)) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): message not to be streamed as requested by DeviceManager, id = %s", $id)); $fetchstatus = SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR; } } catch (StatusException $stex) { $fetchstatus = $stex->getCode(); } self::$encoder->startTag(SYNC_FETCH); self::$encoder->startTag(SYNC_SERVERENTRYID); self::$encoder->content($id); self::$encoder->endTag(); self::$encoder->startTag(SYNC_STATUS); self::$encoder->content($fetchstatus); self::$encoder->endTag(); if ($data !== false && $status == SYNC_STATUS_SUCCESS) { self::$encoder->startTag(SYNC_DATA); $data->Encode(self::$encoder); self::$encoder->endTag(); } else { ZLog::Write(LOGLEVEL_WARN, sprintf("Unable to Fetch '%s'", $id)); } self::$encoder->endTag(); } self::$encoder->endTag(); } if ($sc->GetParameter($spa, "getchanges") && $spa->HasFolderId() && $spa->HasContentClass() && $spa->HasSyncKey()) { $windowSize = self::$deviceManager->GetWindowSize($spa->GetFolderId(), $spa->GetContentClass(), $spa->GetUuid(), $spa->GetUuidCounter(), $changecount); if ($changecount > $windowSize) { self::$encoder->startTag(SYNC_MOREAVAILABLE, false, true); } } // Stream outgoing changes if ($status == SYNC_STATUS_SUCCESS && $sc->GetParameter($spa, "getchanges") == true && $windowSize > 0) { self::$topCollector->AnnounceInformation(sprintf("Streaming data of %d objects", $changecount > $windowSize ? $windowSize : $changecount)); // Output message changes per folder self::$encoder->startTag(SYNC_PERFORM); $n = 0; while (1) { try { $progress = $exporter->Synchronize(); if (!is_array($progress)) { break; } $n++; } catch (SyncObjectBrokenException $mbe) { $brokenSO = $mbe->GetSyncObject(); if (!$brokenSO) { ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): Catched SyncObjectBrokenException but broken SyncObject not available. This should be fixed in the backend.")); } else { if (!isset($brokenSO->id)) { $brokenSO->id = "Unknown ID"; ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): Catched SyncObjectBrokenException but no ID of object set. This should be fixed in the backend.")); } self::$deviceManager->AnnounceIgnoredMessage($spa->GetFolderId(), $brokenSO->id, $brokenSO); } } catch (StatusException $stex) { $status = $stex->getCode(); // during export we found out that the states should be thrown away (ZP-623) if ($status == SYNC_STATUS_INVALIDSYNCKEY) { self::$deviceManager->ForceFolderResync($spa->GetFolderId()); break; } } if ($n >= $windowSize) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Exported maxItems of messages: %d / %d", $n, $changecount)); break; } } // $progress is not an array when exporting the last message // so we get the number to display from the streamimporter if (isset($streamimporter)) { $n = $streamimporter->GetImportedMessages(); } self::$encoder->endTag(); self::$topCollector->AnnounceInformation(sprintf("Outgoing %d objects%s", $n, $n >= $windowSize ? " of " . $changecount : ""), true); // update folder status, if there is something set if ($spa->GetFolderSyncRemaining() && $changecount > 0) { $spa->SetFolderSyncRemaining($changecount); } // changecount is initialized with 'false', so 0 means no changes! if ($changecount === 0 || $changecount !== false && $changecount <= $windowSize) { self::$deviceManager->SetFolderSyncStatus($folderid, DeviceManager::FLD_SYNC_COMPLETED); } else { self::$deviceManager->SetFolderSyncStatus($folderid, DeviceManager::FLD_SYNC_INPROGRESS); } } self::$encoder->endTag(); // Save the sync state for the next time if ($spa->HasNewSyncKey()) { self::$topCollector->AnnounceInformation("Saving state"); try { if (isset($exporter) && $exporter) { $state = $exporter->GetState(); } else { if ($sc->GetParameter($spa, "state") !== null) { $state = $sc->GetParameter($spa, "state"); } else { if (!$spa->HasSyncKey()) { $state = ""; } } } } catch (StatusException $stex) { $status = $stex->getCode(); } if (isset($state) && $status == SYNC_STATUS_SUCCESS) { self::$deviceManager->GetStateManager()->SetSyncState($spa->GetNewSyncKey(), $state, $spa->GetFolderId()); } else { ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): error saving '%s' - no state information available", $spa->GetNewSyncKey())); } } // save SyncParameters if ($status == SYNC_STATUS_SUCCESS && empty($actiondata["fetchids"])) { $sc->SaveCollection($spa); } // reset status for the next folder $status = SYNC_STATUS_SUCCESS; } // END foreach collection self::$encoder->endTag(); //SYNC_FOLDERS } self::$encoder->endTag(); //SYNC_SYNCHRONIZE return true; }
/** * Gets the StateManager from the DeviceManager * if it's not available * * @access private * @return */ private function loadStateManager() { if (!isset($this->stateManager)) { $this->stateManager = ZPush::GetDeviceManager()->GetStateManager(); } }
/** * Imports a single message * * @param array $props * @param long $flags * @param object $retmapimessage * * @access public * @return long */ public function ImportMessageChange($props, $flags, &$retmapimessage) { $sourcekey = $props[PR_SOURCE_KEY]; $parentsourcekey = $props[PR_PARENT_SOURCE_KEY]; $entryid = mapi_msgstore_entryidfromsourcekey($this->store, $parentsourcekey, $sourcekey); if (!$entryid) { return SYNC_E_IGNORE; } $mapimessage = mapi_msgstore_openentry($this->store, $entryid); try { $message = $this->mapiprovider->GetMessage($mapimessage, $this->contentparameters); } catch (SyncObjectBrokenException $mbe) { $brokenSO = $mbe->GetSyncObject(); if (!$brokenSO) { ZLog::Write(LOGLEVEL_ERROR, sprintf("PHPWrapper->ImportMessageChange(): Catched SyncObjectBrokenException but broken SyncObject available")); } else { if (!isset($brokenSO->id)) { $brokenSO->id = "Unknown ID"; ZLog::Write(LOGLEVEL_ERROR, sprintf("PHPWrapper->ImportMessageChange(): Catched SyncObjectBrokenException but no ID of object set")); } ZPush::GetDeviceManager()->AnnounceIgnoredMessage(false, $brokenSO->id, $brokenSO); } // tell MAPI to ignore the message return SYNC_E_IGNORE; } // substitute the MAPI SYNC_NEW_MESSAGE flag by a z-push proprietary flag if ($flags == SYNC_NEW_MESSAGE) { $message->flags = SYNC_NEWMESSAGE; } else { $message->flags = $flags; } $this->importer->ImportMessageChange(bin2hex($sourcekey), $message); // Tell MAPI it doesn't need to do anything itself, as we've done all the work already. return SYNC_E_IGNORE; }
/** * Imports a change on a folder * * @param object $folder SyncFolder * * @access public * @return string id of the folder */ public function ImportFolderChange($folder) { // checks if the next message may cause a loop or is broken if (ZPush::GetDeviceManager(false) && ZPush::GetDeviceManager()->DoNotStreamMessage($folder->serverid, $folder)) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("ImportChangesStream->ImportFolderChange('%s'): folder ignored as requested by DeviceManager.", $folder->serverid)); return true; } // send a modify flag if the folder is already known on the device if (isset($folder->flags) && $folder->flags === SYNC_NEWMESSAGE) { $this->encoder->startTag(SYNC_FOLDERHIERARCHY_ADD); } else { $this->encoder->startTag(SYNC_FOLDERHIERARCHY_UPDATE); } $folder->Encode($this->encoder); $this->encoder->endTag(); return true; }
/** * Synchronizes a folder to the output stream. Changes for this folders are expected. * * @param SyncCollections $sc * @param SyncParameters $spa * @param IExportChanges $exporter Fully configured exporter for this folder * @param int $changecount Amount of changes expected * @param ImportChangesStream $streamimporter Output stream * @param int $status current status of the folder processing * @param string $newFolderStat the new folder stat to be set if everything was exported * * @throws StatusException * @return int sync status code */ private function syncFolder($sc, $spa, $exporter, $changecount, $streamimporter, $status, $newFolderStat) { $actiondata = $sc->GetParameter($spa, "actiondata"); // send the WBXML start tags (if not happened already) $this->sendFolderStartTag(); self::$encoder->startTag(SYNC_FOLDER); if ($spa->HasContentClass()) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("Folder type: %s", $spa->GetContentClass())); // AS 12.0 devices require content class if (Request::GetProtocolVersion() < 12.1) { self::$encoder->startTag(SYNC_FOLDERTYPE); self::$encoder->content($spa->GetContentClass()); self::$encoder->endTag(); } } self::$encoder->startTag(SYNC_SYNCKEY); if ($status == SYNC_STATUS_SUCCESS && $spa->HasNewSyncKey()) { self::$encoder->content($spa->GetNewSyncKey()); } else { self::$encoder->content($spa->GetSyncKey()); } self::$encoder->endTag(); self::$encoder->startTag(SYNC_FOLDERID); self::$encoder->content($spa->GetFolderId()); self::$encoder->endTag(); self::$encoder->startTag(SYNC_STATUS); self::$encoder->content($status); self::$encoder->endTag(); // announce failing status to the process loop detection if ($status !== SYNC_STATUS_SUCCESS) { self::$deviceManager->AnnounceProcessStatus($spa->GetFolderId(), $status); } // Output IDs and status for incoming items & requests if ($status == SYNC_STATUS_SUCCESS && (!empty($actiondata["clientids"]) || !empty($actiondata["modifyids"]) || !empty($actiondata["removeids"]) || !empty($actiondata["fetchids"]))) { self::$encoder->startTag(SYNC_REPLIES); // output result of all new incoming items foreach ($actiondata["clientids"] as $clientid => $serverid) { self::$encoder->startTag(SYNC_ADD); self::$encoder->startTag(SYNC_CLIENTENTRYID); self::$encoder->content($clientid); self::$encoder->endTag(); if ($serverid) { self::$encoder->startTag(SYNC_SERVERENTRYID); self::$encoder->content($serverid); self::$encoder->endTag(); } self::$encoder->startTag(SYNC_STATUS); self::$encoder->content(isset($actiondata["statusids"][$clientid]) ? $actiondata["statusids"][$clientid] : SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR); self::$encoder->endTag(); self::$encoder->endTag(); } // loop through modify operations which were not a success, send status foreach ($actiondata["modifyids"] as $serverid) { if (isset($actiondata["statusids"][$serverid]) && $actiondata["statusids"][$serverid] !== SYNC_STATUS_SUCCESS) { self::$encoder->startTag(SYNC_MODIFY); self::$encoder->startTag(SYNC_SERVERENTRYID); self::$encoder->content($serverid); self::$encoder->endTag(); self::$encoder->startTag(SYNC_STATUS); self::$encoder->content($actiondata["statusids"][$serverid]); self::$encoder->endTag(); self::$encoder->endTag(); } } // loop through remove operations which were not a success, send status foreach ($actiondata["removeids"] as $serverid) { if (isset($actiondata["statusids"][$serverid]) && $actiondata["statusids"][$serverid] !== SYNC_STATUS_SUCCESS) { self::$encoder->startTag(SYNC_REMOVE); self::$encoder->startTag(SYNC_SERVERENTRYID); self::$encoder->content($serverid); self::$encoder->endTag(); self::$encoder->startTag(SYNC_STATUS); self::$encoder->content($actiondata["statusids"][$serverid]); self::$encoder->endTag(); self::$encoder->endTag(); } } if (!empty($actiondata["fetchids"])) { self::$topCollector->AnnounceInformation(sprintf("Fetching %d objects ", count($actiondata["fetchids"])), $this->singleFolder); $this->saveMultiFolderInfo("fetching", count($actiondata["fetchids"])); } foreach ($actiondata["fetchids"] as $id) { $data = false; try { $fetchstatus = SYNC_STATUS_SUCCESS; // if this is an additional folder the backend has to be setup correctly if (!self::$backend->Setup(ZPush::GetAdditionalSyncFolderStore($spa->GetBackendFolderId()))) { throw new StatusException(sprintf("HandleSync(): could not Setup() the backend to fetch in folder id %s/%s", $spa->GetFolderId(), $spa->GetBackendFolderId()), SYNC_STATUS_OBJECTNOTFOUND); } $data = self::$backend->Fetch($spa->GetBackendFolderId(), $id, $spa->GetCPO()); // check if the message is broken if (ZPush::GetDeviceManager(false) && ZPush::GetDeviceManager()->DoNotStreamMessage($id, $data)) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): message not to be streamed as requested by DeviceManager, id = %s", $id)); $fetchstatus = SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR; } } catch (StatusException $stex) { $fetchstatus = $stex->getCode(); } self::$encoder->startTag(SYNC_FETCH); self::$encoder->startTag(SYNC_SERVERENTRYID); self::$encoder->content($id); self::$encoder->endTag(); self::$encoder->startTag(SYNC_STATUS); self::$encoder->content($fetchstatus); self::$encoder->endTag(); if ($data !== false && $status == SYNC_STATUS_SUCCESS) { self::$encoder->startTag(SYNC_DATA); $data->Encode(self::$encoder); self::$encoder->endTag(); } else { ZLog::Write(LOGLEVEL_WARN, sprintf("Unable to Fetch '%s'", $id)); } self::$encoder->endTag(); } self::$encoder->endTag(); } if ($sc->GetParameter($spa, "getchanges") && $spa->HasFolderId() && $spa->HasContentClass() && $spa->HasSyncKey()) { $moreAvailableSent = false; $windowSize = self::$deviceManager->GetWindowSize($spa->GetFolderId(), $spa->GetUuid(), $spa->GetUuidCounter(), $changecount); // limit windowSize to the max available limit of the global window size left $globallyAvailable = $sc->GetGlobalWindowSize() - $this->globallyExportedItems; if ($changecount > $globallyAvailable && $windowSize > $globallyAvailable) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Limit window size to %d as the global window size limit will be reached", $globallyAvailable)); $windowSize = $globallyAvailable; } // send <MoreAvailable/> if there are more changes than fit in the folder windowsize // or there is a move state (another sync should be done afterwards) if ($changecount > $windowSize || $spa->GetMoveState() !== false) { self::$encoder->startTag(SYNC_MOREAVAILABLE, false, true); $moreAvailableSent = true; $spa->DelFolderStat(); } } // Stream outgoing changes if ($status == SYNC_STATUS_SUCCESS && $sc->GetParameter($spa, "getchanges") == true && $windowSize > 0 && !!$exporter) { self::$topCollector->AnnounceInformation(sprintf("Streaming data of %d objects", $changecount > $windowSize ? $windowSize : $changecount)); // Output message changes per folder self::$encoder->startTag(SYNC_PERFORM); $n = 0; WBXMLDecoder::ResetInWhile("syncSynchronize"); while (WBXMLDecoder::InWhile("syncSynchronize")) { try { $progress = $exporter->Synchronize(); if (!is_array($progress)) { break; } $n++; if ($n % 10 == 0) { self::$topCollector->AnnounceInformation(sprintf("Streamed data of %d objects out of %d", $n, $changecount > $windowSize ? $windowSize : $changecount)); } } catch (SyncObjectBrokenException $mbe) { $brokenSO = $mbe->GetSyncObject(); if (!$brokenSO) { ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): Catched SyncObjectBrokenException but broken SyncObject not available. This should be fixed in the backend.")); } else { if (!isset($brokenSO->id)) { $brokenSO->id = "Unknown ID"; ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): Catched SyncObjectBrokenException but no ID of object set. This should be fixed in the backend.")); } self::$deviceManager->AnnounceIgnoredMessage($spa->GetFolderId(), $brokenSO->id, $brokenSO); } } catch (StatusException $stex) { $status = $stex->getCode(); // during export we found out that the states should be thrown away (ZP-623) if ($status == SYNC_STATUS_INVALIDSYNCKEY) { self::$deviceManager->ForceFolderResync($spa->GetFolderId()); break; } } if ($n >= $windowSize || Request::IsRequestTimeoutReached()) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Exported maxItems of messages: %d / %d", $n, $changecount)); break; } } // $progress is not an array when exporting the last message // so we get the number to display from the streamimporter if it's available if (!!$streamimporter) { $n = $streamimporter->GetImportedMessages(); } self::$encoder->endTag(); // log the request timeout if (Request::IsRequestTimeoutReached()) { ZLog::Write(LOGLEVEL_DEBUG, "HandleSync(): Stopping export as maximum request timeout is almost reached!"); // Send a <MoreAvailable/> tag if we reached the request timeout, there are more changes and a moreavailable was not already send if (!$moreAvailableSent && $n > $windowSize) { self::$encoder->startTag(SYNC_MOREAVAILABLE, false, true); $spa->DelFolderStat(); $moreAvailableSent = true; } } self::$topCollector->AnnounceInformation(sprintf("Outgoing %d objects%s", $n, $n >= $windowSize ? " of " . $changecount : ""), $this->singleFolder); $this->saveMultiFolderInfo("outgoing", $n); $this->saveMultiFolderInfo("queued", $changecount); $this->globallyExportedItems += $n; // update folder status, if there is something set if ($spa->GetFolderSyncRemaining() && $changecount > 0) { $spa->SetFolderSyncRemaining($changecount); } // changecount is initialized with 'false', so 0 means no changes! if ($changecount === 0 || $changecount !== false && $changecount <= $windowSize) { self::$deviceManager->SetFolderSyncStatus($spa->GetFolderId(), DeviceManager::FLD_SYNC_COMPLETED); // we should update the folderstat, but we recheck to see if it changed. If so, it's not updated to force another sync $newFolderStatAfterExport = self::$backend->GetFolderStat(ZPush::GetAdditionalSyncFolderStore($spa->GetBackendFolderId()), $spa->GetBackendFolderId()); if ($newFolderStat === $newFolderStatAfterExport) { $this->setFolderStat($spa, $newFolderStat); } else { ZLog::Write(LOGLEVEL_DEBUG, "Sync() Folderstat differs after export, force another exporter run."); } } else { self::$deviceManager->SetFolderSyncStatus($spa->GetFolderId(), DeviceManager::FLD_SYNC_INPROGRESS); } } self::$encoder->endTag(); // Save the sync state for the next time if ($spa->HasNewSyncKey()) { self::$topCollector->AnnounceInformation("Saving state"); try { if (isset($exporter) && $exporter) { $state = $exporter->GetState(); // update the move state (it should be gone now) list($moveState, ) = $exporter->GetMoveStates(); $spa->SetMoveState($moveState); } else { if ($sc->GetParameter($spa, "state") !== null) { $state = $sc->GetParameter($spa, "state"); } else { if (!$spa->HasSyncKey()) { $state = ""; } } } } catch (StatusException $stex) { $status = $stex->getCode(); } if (isset($state) && $status == SYNC_STATUS_SUCCESS) { self::$deviceManager->GetStateManager()->SetSyncState($spa->GetNewSyncKey(), $state, $spa->GetFolderId()); } else { ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): error saving '%s' - no state information available", $spa->GetNewSyncKey())); } } // save SyncParameters if ($status == SYNC_STATUS_SUCCESS && empty($actiondata["fetchids"])) { $sc->SaveCollection($spa); } return $status; }
/** * Initialize the RequestProcessor * * @access public * @return */ public static function Initialize() { self::$backend = ZPush::GetBackend(); self::$deviceManager = ZPush::GetDeviceManager(); self::$topCollector = ZPush::GetTopCollector(); if (!ZPush::CommandNeedsPlainInput(Request::GetCommandCode())) { self::$decoder = new WBXMLDecoder(Request::GetInputStream()); } self::$encoder = new WBXMLEncoder(Request::GetOutputStream(), Request::GetGETAcceptMultipart()); }
/** * Creates a SyncFolder from MAPI properties. * * @param mixed $folderprops * * @access public * @return SyncFolder */ public function GetFolder($folderprops) { $folder = new SyncFolder(); $storeprops = $this->GetStoreProps(); // For ZCP 7.0.x we need to retrieve more properties explicitly, see ZP-780 if (isset($folderprops[PR_SOURCE_KEY]) && !isset($folderprops[PR_ENTRYID]) && !isset($folderprops[PR_CONTAINER_CLASS])) { $entryid = mapi_msgstore_entryidfromsourcekey($this->store, $folderprops[PR_SOURCE_KEY]); $mapifolder = mapi_msgstore_openentry($this->store, $entryid); $folderprops = mapi_getprops($mapifolder, array(PR_DISPLAY_NAME, PR_PARENT_ENTRYID, PR_ENTRYID, PR_SOURCE_KEY, PR_PARENT_SOURCE_KEY, PR_CONTAINER_CLASS, PR_ATTR_HIDDEN, PR_EXTENDED_FOLDER_FLAGS)); ZLog::Write(LOGLEVEL_DEBUG, "MAPIProvider->GetFolder(): received insuffient of data from ICS. Fetching required data."); } if (!isset($folderprops[PR_DISPLAY_NAME]) || !isset($folderprops[PR_PARENT_ENTRYID]) || !isset($folderprops[PR_SOURCE_KEY]) || !isset($folderprops[PR_ENTRYID]) || !isset($folderprops[PR_PARENT_SOURCE_KEY]) || !isset($storeprops[PR_IPM_SUBTREE_ENTRYID])) { ZLog::Write(LOGLEVEL_ERROR, "MAPIProvider->GetFolder(): invalid folder. Missing properties"); return false; } // ignore hidden folders if (isset($folderprops[PR_ATTR_HIDDEN]) && $folderprops[PR_ATTR_HIDDEN] != false) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->GetFolder(): invalid folder '%s' as it is a hidden folder (PR_ATTR_HIDDEN)", $folderprops[PR_DISPLAY_NAME])); return false; } // ignore certain undesired folders, like "RSS Feeds" if (isset($folderprops[PR_CONTAINER_CLASS]) && $folderprops[PR_CONTAINER_CLASS] == "IPF.Note.OutlookHomepage") { ZLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->GetFolder(): folder '%s' should not be synchronized", $folderprops[PR_DISPLAY_NAME])); return false; } // ignore suggested contacts folder if (isset($folderprops[PR_CONTAINER_CLASS]) && $folderprops[PR_CONTAINER_CLASS] == "IPF.Contact" && isset($folderprops[PR_EXTENDED_FOLDER_FLAGS])) { // the PR_EXTENDED_FOLDER_FLAGS is a binary value which consists of subproperties. 070403000000 indicates a suggested contacts folder $extendedFlags = bin2hex($folderprops[PR_EXTENDED_FOLDER_FLAGS]); if (substr_count($extendedFlags, "070403000000") > 0) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->GetFolder(): folder '%s' should not be synchronized", $folderprops[PR_DISPLAY_NAME])); return false; } } $folder->BackendId = bin2hex($folderprops[PR_SOURCE_KEY]); $folder->serverid = ZPush::GetDeviceManager()->GetFolderIdForBackendId($folder->BackendId, true, DeviceManager::FLD_ORIGIN_USER, $folderprops[PR_DISPLAY_NAME]); if ($folderprops[PR_PARENT_ENTRYID] == $storeprops[PR_IPM_SUBTREE_ENTRYID]) { $folder->parentid = "0"; } else { $folder->parentid = ZPush::GetDeviceManager()->GetFolderIdForBackendId(bin2hex($folderprops[PR_PARENT_SOURCE_KEY])); } $folder->displayname = w2u($folderprops[PR_DISPLAY_NAME]); $folder->type = $this->GetFolderType($folderprops[PR_ENTRYID], isset($folderprops[PR_CONTAINER_CLASS]) ? $folderprops[PR_CONTAINER_CLASS] : false); return $folder; }
/** * Imports a folder change * * @param SyncFolder $folder folder to be changed * * @access public * @return boolean/SyncObject status/object with the ath least the serverid of the folder set */ public function ImportFolderChange($folder) { // if the destinationImporter is set, then this folder should be processed by another importer // instead of being loaded in memory. if (isset($this->destinationImporter)) { // normally the $folder->type is not set, but we need this value to check if the change operation is permitted // e.g. system folders can normally not be changed - set the type from cache and let the destinationImporter decide if (!isset($folder->type) || !$folder->type) { $cacheFolder = $this->GetFolder($folder->serverid); $folder->type = $cacheFolder->type; ZLog::Write(LOGLEVEL_DEBUG, sprintf("ChangesMemoryWrapper->ImportFolderChange(): Set foldertype for folder '%s' from cache as it was not sent: '%s'", $folder->displayname, $folder->type)); } // KOE ZO-42: When Notes folders are updated in Outlook, it tries to update the name (that fails by default, as it's a system folder) // catch this case here and ignore the change if (($folder->type == SYNC_FOLDER_TYPE_NOTE || $folder->type == SYNC_FOLDER_TYPE_USER_NOTE) && ZPush::GetDeviceManager()->IsKoe()) { $retFolder = false; } else { $retFolder = $this->destinationImporter->ImportFolderChange($folder); } // if the operation was sucessfull, update the HierarchyCache if ($retFolder) { // if we get a folder back, we need to update some data in the cache if (isset($retFolder->serverid) && $retFolder->serverid) { // for folder creation, the serverid & backendid are not set and have to be updated if (!isset($folder->serverid) || $folder->serverid == "") { $folder->serverid = $retFolder->serverid; if (isset($retFolder->BackendId) && $retFolder->BackendId) { $folder->BackendId = $retFolder->BackendId; } } // if the parentid changed (folder was moved) this needs to be updated as well if ($retFolder->parentid != $folder->parentid) { $folder->parentid = $retFolder->parentid; } } $this->AddFolder($folder); } return $retFolder; } else { if (isset($folder->serverid)) { // The Zarafa/Kopano HierarchyExporter exports all kinds of changes for folders (e.g. update no. of unread messages in a folder). // These changes are not relevant for the mobiles, as something changes but the relevant displayname and parentid // stay the same. These changes will be dropped and are not sent! $cacheFolder = $this->GetFolder($folder->serverid); if ($folder->equals($this->GetFolder($folder->serverid))) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("ChangesMemoryWrapper->ImportFolderChange(): Change for folder '%s' will not be sent as modification is not relevant.", $folder->displayname)); return false; } // check if the parent ID is known on the device if (!isset($folder->parentid) || $folder->parentid != "0" && !$this->GetFolder($folder->parentid)) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("ChangesMemoryWrapper->ImportFolderChange(): Change for folder '%s' will not be sent as parent folder is not set or not known on mobile.", $folder->displayname)); return false; } // load this change into memory $this->changes[] = array(self::CHANGE, $folder); // HierarchyCache: already add/update the folder so changes are not sent twice (if exported twice) $this->AddFolder($folder); return true; } return false; } }
/** * Verifies Timezone, StateMachine and Backend configuration * * @access public * @return boolean * @trows FatalMisconfigurationException */ public static function CheckAdvancedConfig() { global $specialLogUsers, $additionalFolders; if (!is_array($specialLogUsers)) { throw new FatalMisconfigurationException("The WBXML log users is not an array."); } if (!defined('SYNC_CONTACTS_MAXPICTURESIZE')) { define('SYNC_CONTACTS_MAXPICTURESIZE', 49152); } else { if (!is_int(SYNC_CONTACTS_MAXPICTURESIZE) || SYNC_CONTACTS_MAXPICTURESIZE < 1) { throw new FatalMisconfigurationException("The SYNC_CONTACTS_MAXPICTURESIZE value must be a number higher than 0."); } } if (!defined('USE_PARTIAL_FOLDERSYNC')) { define('USE_PARTIAL_FOLDERSYNC', false); } if (!defined('PING_LOWER_BOUND_LIFETIME')) { define('PING_LOWER_BOUND_LIFETIME', false); } elseif (PING_LOWER_BOUND_LIFETIME !== false && (!is_int(PING_LOWER_BOUND_LIFETIME) || PING_LOWER_BOUND_LIFETIME < 1 || PING_LOWER_BOUND_LIFETIME > 3540)) { throw new FatalMisconfigurationException("The PING_LOWER_BOUND_LIFETIME value must be 'false' or a number between 1 and 3540 inclusively."); } if (!defined('PING_HIGHER_BOUND_LIFETIME')) { define('PING_HIGHER_BOUND_LIFETIME', false); } elseif (PING_HIGHER_BOUND_LIFETIME !== false && (!is_int(PING_HIGHER_BOUND_LIFETIME) || PING_HIGHER_BOUND_LIFETIME < 1 || PING_HIGHER_BOUND_LIFETIME > 3540)) { throw new FatalMisconfigurationException("The PING_HIGHER_BOUND_LIFETIME value must be 'false' or a number between 1 and 3540 inclusively."); } if (PING_HIGHER_BOUND_LIFETIME !== false && PING_LOWER_BOUND_LIFETIME !== false && PING_HIGHER_BOUND_LIFETIME < PING_LOWER_BOUND_LIFETIME) { throw new FatalMisconfigurationException("The PING_HIGHER_BOUND_LIFETIME value must be greater or equal to PING_LOWER_BOUND_LIFETIME."); } // Check KOE flags if (!defined('KOE_CAPABILITY_GAB')) { define('KOE_CAPABILITY_GAB', false); } if (!defined('KOE_CAPABILITY_RECEIVEFLAGS')) { define('KOE_CAPABILITY_RECEIVEFLAGS', false); } if (!defined('KOE_CAPABILITY_SENDFLAGS')) { define('KOE_CAPABILITY_SENDFLAGS', false); } if (!defined('KOE_CAPABILITY_OOF')) { define('KOE_CAPABILITY_OOF', false); } if (!defined('KOE_CAPABILITY_OOFTIMES')) { define('KOE_CAPABILITY_OOFTIMES', false); } if (!defined('KOE_CAPABILITY_NOTES')) { define('KOE_CAPABILITY_NOTES', false); } if (!defined('KOE_CAPABILITY_SHAREDFOLDER')) { define('KOE_CAPABILITY_SHAREDFOLDER', false); } if (!defined('KOE_GAB_FOLDERID')) { define('KOE_GAB_FOLDERID', ''); } if (!defined('KOE_GAB_STORE')) { define('KOE_GAB_STORE', ''); } if (!defined('KOE_GAB_NAME')) { define('KOE_GAB_NAME', false); } // the check on additional folders will not throw hard errors, as this is probably changed on live systems if (isset($additionalFolders) && !is_array($additionalFolders)) { ZLog::Write(LOGLEVEL_ERROR, "ZPush::CheckConfig() : The additional folders synchronization not available as array."); } else { self::$addSyncFolders = array(); // process configured data foreach ($additionalFolders as $af) { if (!is_array($af) || !isset($af['store']) || !isset($af['folderid']) || !isset($af['name']) || !isset($af['type'])) { ZLog::Write(LOGLEVEL_ERROR, "ZPush::CheckConfig() : the additional folder synchronization is not configured correctly. Missing parameters. Entry will be ignored."); continue; } if ($af['store'] == "" || $af['folderid'] == "" || $af['name'] == "" || $af['type'] == "") { ZLog::Write(LOGLEVEL_WARN, "ZPush::CheckConfig() : the additional folder synchronization is not configured correctly. Empty parameters. Entry will be ignored."); continue; } if (!in_array($af['type'], array(SYNC_FOLDER_TYPE_USER_NOTE, SYNC_FOLDER_TYPE_USER_CONTACT, SYNC_FOLDER_TYPE_USER_APPOINTMENT, SYNC_FOLDER_TYPE_USER_TASK, SYNC_FOLDER_TYPE_USER_MAIL))) { ZLog::Write(LOGLEVEL_ERROR, sprintf("ZPush::CheckConfig() : the type of the additional synchronization folder '%s is not permitted.", $af['name'])); continue; } $folder = new SyncFolder(); $folder->BackendId = $af['folderid']; $folder->serverid = ZPush::GetDeviceManager(true)->GetFolderIdForBackendId($folder->BackendId, true, DeviceManager::FLD_ORIGIN_CONFIG, $af['name']); $folder->parentid = 0; // only top folders are supported $folder->displayname = $af['name']; $folder->type = $af['type']; // save store as custom property which is not streamed directly to the device $folder->NoBackendFolder = true; $folder->Store = $af['store']; self::$addSyncFolders[$folder->BackendId] = $folder; } } ZLog::Write(LOGLEVEL_DEBUG, sprintf("Used timezone '%s'", date_default_timezone_get())); // get the statemachine, which will also try to load the backend.. This could throw errors self::GetStateMachine(); }
/** * Imports a change on a folder * * @param object $folder SyncFolder * * @access public * @return boolean|SyncFolder false on error or a SyncFolder object with serverid and BackendId set (if available) * @throws StatusException */ public function ImportFolderChange($folder) { $id = isset($folder->BackendId) ? $folder->BackendId : false; $parent = $folder->parentid; $parent_org = $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])) { $folder->BackendId = bin2hex($props[PR_SOURCE_KEY]); $folder->serverid = ZPush::GetDeviceManager()->GetFolderIdForBackendId($folder->BackendId, true, DeviceManager::FLD_ORIGIN_USER, $folder->displayname); ZLog::Write(LOGLEVEL_DEBUG, sprintf("ImportChangesICS->ImportFolderChange(): Created folder '%s' with id: '%s' backendid: '%s'", $displayname, $folder->serverid, $folder->BackendId)); return $folder; } 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); } } // 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); } // the parent changed, but we got a backendID as parent and have to return an AS folderid - the parent-backendId must be mapped at this point already if ($folder->parentid != 0) { $folder->parentid = ZPush::GetDeviceManager()->GetFolderIdForBackendId($parent); } ZLog::Write(LOGLEVEL_DEBUG, sprintf("ImportChangesICS->ImportFolderChange(): Moved folder '%s' with id: %s/%s from: %s to: %s/%s", $displayname, $folder->serverid, $folder->BackendId, bin2hex($props[PR_PARENT_SOURCE_KEY]), $folder->parentid, $parent_org)); return $folder; } // 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 true; }
/** * Sends an email notification to the user containing the data the user tried to save. * * @param SyncObject $message * @param SyncObject $oldmessage * @return void */ private function sendNotificationEmail($message, $oldmessage) { // get email address and full name of the user $userinfo = ZPush::GetBackend()->GetUserDetails(Request::GetAuthUser()); // get the name of the folder $foldername = "unknown"; $folderid = bin2hex($this->folderid); $folders = ZPush::GetAdditionalSyncFolders(); if (isset($folders[$folderid]) && isset($folders[$folderid]->displayname)) { $foldername = $folders[$folderid]->displayname; } // get the differences between the two objects $data = substr(get_class($oldmessage), 4) . "\r\n"; // get the suppported fields as we need them to determine the ghosted properties $supportedFields = ZPush::GetDeviceManager()->GetSupportedFields(ZPush::GetDeviceManager()->GetFolderIdForBackendId($folderid)); $dataarray = $oldmessage->EvaluateAndCompare($message, @constant('READ_ONLY_NOTIFY_YOURDATA'), $supportedFields); foreach ($dataarray as $key => $value) { $value = str_replace("\r", "", $value); $value = str_replace("\n", str_pad("\r\n", 25), $value); $data .= str_pad(ucfirst($key) . ":", 25) . $value . "\r\n"; } // build a simple mime message $toEmail = $userinfo['emailaddress']; $mail = "From: Z-Push <no-reply>\r\n"; $mail .= "To: {$toEmail}\r\n"; $mail .= "Content-Type: text/plain; charset=utf-8\r\n"; $mail .= "Subject: " . @constant('READ_ONLY_NOTIFY_SUBJECT') . "\r\n\r\n"; $mail .= @constant('READ_ONLY_NOTIFY_BODY') . "\r\n"; // replace values of template $mail = str_replace("**USERFULLNAME**", $userinfo['fullname'], $mail); $mail = str_replace("**DATE**", strftime(@constant('READ_ONLY_NOTIFY_DATE_FORMAT')), $mail); $mail = str_replace("**TIME**", strftime(@constant('READ_ONLY_NOTIFY_TIME_FORMAT')), $mail); $mail = str_replace("**FOLDERNAME**", $foldername, $mail); $mail = str_replace("**MOBILETYPE**", Request::GetDeviceType(), $mail); $mail = str_replace("**MOBILEDEVICEID**", Request::GetDeviceID(), $mail); $mail = str_replace("**DIFFERENCES**", $data, $mail); // user send email to himself $m = new SyncSendMail(); $m->saveinsent = false; $m->replacemime = true; $m->mime = $mail; ZPush::GetBackend()->SendMail($m); }
/** * Authenticates the user with the configured Zarafa server * * @param string $username * @param string $domain * @param string $password * * @access public * @return boolean * @throws AuthenticationRequiredException */ public function Logon($user, $domain, $pass) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZarafaBackend->Logon(): Trying to authenticate user '%s'..", $user)); $this->mainUser = strtolower($user); try { // check if notifications are available in php-mapi if (function_exists('mapi_feature') && mapi_feature('LOGONFLAGS')) { // send Z-Push version and user agent to ZCP - ZP-589 if (Utils::CheckMapiExtVersion('7.2.0')) { $zpush_version = 'Z-Push_' . @constant('ZPUSH_VERSION'); $user_agent = ZPush::GetDeviceManager()->GetUserAgent(); $this->session = @mapi_logon_zarafa($user, $pass, MAPI_SERVER, null, null, 0, $zpush_version, $user_agent); } else { $this->session = @mapi_logon_zarafa($user, $pass, MAPI_SERVER, null, null, 0); } $this->notifications = true; } else { $this->session = @mapi_logon_zarafa($user, $pass, MAPI_SERVER); $this->notifications = false; } if (mapi_last_hresult()) { ZLog::Write(LOGLEVEL_ERROR, sprintf("ZarafaBackend->Logon(): login failed with error code: 0x%X", mapi_last_hresult())); if (mapi_last_hresult() == MAPI_E_NETWORK_ERROR) { throw new HTTPReturnCodeException("Error connecting to ZCP (login)", 503, null, LOGLEVEL_INFO); } } } catch (MAPIException $ex) { throw new AuthenticationRequiredException($ex->getDisplayMessage()); } if (!$this->session) { ZLog::Write(LOGLEVEL_WARN, sprintf("ZarafaBackend->Logon(): logon failed for user '%s'", $user)); $this->defaultstore = false; return false; } // Get/open default store $this->defaultstore = $this->openMessageStore($this->mainUser); if (mapi_last_hresult() == MAPI_E_FAILONEPROVIDER) { throw new HTTPReturnCodeException("Error connecting to ZCP (open store)", 503, null, LOGLEVEL_INFO); } if ($this->defaultstore === false) { throw new AuthenticationRequiredException(sprintf("ZarafaBackend->Logon(): User '%s' has no default store", $user)); } $this->store = $this->defaultstore; $this->storeName = $this->mainUser; ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZarafaBackend->Logon(): User '%s' is authenticated", $user)); $this->isZPushEnabled(); // check if this is a Zarafa 7 store with unicode support MAPIUtils::IsUnicodeStore($this->store); return true; }
/** * Searches for the emails on the server * * @param ContentParameter $cpo * * @return array */ public function GetMailboxSearchResults($cpo) { $searchFolder = $this->getSearchFolder(); $searchRestriction = $this->getSearchRestriction($cpo); $searchRange = explode('-', $cpo->GetSearchRange()); $searchFolderId = $cpo->GetSearchFolderid(); $searchFolders = array(); // search only in required folders if (!empty($searchFolderId)) { $searchFolderEntryId = mapi_msgstore_entryidfromsourcekey($this->store, hex2bin($searchFolderId)); $searchFolders[] = $searchFolderEntryId; } else { $tmp = mapi_getprops($this->store, array(PR_ENTRYID, PR_DISPLAY_NAME, PR_IPM_SUBTREE_ENTRYID)); $searchFolders[] = $tmp[PR_IPM_SUBTREE_ENTRYID]; } $items = array(); $flags = 0; // if subfolders are required, do a recursive search if ($cpo->GetSearchDeepTraversal()) { $flags |= SEARCH_RECURSIVE; } mapi_folder_setsearchcriteria($searchFolder, $searchRestriction, $searchFolders, $flags); $table = mapi_folder_getcontentstable($searchFolder); $searchStart = time(); // do the search and wait for all the results available while (time() - $searchStart < SEARCH_WAIT) { $searchcriteria = mapi_folder_getsearchcriteria($searchFolder); if (($searchcriteria["searchstate"] & SEARCH_REBUILD) == 0) { break; } // Search is done sleep(1); } // if the search range is set limit the result to it, otherwise return all found messages $rows = is_array($searchRange) && isset($searchRange[0]) && isset($searchRange[1]) ? mapi_table_queryrows($table, array(PR_SOURCE_KEY, PR_PARENT_SOURCE_KEY), $searchRange[0], $searchRange[1] - $searchRange[0] + 1) : mapi_table_queryrows($table, array(PR_SOURCE_KEY, PR_PARENT_SOURCE_KEY), 0, SEARCH_MAXRESULTS); $cnt = count($rows); $items['searchtotal'] = $cnt; $items["range"] = $cpo->GetSearchRange(); for ($i = 0; $i < $cnt; $i++) { $items[$i]['class'] = 'Email'; $items[$i]['longid'] = ZPush::GetDeviceManager()->GetFolderIdForBackendId(bin2hex($rows[$i][PR_PARENT_SOURCE_KEY])) . ":" . bin2hex($rows[$i][PR_SOURCE_KEY]); $items[$i]['folderid'] = bin2hex($rows[$i][PR_PARENT_SOURCE_KEY]); } return $items; }