Пример #1
0
 /**
  * Checks the hierarchy for changes.
  *
  * @param boolean       export changes, default: false
  *
  * @access private
  * @return boolean      indicating if changes were found or not
  */
 private function countHierarchyChange($exportChanges = false)
 {
     $folderid = false;
     $spa = $this->GetCollection($folderid);
     // Check with device manager if the hierarchy should be reloaded.
     // New additional folders are loaded here.
     if (ZPush::GetDeviceManager()->IsHierarchySyncRequired()) {
         ZLog::Write(LOGLEVEL_DEBUG, "SyncCollections->countHierarchyChange(): DeviceManager says HierarchySync is required.");
         return true;
     }
     $changecount = false;
     if ($exportChanges || $this->hierarchyExporterChecked === false) {
         try {
             // if this is a validation (not first run), make sure to load the hierarchy data again
             if ($this->hierarchyExporterChecked === true && !$this->LoadCollection(false, true, false)) {
                 throw new StatusException("Invalid states found while re-loading hierarchy data.");
             }
             $changesMem = ZPush::GetDeviceManager()->GetHierarchyChangesWrapper();
             // the hierarchyCache should now fully be initialized - check for changes in the additional folders
             $changesMem->Config(ZPush::GetAdditionalSyncFolders(false));
             // reset backend to the main store
             ZPush::GetBackend()->Setup(false);
             $exporter = ZPush::GetBackend()->GetExporter();
             if ($exporter !== false && isset($this->addparms[$folderid]["state"])) {
                 $exporter->Config($this->addparms[$folderid]["state"]);
                 $ret = $exporter->InitializeExporter($changesMem);
                 while (is_array($exporter->Synchronize())) {
                 }
                 if ($ret !== false) {
                     $changecount = $changesMem->GetChangeCount();
                 }
                 $this->hierarchyExporterChecked = true;
             }
         } catch (StatusException $ste) {
             throw new StatusException("SyncCollections->countHierarchyChange(): exporter can not be re-configured.", self::ERROR_WRONG_HIERARCHY, null, LOGLEVEL_WARN);
         }
         // start over if exporter can not be configured atm
         if ($changecount === false) {
             ZLog::Write(LOGLEVEL_WARN, "SyncCollections->countHierarchyChange(): no changes received from Exporter.");
         }
     }
     return $changecount > 0;
 }
Пример #2
0
 /**
  * Handles creates, updates or deletes of a folder
  * issued by the commands FolderCreate, FolderUpdate and FolderDelete
  *
  * @param int       $commandCode
  *
  * @access public
  * @return boolean
  */
 public function Handle($commandCode)
 {
     $el = self::$decoder->getElement();
     if ($el[EN_TYPE] != EN_TYPE_STARTTAG) {
         return false;
     }
     $create = $update = $delete = false;
     if ($el[EN_TAG] == SYNC_FOLDERHIERARCHY_FOLDERCREATE) {
         $create = true;
     } else {
         if ($el[EN_TAG] == SYNC_FOLDERHIERARCHY_FOLDERUPDATE) {
             $update = true;
         } else {
             if ($el[EN_TAG] == SYNC_FOLDERHIERARCHY_FOLDERDELETE) {
                 $delete = true;
             }
         }
     }
     if (!$create && !$update && !$delete) {
         return false;
     }
     // SyncKey
     if (!self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_SYNCKEY)) {
         return false;
     }
     $synckey = self::$decoder->getElementContent();
     if (!self::$decoder->getElementEndTag()) {
         return false;
     }
     // ServerID
     $serverid = false;
     if (self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_SERVERENTRYID)) {
         $serverid = self::$decoder->getElementContent();
         if (!self::$decoder->getElementEndTag()) {
             return false;
         }
     }
     // Parent
     $parentid = false;
     // when creating or updating more information is necessary
     if (!$delete) {
         if (self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_PARENTID)) {
             $parentid = self::$decoder->getElementContent();
             if (!self::$decoder->getElementEndTag()) {
                 return false;
             }
         }
         // Displayname
         if (!self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_DISPLAYNAME)) {
             return false;
         }
         $displayname = self::$decoder->getElementContent();
         if (!self::$decoder->getElementEndTag()) {
             return false;
         }
         // Type
         $type = false;
         if (self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_TYPE)) {
             $type = self::$decoder->getElementContent();
             if (!self::$decoder->getElementEndTag()) {
                 return false;
             }
         }
     }
     // endtag foldercreate, folderupdate, folderdelete
     if (!self::$decoder->getElementEndTag()) {
         return false;
     }
     $status = SYNC_FSSTATUS_SUCCESS;
     // Get state of hierarchy
     try {
         $syncstate = self::$deviceManager->GetStateManager()->GetSyncState($synckey);
         $newsynckey = self::$deviceManager->GetStateManager()->GetNewSyncKey($synckey);
         // Over the ChangesWrapper the HierarchyCache is notified about all changes
         $changesMem = self::$deviceManager->GetHierarchyChangesWrapper();
         // the hierarchyCache should now fully be initialized - check for changes in the additional folders
         $changesMem->Config(ZPush::GetAdditionalSyncFolders());
         // there are unprocessed changes in the hierarchy, trigger resync
         if ($changesMem->GetChangeCount() > 0) {
             throw new StatusException("HandleFolderChange() can not proceed as there are unprocessed hierarchy changes", SYNC_FSSTATUS_SERVERERROR);
         }
         // any additional folders can not be modified!
         if ($serverid !== false && ZPush::GetAdditionalSyncFolderStore($serverid)) {
             throw new StatusException("HandleFolderChange() can not change additional folders which are configured", SYNC_FSSTATUS_SYSTEMFOLDER);
         }
         // switch user store if this this happens inside an additional folder
         // if this is an additional folder the backend has to be setup correctly
         if (!self::$backend->Setup(ZPush::GetAdditionalSyncFolderStore($parentid != false ? $parentid : $serverid))) {
             throw new StatusException(sprintf("HandleFolderChange() could not Setup() the backend for folder id '%s'", $parentid != false ? $parentid : $serverid), SYNC_FSSTATUS_SERVERERROR);
         }
     } catch (StateNotFoundException $snfex) {
         $status = SYNC_FSSTATUS_SYNCKEYERROR;
     } catch (StatusException $stex) {
         $status = $stex->getCode();
     }
     // set $newsynckey in case of an error
     if (!isset($newsynckey)) {
         $newsynckey = $synckey;
     }
     if ($status == SYNC_FSSTATUS_SUCCESS) {
         try {
             // Configure importer with last state
             $importer = self::$backend->GetImporter();
             $importer->Config($syncstate);
             // the messages from the PIM will be forwarded to the real importer
             $changesMem->SetDestinationImporter($importer);
             // process incoming change
             if (!$delete) {
                 // Send change
                 $folder = new SyncFolder();
                 $folder->serverid = $serverid;
                 $folder->parentid = $parentid;
                 $folder->displayname = $displayname;
                 $folder->type = $type;
                 $serverid = $changesMem->ImportFolderChange($folder);
             } else {
                 // delete folder
                 $changesMem->ImportFolderDeletion($serverid, 0);
             }
         } catch (StatusException $stex) {
             $status = $stex->getCode();
         }
     }
     self::$encoder->startWBXML();
     if ($create) {
         self::$encoder->startTag(SYNC_FOLDERHIERARCHY_FOLDERCREATE);
         self::$encoder->startTag(SYNC_FOLDERHIERARCHY_STATUS);
         self::$encoder->content($status);
         self::$encoder->endTag();
         self::$encoder->startTag(SYNC_FOLDERHIERARCHY_SYNCKEY);
         self::$encoder->content($newsynckey);
         self::$encoder->endTag();
         self::$encoder->startTag(SYNC_FOLDERHIERARCHY_SERVERENTRYID);
         self::$encoder->content($serverid);
         self::$encoder->endTag();
         self::$encoder->endTag();
     } elseif ($update) {
         self::$encoder->startTag(SYNC_FOLDERHIERARCHY_FOLDERUPDATE);
         self::$encoder->startTag(SYNC_FOLDERHIERARCHY_STATUS);
         self::$encoder->content($status);
         self::$encoder->endTag();
         self::$encoder->startTag(SYNC_FOLDERHIERARCHY_SYNCKEY);
         self::$encoder->content($newsynckey);
         self::$encoder->endTag();
         self::$encoder->endTag();
     } elseif ($delete) {
         self::$encoder->startTag(SYNC_FOLDERHIERARCHY_FOLDERDELETE);
         self::$encoder->startTag(SYNC_FOLDERHIERARCHY_STATUS);
         self::$encoder->content($status);
         self::$encoder->endTag();
         self::$encoder->startTag(SYNC_FOLDERHIERARCHY_SYNCKEY);
         self::$encoder->content($newsynckey);
         self::$encoder->endTag();
         self::$encoder->endTag();
     }
     self::$topCollector->AnnounceInformation(sprintf("Operation status %d", $status), true);
     // Save the sync state for the next time
     if (isset($importer)) {
         self::$deviceManager->GetStateManager()->SetSyncState($newsynckey, $importer->GetState());
     }
     return true;
 }
Пример #3
0
 /**
  * Handles the FolderSync command
  *
  * @param int       $commandCode
  *
  * @access public
  * @return boolean
  */
 public function Handle($commandCode)
 {
     // Maps serverid -> clientid for items that are received from the PIM
     $map = array();
     // Parse input
     if (!self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_FOLDERSYNC)) {
         return false;
     }
     if (!self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_SYNCKEY)) {
         return false;
     }
     $synckey = self::$decoder->getElementContent();
     if (!self::$decoder->getElementEndTag()) {
         return false;
     }
     $status = SYNC_FSSTATUS_SUCCESS;
     $newsynckey = $synckey;
     try {
         $syncstate = self::$deviceManager->GetStateManager()->GetSyncState($synckey);
         // We will be saving the sync state under 'newsynckey'
         $newsynckey = self::$deviceManager->GetStateManager()->GetNewSyncKey($synckey);
     } catch (StateNotFoundException $snfex) {
         $status = SYNC_FSSTATUS_SYNCKEYERROR;
     } catch (StateInvalidException $sive) {
         $status = SYNC_FSSTATUS_SYNCKEYERROR;
     }
     // The ChangesWrapper caches all imports in-memory, so we can send a change count
     // before sending the actual data.
     // the HierarchyCache is notified and the changes from the PIM are transmitted to the actual backend
     $changesMem = self::$deviceManager->GetHierarchyChangesWrapper();
     // the hierarchyCache should now fully be initialized - check for changes in the additional folders
     $changesMem->Config(ZPush::GetAdditionalSyncFolders());
     // process incoming changes
     if (self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_CHANGES)) {
         // Ignore <Count> if present
         if (self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_COUNT)) {
             self::$decoder->getElementContent();
             if (!self::$decoder->getElementEndTag()) {
                 return false;
             }
         }
         // Process the changes (either <Add>, <Modify>, or <Remove>)
         $element = self::$decoder->getElement();
         if ($element[EN_TYPE] != EN_TYPE_STARTTAG) {
             return false;
         }
         $importer = false;
         while (1) {
             $folder = new SyncFolder();
             if (!$folder->Decode(self::$decoder)) {
                 break;
             }
             try {
                 if ($status == SYNC_FSSTATUS_SUCCESS && !$importer) {
                     // Configure the backends importer with last state
                     $importer = self::$backend->GetImporter();
                     $importer->Config($syncstate);
                     // the messages from the PIM will be forwarded to the backend
                     $changesMem->forwardImporter($importer);
                 }
                 if ($status == SYNC_FSSTATUS_SUCCESS) {
                     switch ($element[EN_TAG]) {
                         case SYNC_ADD:
                         case SYNC_MODIFY:
                             $serverid = $changesMem->ImportFolderChange($folder);
                             break;
                         case SYNC_REMOVE:
                             $serverid = $changesMem->ImportFolderDeletion($folder);
                             break;
                     }
                     // TODO what does $map??
                     if ($serverid) {
                         $map[$serverid] = $folder->clientid;
                     }
                 } else {
                     ZLog::Write(LOGLEVEL_WARN, sprintf("Request->HandleFolderSync(): ignoring incoming folderchange for folder '%s' as status indicates problem.", $folder->displayname));
                     self::$topCollector->AnnounceInformation("Incoming change ignored", true);
                 }
             } catch (StatusException $stex) {
                 $status = $stex->getCode();
             }
         }
         if (!self::$decoder->getElementEndTag()) {
             return false;
         }
     } else {
         // check for a potential process loop like described in Issue ZP-5
         if ($synckey != "0" && self::$deviceManager->IsHierarchyFullResyncRequired()) {
             $status = SYNC_FSSTATUS_SYNCKEYERROR;
         }
         self::$deviceManager->AnnounceProcessStatus(false, $status);
     }
     if (!self::$decoder->getElementEndTag()) {
         return false;
     }
     // We have processed incoming foldersync requests, now send the PIM
     // our changes
     // Output our WBXML reply now
     self::$encoder->StartWBXML();
     self::$encoder->startTag(SYNC_FOLDERHIERARCHY_FOLDERSYNC);
     if ($status == SYNC_FSSTATUS_SUCCESS) {
         try {
             // do nothing if this is an invalid device id (like the 'validate' Androids internal client sends)
             if (!Request::IsValidDeviceID()) {
                 throw new StatusException(sprintf("Request::IsValidDeviceID() indicated that '%s' is not a valid device id", Request::GetDeviceID()), SYNC_FSSTATUS_SERVERERROR);
             }
             // Changes from backend are sent to the MemImporter and processed for the HierarchyCache.
             // The state which is saved is from the backend, as the MemImporter is only a proxy.
             $exporter = self::$backend->GetExporter();
             $exporter->Config($syncstate);
             $exporter->InitializeExporter($changesMem);
             // Stream all changes to the ImportExportChangesMem
             while (is_array($exporter->Synchronize())) {
             }
             // get the new state from the backend
             $newsyncstate = isset($exporter) ? $exporter->GetState() : "";
         } catch (StatusException $stex) {
             if ($stex->getCode() == SYNC_FSSTATUS_CODEUNKNOWN) {
                 $status = SYNC_FSSTATUS_SYNCKEYERROR;
             } else {
                 $status = $stex->getCode();
             }
         }
     }
     self::$encoder->startTag(SYNC_FOLDERHIERARCHY_STATUS);
     self::$encoder->content($status);
     self::$encoder->endTag();
     if ($status == SYNC_FSSTATUS_SUCCESS) {
         self::$encoder->startTag(SYNC_FOLDERHIERARCHY_SYNCKEY);
         $synckey = $changesMem->IsStateChanged() ? $newsynckey : $synckey;
         self::$encoder->content($synckey);
         self::$encoder->endTag();
         // Stream folders directly to the PDA
         $streamimporter = new ImportChangesStream(self::$encoder, false);
         $changesMem->InitializeExporter($streamimporter);
         $changeCount = $changesMem->GetChangeCount();
         self::$encoder->startTag(SYNC_FOLDERHIERARCHY_CHANGES);
         self::$encoder->startTag(SYNC_FOLDERHIERARCHY_COUNT);
         self::$encoder->content($changeCount);
         self::$encoder->endTag();
         while ($changesMem->Synchronize()) {
         }
         self::$encoder->endTag();
         self::$topCollector->AnnounceInformation(sprintf("Outgoing %d folders", $changeCount), true);
         // everything fine, save the sync state for the next time
         if ($synckey == $newsynckey) {
             self::$deviceManager->GetStateManager()->SetSyncState($newsynckey, $newsyncstate);
         }
     }
     self::$encoder->endTag();
     return true;
 }
Пример #4
0
 /**
  * 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);
 }
Пример #5
0
 /**
  * Handles the FolderSync command
  *
  * @param int       $commandCode
  *
  * @access public
  * @return boolean
  */
 public function Handle($commandCode)
 {
     // Parse input
     if (!self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_FOLDERSYNC)) {
         return false;
     }
     if (!self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_SYNCKEY)) {
         return false;
     }
     $synckey = self::$decoder->getElementContent();
     if (!self::$decoder->getElementEndTag()) {
         return false;
     }
     // every FolderSync with SyncKey 0 should return the supported AS version & command headers
     if ($synckey == "0") {
         self::$specialHeaders = array();
         self::$specialHeaders[] = ZPush::GetSupportedProtocolVersions();
         self::$specialHeaders[] = ZPush::GetSupportedCommands();
     }
     $status = SYNC_FSSTATUS_SUCCESS;
     $newsynckey = $synckey;
     try {
         $syncstate = self::$deviceManager->GetStateManager()->GetSyncState($synckey);
         // We will be saving the sync state under 'newsynckey'
         $newsynckey = self::$deviceManager->GetStateManager()->GetNewSyncKey($synckey);
     } catch (StateNotFoundException $snfex) {
         $status = SYNC_FSSTATUS_SYNCKEYERROR;
     } catch (StateInvalidException $sive) {
         $status = SYNC_FSSTATUS_SYNCKEYERROR;
     }
     // The ChangesWrapper caches all imports in-memory, so we can send a change count
     // before sending the actual data.
     // the HierarchyCache is notified and the changes from the PIM are transmitted to the actual backend
     $changesMem = self::$deviceManager->GetHierarchyChangesWrapper();
     // the hierarchyCache should now fully be initialized - check for changes in the additional folders
     $changesMem->Config(ZPush::GetAdditionalSyncFolders());
     // process incoming changes
     if (self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_CHANGES)) {
         // Ignore <Count> if present
         if (self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_COUNT)) {
             self::$decoder->getElementContent();
             if (!self::$decoder->getElementEndTag()) {
                 return false;
             }
         }
         // Process the changes (either <Add>, <Modify>, or <Remove>)
         $element = self::$decoder->getElement();
         if ($element[EN_TYPE] != EN_TYPE_STARTTAG) {
             return false;
         }
         $importer = false;
         while (1) {
             $folder = new SyncFolder();
             if (!$folder->Decode(self::$decoder)) {
                 break;
             }
             try {
                 if ($status == SYNC_FSSTATUS_SUCCESS && !$importer) {
                     // Configure the backends importer with last state
                     $importer = self::$backend->GetImporter();
                     $importer->Config($syncstate);
                     // the messages from the PIM will be forwarded to the backend
                     $changesMem->forwardImporter($importer);
                 }
                 if ($status == SYNC_FSSTATUS_SUCCESS) {
                     switch ($element[EN_TAG]) {
                         case SYNC_ADD:
                         case SYNC_MODIFY:
                             $serverid = $changesMem->ImportFolderChange($folder);
                             break;
                         case SYNC_REMOVE:
                             $serverid = $changesMem->ImportFolderDeletion($folder);
                             break;
                     }
                 } else {
                     ZLog::Write(LOGLEVEL_WARN, sprintf("Request->HandleFolderSync(): ignoring incoming folderchange for folder '%s' as status indicates problem.", $folder->displayname));
                     self::$topCollector->AnnounceInformation("Incoming change ignored", true);
                 }
             } catch (StatusException $stex) {
                 $status = $stex->getCode();
             }
         }
         if (!self::$decoder->getElementEndTag()) {
             return false;
         }
     } else {
         // check for a potential process loop like described in Issue ZP-5
         if ($synckey != "0" && self::$deviceManager->IsHierarchyFullResyncRequired()) {
             $status = SYNC_FSSTATUS_SYNCKEYERROR;
         }
         self::$deviceManager->AnnounceProcessStatus(false, $status);
     }
     if (!self::$decoder->getElementEndTag()) {
         return false;
     }
     // We have processed incoming foldersync requests, now send the PIM
     // our changes
     // Output our WBXML reply now
     self::$encoder->StartWBXML();
     self::$encoder->startTag(SYNC_FOLDERHIERARCHY_FOLDERSYNC);
     if ($status == SYNC_FSSTATUS_SUCCESS) {
         try {
             // do nothing if this is an invalid device id (like the 'validate' Androids internal client sends)
             if (!Request::IsValidDeviceID()) {
                 throw new StatusException(sprintf("Request::IsValidDeviceID() indicated that '%s' is not a valid device id", Request::GetDeviceID()), SYNC_FSSTATUS_SERVERERROR);
             }
             // Changes from backend are sent to the MemImporter and processed for the HierarchyCache.
             // The state which is saved is from the backend, as the MemImporter is only a proxy.
             $exporter = self::$backend->GetExporter();
             $exporter->Config($syncstate);
             $exporter->InitializeExporter($changesMem);
             // Stream all changes to the ImportExportChangesMem
             $maxExporttime = Request::GetExpectedConnectionTimeout();
             $totalChanges = $exporter->GetChangeCount();
             $started = time();
             $exported = 0;
             $partial = false;
             while (is_array($exporter->Synchronize())) {
                 $exported++;
                 if (time() % 4) {
                     self::$topCollector->AnnounceInformation(sprintf("Exported %d from %d folders", $exported, $totalChanges));
                 }
                 // if partial sync is allowed, stop if this takes too long
                 if (USE_PARTIAL_FOLDERSYNC && time() - $started > $maxExporttime) {
                     ZLog::Write(LOGLEVEL_WARN, sprintf("Request->HandleFolderSync(): Exporting folders is too slow. In %d seconds only %d from %d changes were processed.", time() - $started, $exported, $totalChanges));
                     self::$topCollector->AnnounceInformation(sprintf("Partial export of %d out of %d folders", $exported, $totalChanges), true);
                     self::$deviceManager->SetFolderSyncComplete(false);
                     $partial = true;
                     break;
                 }
             }
             // update the foldersync complete flag
             if (USE_PARTIAL_FOLDERSYNC && $partial == false && self::$deviceManager->GetFolderSyncComplete() === false) {
                 // say that we are done with partial synching
                 self::$deviceManager->SetFolderSyncComplete(true);
                 // reset the loop data to prevent any loop detection to kick in now
                 self::$deviceManager->ClearLoopDetectionData(Request::GetAuthUser(), Request::GetDeviceID());
                 ZLog::Write(LOGLEVEL_INFO, "Request->HandleFolderSync(): Chunked exporting of folders completed successfully");
             }
             // get the new state from the backend
             $newsyncstate = isset($exporter) ? $exporter->GetState() : "";
         } catch (StatusException $stex) {
             if ($stex->getCode() == SYNC_FSSTATUS_CODEUNKNOWN) {
                 $status = SYNC_FSSTATUS_SYNCKEYERROR;
             } else {
                 $status = $stex->getCode();
             }
         }
     }
     self::$encoder->startTag(SYNC_FOLDERHIERARCHY_STATUS);
     self::$encoder->content($status);
     self::$encoder->endTag();
     if ($status == SYNC_FSSTATUS_SUCCESS) {
         self::$encoder->startTag(SYNC_FOLDERHIERARCHY_SYNCKEY);
         $synckey = $changesMem->IsStateChanged() ? $newsynckey : $synckey;
         self::$encoder->content($synckey);
         self::$encoder->endTag();
         // Stream folders directly to the PDA
         $streamimporter = new ImportChangesStream(self::$encoder, false);
         $changesMem->InitializeExporter($streamimporter);
         $changeCount = $changesMem->GetChangeCount();
         self::$encoder->startTag(SYNC_FOLDERHIERARCHY_CHANGES);
         self::$encoder->startTag(SYNC_FOLDERHIERARCHY_COUNT);
         self::$encoder->content($changeCount);
         self::$encoder->endTag();
         while ($changesMem->Synchronize()) {
         }
         self::$encoder->endTag();
         self::$topCollector->AnnounceInformation(sprintf("Outgoing %d folders", $changeCount), true);
         // everything fine, save the sync state for the next time
         if ($synckey == $newsynckey) {
             self::$deviceManager->GetStateManager()->SetSyncState($newsynckey, $newsyncstate);
         }
     }
     self::$encoder->endTag();
     return true;
 }
Пример #6
0
 /**
  * Sends an e-mail
  * This messages needs to be saved into the 'sent items' folder
  *
  * @param SyncSendMail  $sm     SyncSendMail object
  *
  * @access public
  * @return boolean
  * @throws StatusException
  */
 public function SendMail($sm)
 {
     // Check if imtomapi function is available and use it to send the mime message.
     // It is available since ZCP 7.0.6
     // @see http://jira.zarafa.com/browse/ZCP-9508
     if (!(function_exists('mapi_feature') && mapi_feature('INETMAPI_IMTOMAPI'))) {
         throw new StatusException("KopanoBackend->SendMail(): ZCP/KC version is too old, INETMAPI_IMTOMAPI is not available. Install at least ZCP version 7.0.6 or later.", SYNC_COMMONSTATUS_MAILSUBMISSIONFAILED, null, LOGLEVEL_FATAL);
         return false;
     }
     ZLog::Write(LOGLEVEL_DEBUG, sprintf("KopanoBackend->SendMail(): RFC822: %d bytes  forward-id: '%s' reply-id: '%s' parent-id: '%s' SaveInSent: '%s' ReplaceMIME: '%s'", strlen($sm->mime), Utils::PrintAsString($sm->forwardflag), Utils::PrintAsString($sm->replyflag), Utils::PrintAsString(isset($sm->source->folderid) ? $sm->source->folderid : false), Utils::PrintAsString($sm->saveinsent), Utils::PrintAsString(isset($sm->replacemime))));
     // Send-As functionality - https://jira.z-hub.io/browse/ZP-908
     $sendingAsSomeone = false;
     if (defined('KOE_CAPABILITY_SENDAS') && KOE_CAPABILITY_SENDAS) {
         $senderEmail = array();
         // KOE: grep for the Sender header indicating we should send-as
         // the 'X-Push-Sender-Name' header is not used
         if (preg_match("/^X-Push-Sender:\\s(.*?)\$/im", $sm->mime, $senderEmail)) {
             $sendAsEmail = trim($senderEmail[1]);
             ZLog::Write(LOGLEVEL_DEBUG, sprintf("KopanoBackend->SendMail(): Send-As '%s' requested by KOE", $sendAsEmail));
             $sm->mime = preg_replace("/^From: .*?\$/im", "From: " . $sendAsEmail, $sm->mime, 1);
             $sendingAsSomeone = true;
         } elseif (isset($sm->source->folderid)) {
             // get the owner of this folder - System is not allowed
             $sharedUser = ZPush::GetAdditionalSyncFolderStore($sm->source->folderid);
             if ($sharedUser != false && $sharedUser != 'SYSTEM') {
                 $folders = ZPush::GetAdditionalSyncFolders();
                 if (isset($folders[$sm->source->folderid]) && $folders[$sm->source->folderid]->Flags & DeviceManager::FLD_FLAGS_REPLYASUSER) {
                     $sendAs = $this->resolveRecipientGAL($sharedUser, 1);
                     if (isset($sendAs[0])) {
                         ZLog::Write(LOGLEVEL_DEBUG, sprintf("KopanoBackend->SendMail(): Server side Send-As activated for shared folder. Sending as '%s'.", $sendAs[0]->emailaddress));
                         $sm->mime = preg_replace("/^From: .*?\$/im", "From: " . $sendAs[0]->emailaddress, $sm->mime, 1);
                         $sendingAsSomeone = true;
                     }
                 }
             }
         }
     }
     // by splitting the message in several lines we can easily grep later
     foreach (preg_split("/((\r)?\n)/", $sm->mime) as $rfc822line) {
         ZLog::Write(LOGLEVEL_WBXML, "RFC822: " . $rfc822line);
     }
     $sendMailProps = MAPIMapping::GetSendMailProperties();
     $sendMailProps = getPropIdsFromStrings($this->defaultstore, $sendMailProps);
     // Open the outbox and create the message there
     $storeprops = mapi_getprops($this->defaultstore, array($sendMailProps["outboxentryid"], $sendMailProps["ipmsentmailentryid"]));
     if (isset($storeprops[$sendMailProps["outboxentryid"]])) {
         $outbox = mapi_msgstore_openentry($this->defaultstore, $storeprops[$sendMailProps["outboxentryid"]]);
     }
     if (!$outbox) {
         throw new StatusException(sprintf("KopanoBackend->SendMail(): No Outbox found or unable to create message: 0x%X", mapi_last_hresult()), SYNC_COMMONSTATUS_SERVERERROR);
     }
     $mapimessage = mapi_folder_createmessage($outbox);
     //message properties to be set
     $mapiprops = array();
     // only save the outgoing in sent items folder if the mobile requests it
     $mapiprops[$sendMailProps["sentmailentryid"]] = $storeprops[$sendMailProps["ipmsentmailentryid"]];
     ZLog::Write(LOGLEVEL_DEBUG, "Use the mapi_inetmapi_imtomapi function");
     $ab = mapi_openaddressbook($this->session);
     mapi_inetmapi_imtomapi($this->session, $this->defaultstore, $ab, $mapimessage, $sm->mime, array());
     // Set the appSeqNr so that tracking tab can be updated for meeting request updates
     // @see http://jira.zarafa.com/browse/ZP-68
     $meetingRequestProps = MAPIMapping::GetMeetingRequestProperties();
     $meetingRequestProps = getPropIdsFromStrings($this->defaultstore, $meetingRequestProps);
     $props = mapi_getprops($mapimessage, array(PR_MESSAGE_CLASS, $meetingRequestProps["goidtag"], $sendMailProps["internetcpid"], $sendMailProps["body"], $sendMailProps["html"], $sendMailProps["rtf"], $sendMailProps["rtfinsync"]));
     // Convert sent message's body to UTF-8 if it was a HTML message.
     // @see http://jira.zarafa.com/browse/ZP-505 and http://jira.zarafa.com/browse/ZP-555
     if (isset($props[$sendMailProps["internetcpid"]]) && $props[$sendMailProps["internetcpid"]] != INTERNET_CPID_UTF8 && MAPIUtils::GetNativeBodyType($props) == SYNC_BODYPREFERENCE_HTML) {
         ZLog::Write(LOGLEVEL_DEBUG, sprintf("Sent email cpid is not unicode (%d). Set it to unicode and convert email html body.", $props[$sendMailProps["internetcpid"]]));
         $mapiprops[$sendMailProps["internetcpid"]] = INTERNET_CPID_UTF8;
         $bodyHtml = MAPIUtils::readPropStream($mapimessage, PR_HTML);
         $bodyHtml = Utils::ConvertCodepageStringToUtf8($props[$sendMailProps["internetcpid"]], $bodyHtml);
         $mapiprops[$sendMailProps["html"]] = $bodyHtml;
         mapi_setprops($mapimessage, $mapiprops);
     }
     if (stripos($props[PR_MESSAGE_CLASS], "IPM.Schedule.Meeting.Resp.") === 0) {
         // search for calendar items using goid
         $mr = new Meetingrequest($this->defaultstore, $mapimessage);
         $appointments = $mr->findCalendarItems($props[$meetingRequestProps["goidtag"]]);
         if (is_array($appointments) && !empty($appointments)) {
             $app = mapi_msgstore_openentry($this->defaultstore, $appointments[0]);
             $appprops = mapi_getprops($app, array($meetingRequestProps["appSeqNr"]));
             if (isset($appprops[$meetingRequestProps["appSeqNr"]]) && $appprops[$meetingRequestProps["appSeqNr"]]) {
                 $mapiprops[$meetingRequestProps["appSeqNr"]] = $appprops[$meetingRequestProps["appSeqNr"]];
                 ZLog::Write(LOGLEVEL_DEBUG, sprintf("Set sequence number to:%d", $appprops[$meetingRequestProps["appSeqNr"]]));
             }
         }
     }
     // Delete the PR_SENT_REPRESENTING_* properties because some android devices
     // do not send neither From nor Sender header causing empty PR_SENT_REPRESENTING_NAME and
     // PR_SENT_REPRESENTING_EMAIL_ADDRESS properties and "broken" PR_SENT_REPRESENTING_ENTRYID
     // which results in spooler not being able to send the message.
     // @see http://jira.zarafa.com/browse/ZP-85
     // If using KOE send-as feature, we keep this properties because they actually are the send-as
     if (!$sendingAsSomeone) {
         mapi_deleteprops($mapimessage, array($sendMailProps["sentrepresentingname"], $sendMailProps["sentrepresentingemail"], $sendMailProps["representingentryid"], $sendMailProps["sentrepresentingaddt"], $sendMailProps["sentrepresentinsrchk"]));
     }
     if (isset($sm->source->itemid) && $sm->source->itemid) {
         // answering an email in a public/shared folder
         // TODO as the store is setup, we should actually user $this->store instead of $this->defaultstore - nevertheless we need to make sure this store is able to send mail (has an outbox)
         if (!$this->Setup(ZPush::GetAdditionalSyncFolderStore($sm->source->folderid))) {
             throw new StatusException(sprintf("KopanoBackend->SendMail() could not Setup() the backend for folder id '%s'", $sm->source->folderid), SYNC_COMMONSTATUS_SERVERERROR);
         }
         $entryid = mapi_msgstore_entryidfromsourcekey($this->store, hex2bin($sm->source->folderid), hex2bin($sm->source->itemid));
         if ($entryid) {
             $fwmessage = mapi_msgstore_openentry($this->store, $entryid);
         }
         if (isset($fwmessage) && $fwmessage) {
             // update icon and last_verb when forwarding or replying message
             // reply-all (verb 103) is not supported, as we cannot really detect this case
             if ($sm->forwardflag) {
                 $updateProps = array(PR_ICON_INDEX => 262, PR_LAST_VERB_EXECUTED => 104);
             } elseif ($sm->replyflag) {
                 $updateProps = array(PR_ICON_INDEX => 261, PR_LAST_VERB_EXECUTED => 102);
             }
             if (isset($updateProps)) {
                 $updateProps[PR_LAST_VERB_EXECUTION_TIME] = time();
                 mapi_setprops($fwmessage, $updateProps);
                 mapi_savechanges($fwmessage);
             }
             // only attach the original message if the mobile does not send it itself
             if (!isset($sm->replacemime)) {
                 // get message's body in order to append forward or reply text
                 if (!isset($body)) {
                     $body = MAPIUtils::readPropStream($mapimessage, PR_BODY);
                 }
                 if (!isset($bodyHtml)) {
                     $bodyHtml = MAPIUtils::readPropStream($mapimessage, PR_HTML);
                 }
                 $cpid = mapi_getprops($fwmessage, array($sendMailProps["internetcpid"]));
                 if ($sm->forwardflag) {
                     // attach the original attachments to the outgoing message
                     $this->copyAttachments($mapimessage, $fwmessage);
                 }
                 // regarding the conversion @see ZP-470
                 if (strlen($body) > 0) {
                     $fwbody = MAPIUtils::readPropStream($fwmessage, PR_BODY);
                     // if only the old message's cpid is set, convert from old charset to utf-8
                     if (isset($cpid[$sendMailProps["internetcpid"]]) && $cpid[$sendMailProps["internetcpid"]] != INTERNET_CPID_UTF8) {
                         ZLog::Write(LOGLEVEL_DEBUG, sprintf("KopanoBackend->SendMail(): convert plain forwarded message charset (only fw set) from '%s' to '65001'", $cpid[$sendMailProps["internetcpid"]]));
                         $fwbody = Utils::ConvertCodepageStringToUtf8($cpid[$sendMailProps["internetcpid"]], $fwbody);
                     } else {
                         ZLog::Write(LOGLEVEL_DEBUG, "KopanoBackend->SendMail(): no charset conversion done for plain forwarded message");
                         $fwbody = w2u($fwbody);
                     }
                     $mapiprops[$sendMailProps["body"]] = $body . "\r\n\r\n" . $fwbody;
                 }
                 if (strlen($bodyHtml) > 0) {
                     $fwbodyHtml = MAPIUtils::readPropStream($fwmessage, PR_HTML);
                     // if only new message's cpid is set, convert to UTF-8
                     if (isset($cpid[$sendMailProps["internetcpid"]]) && $cpid[$sendMailProps["internetcpid"]] != INTERNET_CPID_UTF8) {
                         ZLog::Write(LOGLEVEL_DEBUG, sprintf("KopanoBackend->SendMail(): convert html forwarded message charset (only fw set) from '%s' to '65001'", $cpid[$sendMailProps["internetcpid"]]));
                         $fwbodyHtml = Utils::ConvertCodepageStringToUtf8($cpid[$sendMailProps["internetcpid"]], $fwbodyHtml);
                     } else {
                         ZLog::Write(LOGLEVEL_DEBUG, "KopanoBackend->SendMail(): no charset conversion done for html forwarded message");
                         $fwbodyHtml = w2u($fwbodyHtml);
                     }
                     $mapiprops[$sendMailProps["html"]] = $bodyHtml . "<br><br>" . $fwbodyHtml;
                 }
             }
         } else {
             // no fwmessage could be opened and we need it because we do not replace mime
             if (!isset($sm->replacemime) || $sm->replacemime == false) {
                 throw new StatusException(sprintf("KopanoBackend->SendMail(): Could not open message id '%s' in folder id '%s' to be replied/forwarded: 0x%X", $sm->source->itemid, $sm->source->folderid, mapi_last_hresult()), SYNC_COMMONSTATUS_ITEMNOTFOUND);
             }
         }
     }
     mapi_setprops($mapimessage, $mapiprops);
     mapi_message_savechanges($mapimessage);
     mapi_message_submitmessage($mapimessage);
     $hr = mapi_last_hresult();
     if ($hr) {
         throw new StatusException(sprintf("KopanoBackend->SendMail(): Error saving/submitting the message to the Outbox: 0x%X", mapi_last_hresult()), SYNC_COMMONSTATUS_MAILSUBMISSIONFAILED);
     }
     ZLog::Write(LOGLEVEL_DEBUG, "KopanoBackend->SendMail(): email submitted");
     return true;
 }