Back to index

d-push  2.0
sync.php
Go to the documentation of this file.
00001 <?php
00002 /***********************************************
00003 * File      :   sync.php
00004 * Project   :   Z-Push
00005 * Descr     :   Provides the SYNC command
00006 *
00007 * Created   :   16.02.2012
00008 *
00009 * Copyright 2007 - 2012 Zarafa Deutschland GmbH
00010 *
00011 * This program is free software: you can redistribute it and/or modify
00012 * it under the terms of the GNU Affero General Public License, version 3,
00013 * as published by the Free Software Foundation with the following additional
00014 * term according to sec. 7:
00015 *
00016 * According to sec. 7 of the GNU Affero General Public License, version 3,
00017 * the terms of the AGPL are supplemented with the following terms:
00018 *
00019 * "Zarafa" is a registered trademark of Zarafa B.V.
00020 * "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
00021 * The licensing of the Program under the AGPL does not imply a trademark license.
00022 * Therefore any rights, title and interest in our trademarks remain entirely with us.
00023 *
00024 * However, if you propagate an unmodified version of the Program you are
00025 * allowed to use the term "Z-Push" to indicate that you distribute the Program.
00026 * Furthermore you may use our trademarks where it is necessary to indicate
00027 * the intended purpose of a product or service provided you use it in accordance
00028 * with honest practices in industrial or commercial matters.
00029 * If you want to propagate modified versions of the Program under the name "Z-Push",
00030 * you may only do so if you have a written permission by Zarafa Deutschland GmbH
00031 * (to acquire a permission please contact Zarafa at trademark@zarafa.com).
00032 *
00033 * This program is distributed in the hope that it will be useful,
00034 * but WITHOUT ANY WARRANTY; without even the implied warranty of
00035 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00036 * GNU Affero General Public License for more details.
00037 *
00038 * You should have received a copy of the GNU Affero General Public License
00039 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
00040 *
00041 * Consult LICENSE file for details
00042 ************************************************/
00043 
00044 class Sync extends RequestProcessor {
00045     private $importer;
00046 
00056     public function Handle($commandCode) {
00057         // Contains all requested folders (containers)
00058         $sc = new SyncCollections();
00059         $status = SYNC_STATUS_SUCCESS;
00060         $wbxmlproblem = false;
00061         $emtpysync = false;
00062 
00063         // Start Synchronize
00064         if(self::$decoder->getElementStartTag(SYNC_SYNCHRONIZE)) {
00065 
00066             // AS 1.0 sends version information in WBXML
00067             if(self::$decoder->getElementStartTag(SYNC_VERSION)) {
00068                 $sync_version = self::$decoder->getElementContent();
00069                 ZLog::Write(LOGLEVEL_DEBUG, sprintf("WBXML sync version: '%s'", $sync_version));
00070                 if(!self::$decoder->getElementEndTag())
00071                     return false;
00072             }
00073 
00074             // Synching specified folders
00075             if(self::$decoder->getElementStartTag(SYNC_FOLDERS)) {
00076                 while(self::$decoder->getElementStartTag(SYNC_FOLDER)) {
00077                     $actiondata = array();
00078                     $actiondata["requested"] = true;
00079                     $actiondata["clientids"] = array();
00080                     $actiondata["modifyids"] = array();
00081                     $actiondata["removeids"] = array();
00082                     $actiondata["fetchids"] = array();
00083                     $actiondata["statusids"] = array();
00084 
00085                     // read class, synckey and folderid without SyncParameters Object for now
00086                     $class = $synckey = $folderid = false;
00087 
00088                     //for AS versions < 2.5
00089                     if(self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) {
00090                         $class = self::$decoder->getElementContent();
00091                         ZLog::Write(LOGLEVEL_DEBUG, sprintf("Sync folder: '%s'", $class));
00092 
00093                         if(!self::$decoder->getElementEndTag())
00094                             return false;
00095                     }
00096 
00097                     // SyncKey
00098                     if(!self::$decoder->getElementStartTag(SYNC_SYNCKEY))
00099                         return false;
00100                     $synckey = self::$decoder->getElementContent();
00101                     if(!self::$decoder->getElementEndTag())
00102                         return false;
00103 
00104                     // FolderId
00105                     if(self::$decoder->getElementStartTag(SYNC_FOLDERID)) {
00106                         $folderid = self::$decoder->getElementContent();
00107 
00108                         if(!self::$decoder->getElementEndTag())
00109                             return false;
00110                     }
00111 
00112                     // compatibility mode AS 1.0 - get folderid which was sent during GetHierarchy()
00113                     if (! $folderid && $class) {
00114                         $folderid = self::$deviceManager->GetFolderIdFromCacheByClass($class);
00115                     }
00116 
00117                     // folderid HAS TO BE known by now, so we retrieve the correct SyncParameters object for an update
00118                     try {
00119                         $spa = self::$deviceManager->GetStateManager()->GetSynchedFolderState($folderid);
00120 
00121                         // TODO remove resync of folders for < Z-Push 2 beta4 users
00122                         // this forces a resync of all states previous to Z-Push 2 beta4
00123                         if (! $spa instanceof SyncParameters)
00124                             throw new StateInvalidException("Saved state are not of type SyncParameters");
00125 
00126                         // new/resync requested
00127                         if ($synckey == "0")
00128                             $spa->RemoveSyncKey();
00129                         else if ($synckey !== false)
00130                             $spa->SetSyncKey($synckey);
00131                     }
00132                     catch (StateInvalidException $stie) {
00133                         $spa = new SyncParameters();
00134                         $status = SYNC_STATUS_INVALIDSYNCKEY;
00135                         self::$topCollector->AnnounceInformation("State invalid - Resync folder", true);
00136                         self::$deviceManager->ForceFolderResync($folderid);
00137                     }
00138 
00139                     // update folderid.. this might be a new object
00140                     $spa->SetFolderId($folderid);
00141 
00142                     if ($class !== false)
00143                         $spa->SetContentClass($class);
00144 
00145                     // Get class for as versions >= 12.0
00146                     if (! $spa->HasContentClass()) {
00147                         try {
00148                             $spa->SetContentClass(self::$deviceManager->GetFolderClassFromCacheByID($spa->GetFolderId()));
00149                             ZLog::Write(LOGLEVEL_DEBUG, sprintf("GetFolderClassFromCacheByID from Device Manager: '%s' for id:'%s'", $spa->GetContentClass(), $spa->GetFolderId()));
00150                         }
00151                         catch (NoHierarchyCacheAvailableException $nhca) {
00152                             $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED;
00153                             self::$deviceManager->ForceFullResync();
00154                         }
00155                     }
00156 
00157                     // done basic SPA initialization/loading -> add to SyncCollection
00158                     $sc->AddCollection($spa);
00159                     $sc->AddParameter($spa, "requested", true);
00160 
00161                     if ($spa->HasContentClass())
00162                         self::$topCollector->AnnounceInformation(sprintf("%s request", $spa->GetContentClass()), true);
00163                     else
00164                         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.");
00165 
00166                     // SUPPORTED properties
00167                     if(self::$decoder->getElementStartTag(SYNC_SUPPORTED)) {
00168                         $supfields = array();
00169                         while(1) {
00170                             $el = self::$decoder->getElement();
00171 
00172                             if($el[EN_TYPE] == EN_TYPE_ENDTAG)
00173                                 break;
00174                             else
00175                                 $supfields[] = $el[EN_TAG];
00176                         }
00177                         self::$deviceManager->SetSupportedFields($spa->GetFolderId(), $supfields);
00178                     }
00179 
00180                     // Deletes as moves can be an empty tag as well as have value
00181                     if(self::$decoder->getElementStartTag(SYNC_DELETESASMOVES)) {
00182                         $spa->SetDeletesAsMoves(true);
00183                         if (($dam = self::$decoder->getElementContent()) !== false) {
00184                             $spa->SetDeletesAsMoves((boolean)$dam);
00185                             if(!self::$decoder->getElementEndTag()) {
00186                                 return false;
00187                             }
00188                         }
00189                     }
00190 
00191                     // Get changes can be an empty tag as well as have value
00192                     // code block partly contributed by dw2412
00193                     if(self::$decoder->getElementStartTag(SYNC_GETCHANGES)) {
00194                         $sc->AddParameter($spa, "getchanges", true);
00195                         if (($gc = self::$decoder->getElementContent()) !== false) {
00196                             $sc->AddParameter($spa, "getchanges", $gc);
00197                             if(!self::$decoder->getElementEndTag()) {
00198                                 return false;
00199                             }
00200                         }
00201                     }
00202 
00203                     if(self::$decoder->getElementStartTag(SYNC_WINDOWSIZE)) {
00204                         $spa->SetWindowSize(self::$decoder->getElementContent());
00205 
00206                         // also announce the currently requested window size to the DeviceManager
00207                         self::$deviceManager->SetWindowSize($spa->GetFolderId(), $spa->GetWindowSize());
00208 
00209                         if(!self::$decoder->getElementEndTag())
00210                             return false;
00211                     }
00212 
00213                     // conversation mode requested
00214                     if(self::$decoder->getElementStartTag(SYNC_CONVERSATIONMODE)) {
00215                         $spa->SetConversationMode(true);
00216                         if(($conversationmode = self::$decoder->getElementContent()) !== false) {
00217                             $spa->SetConversationMode((boolean)$conversationmode);
00218                             if(!self::$decoder->getElementEndTag())
00219                             return false;
00220                         }
00221                     }
00222 
00223                     // Do not truncate by default
00224                     $spa->SetTruncation(SYNC_TRUNCATION_ALL);
00225                     // set to synchronize all changes. The mobile could overwrite this value
00226                     $spa->SetFilterType(SYNC_FILTERTYPE_ALL);
00227 
00228                     while(self::$decoder->getElementStartTag(SYNC_OPTIONS)) {
00229                         while(1) {
00230                             if(self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) {
00231                                 $foldertype = self::$decoder->getElementContent();
00232                                 ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): specified options block with foldertype '%s'", $foldertype));
00233 
00234                                 // switch the foldertype for the next options
00235                                 $spa->UseCPO($foldertype);
00236                                 if(!self::$decoder->getElementEndTag())
00237                                 return false;
00238                             }
00239 
00240                             if(self::$decoder->getElementStartTag(SYNC_FILTERTYPE)) {
00241                                 $spa->SetFilterType(self::$decoder->getElementContent());
00242                                 if(!self::$decoder->getElementEndTag())
00243                                     return false;
00244                             }
00245                             if(self::$decoder->getElementStartTag(SYNC_TRUNCATION)) {
00246                                 $spa->SetTruncation(self::$decoder->getElementContent());
00247                                 if(!self::$decoder->getElementEndTag())
00248                                     return false;
00249                             }
00250                             if(self::$decoder->getElementStartTag(SYNC_RTFTRUNCATION)) {
00251                                 $spa->SetRTFTruncation(self::$decoder->getElementContent());
00252                                 if(!self::$decoder->getElementEndTag())
00253                                     return false;
00254                             }
00255 
00256                             if(self::$decoder->getElementStartTag(SYNC_MIMESUPPORT)) {
00257                                 $spa->SetMimeSupport(self::$decoder->getElementContent());
00258                                 if(!self::$decoder->getElementEndTag())
00259                                     return false;
00260                             }
00261 
00262                             if(self::$decoder->getElementStartTag(SYNC_MIMETRUNCATION)) {
00263                                 $spa->SetMimeTruncation(self::$decoder->getElementContent());
00264                                 if(!self::$decoder->getElementEndTag())
00265                                     return false;
00266                             }
00267 
00268                             if(self::$decoder->getElementStartTag(SYNC_CONFLICT)) {
00269                                 $spa->SetConflict(self::$decoder->getElementContent());
00270                                 if(!self::$decoder->getElementEndTag())
00271                                     return false;
00272                             }
00273 
00274                             while (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_BODYPREFERENCE)) {
00275                                 if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TYPE)) {
00276                                     $bptype = self::$decoder->getElementContent();
00277                                     $spa->BodyPreference($bptype);
00278                                     if(!self::$decoder->getElementEndTag()) {
00279                                         return false;
00280                                     }
00281                                 }
00282 
00283                                 if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TRUNCATIONSIZE)) {
00284                                     $spa->BodyPreference($bptype)->SetTruncationSize(self::$decoder->getElementContent());
00285                                     if(!self::$decoder->getElementEndTag())
00286                                         return false;
00287                                 }
00288 
00289                                 if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_ALLORNONE)) {
00290                                     $spa->BodyPreference($bptype)->SetAllOrNone(self::$decoder->getElementContent());
00291                                     if(!self::$decoder->getElementEndTag())
00292                                         return false;
00293                                 }
00294 
00295                                 if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_PREVIEW)) {
00296                                     $spa->BodyPreference($bptype)->SetPreview(self::$decoder->getElementContent());
00297                                     if(!self::$decoder->getElementEndTag())
00298                                         return false;
00299                                 }
00300 
00301                                 if(!self::$decoder->getElementEndTag())
00302                                     return false;
00303                             }
00304 
00305                             $e = self::$decoder->peek();
00306                             if($e[EN_TYPE] == EN_TYPE_ENDTAG) {
00307                                 self::$decoder->getElementEndTag();
00308                                 break;
00309                             }
00310                         }
00311                     }
00312 
00313                     // limit items to be synchronized to the mobiles if configured
00314                     if (defined('SYNC_FILTERTIME_MAX') && SYNC_FILTERTIME_MAX > SYNC_FILTERTYPE_ALL &&
00315                         (!$spa->HasFilterType() || $spa->GetFilterType() == SYNC_FILTERTYPE_ALL || $spa->GetFilterType() > SYNC_FILTERTIME_MAX)) {
00316                             ZLog::Write(LOGLEVEL_DEBUG, sprintf("SYNC_FILTERTIME_MAX defined. Filter set to value: %s", SYNC_FILTERTIME_MAX));
00317                             $spa->SetFilterType(SYNC_FILTERTIME_MAX);
00318                     }
00319 
00320                     // set default conflict behavior from config if the device doesn't send a conflict resolution parameter
00321                     if (! $spa->HasConflict()) {
00322                         $spa->SetConflict(SYNC_CONFLICT_DEFAULT);
00323                     }
00324 
00325                     // Check if the hierarchycache is available. If not, trigger a HierarchySync
00326                     if (self::$deviceManager->IsHierarchySyncRequired()) {
00327                         $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED;
00328                         ZLog::Write(LOGLEVEL_DEBUG, "HierarchyCache is also not available. Triggering HierarchySync to device");
00329                     }
00330 
00331                     if(self::$decoder->getElementStartTag(SYNC_PERFORM)) {
00332                         // We can not proceed here as the content class is unknown
00333                         if ($status != SYNC_STATUS_SUCCESS) {
00334                             ZLog::Write(LOGLEVEL_WARN, "Ignoring all incoming actions as global status indicates problem.");
00335                             $wbxmlproblem = true;
00336                             break;
00337                         }
00338 
00339                         $performaction = true;
00340 
00341                         // unset the importer
00342                         $this->importer = false;
00343 
00344                         $nchanges = 0;
00345                         while(1) {
00346                             // ADD, MODIFY, REMOVE or FETCH
00347                             $element = self::$decoder->getElement();
00348 
00349                             if($element[EN_TYPE] != EN_TYPE_STARTTAG) {
00350                                 self::$decoder->ungetElement($element);
00351                                 break;
00352                             }
00353 
00354                             if ($status == SYNC_STATUS_SUCCESS)
00355                                 $nchanges++;
00356 
00357                             if(self::$decoder->getElementStartTag(SYNC_SERVERENTRYID)) {
00358                                 $serverid = self::$decoder->getElementContent();
00359 
00360                                 if(!self::$decoder->getElementEndTag()) // end serverid
00361                                     return false;
00362                             }
00363                             else
00364                                 $serverid = false;
00365 
00366                             if(self::$decoder->getElementStartTag(SYNC_CLIENTENTRYID)) {
00367                                 $clientid = self::$decoder->getElementContent();
00368 
00369                                 if(!self::$decoder->getElementEndTag()) // end clientid
00370                                     return false;
00371                             }
00372                             else
00373                                 $clientid = false;
00374 
00375                             // Get the SyncMessage if sent
00376                             if(self::$decoder->getElementStartTag(SYNC_DATA)) {
00377                                 $message = ZPush::getSyncObjectFromFolderClass($spa->GetContentClass());
00378                                 $message->Decode(self::$decoder);
00379 
00380                                 // set Ghosted fields
00381                                 $message->emptySupported(self::$deviceManager->GetSupportedFields($spa->GetFolderId()));
00382                                 if(!self::$decoder->getElementEndTag()) // end applicationdata
00383                                     return false;
00384                             }
00385                             else
00386                                 $message = false;
00387 
00388                             if ($status != SYNC_STATUS_SUCCESS) {
00389                                 ZLog::Write(LOGLEVEL_WARN, "Ignored incoming change, global status indicates problem.");
00390                                 continue;
00391                             }
00392 
00393                             switch($element[EN_TAG]) {
00394                                 case SYNC_FETCH:
00395                                     array_push($actiondata["fetchids"], $serverid);
00396                                     break;
00397                                 default:
00398                                     // get the importer
00399                                     if ($this->importer == false)
00400                                         $status = $this->getImporter($sc, $spa, $actiondata);
00401 
00402                                     if ($status == SYNC_STATUS_SUCCESS)
00403                                         $this->importMessage($spa, $actiondata, $element[EN_TAG], $message, $clientid, $serverid);
00404 
00405                                     break;
00406                             }
00407 
00408                             if ($actiondata["fetchids"])
00409                                 self::$topCollector->AnnounceInformation(sprintf("Fetching %d", $nchanges),true);
00410                             else
00411                                 self::$topCollector->AnnounceInformation(sprintf("Incoming %d", $nchanges),($nchanges>0)?true:false);
00412 
00413                             if(!self::$decoder->getElementEndTag()) // end add/change/delete/move
00414                                 return false;
00415                         }
00416 
00417                         if ($status == SYNC_STATUS_SUCCESS && $this->importer !== false) {
00418                             ZLog::Write(LOGLEVEL_INFO, sprintf("Processed '%d' incoming changes", $nchanges));
00419                             try {
00420                                 // Save the updated state, which is used for the exporter later
00421                                 $sc->AddParameter($spa, "state", $this->importer->GetState());
00422                             }
00423                             catch (StatusException $stex) {
00424                                $status = $stex->getCode();
00425                             }
00426                         }
00427 
00428                         if(!self::$decoder->getElementEndTag()) // end PERFORM
00429                             return false;
00430                     }
00431 
00432                     // save the failsave state
00433                     if (!empty($actiondata["statusids"])) {
00434                         unset($actiondata["failstate"]);
00435                         $actiondata["failedsyncstate"] = $sc->GetParameter($spa, "state");
00436                         self::$deviceManager->GetStateManager()->SetSyncFailState($actiondata);
00437                     }
00438 
00439                     // save actiondata
00440                     $sc->AddParameter($spa, "actiondata", $actiondata);
00441 
00442                     if(!self::$decoder->getElementEndTag()) // end collection
00443                         return false;
00444 
00445                     // AS14 does not send GetChanges anymore. We should do it if there were no incoming changes
00446                     if (!isset($performaction) && !$sc->GetParameter($spa, "getchanges") && $spa->HasSyncKey())
00447                         $sc->AddParameter($spa, "getchanges", true);
00448                 } // END FOLDER
00449 
00450                 if(!$wbxmlproblem && !self::$decoder->getElementEndTag()) // end collections
00451                     return false;
00452             } // end FOLDERS
00453 
00454             if (self::$decoder->getElementStartTag(SYNC_HEARTBEATINTERVAL)) {
00455                 $hbinterval = self::$decoder->getElementContent();
00456                 if(!self::$decoder->getElementEndTag()) // SYNC_HEARTBEATINTERVAL
00457                     return false;
00458             }
00459 
00460             if (self::$decoder->getElementStartTag(SYNC_WAIT)) {
00461                 $wait = self::$decoder->getElementContent();
00462                 if(!self::$decoder->getElementEndTag()) // SYNC_WAIT
00463                     return false;
00464 
00465                 // internally the heartbeat interval and the wait time are the same
00466                 // heartbeat is in seconds, wait in minutes
00467                 $hbinterval = $wait * 60;
00468             }
00469 
00470             if (self::$decoder->getElementStartTag(SYNC_WINDOWSIZE)) {
00471                 $sc->SetGlobalWindowSize(self::$decoder->getElementContent());
00472                 if(!self::$decoder->getElementEndTag()) // SYNC_WINDOWSIZE
00473                     return false;
00474             }
00475 
00476             if(self::$decoder->getElementStartTag(SYNC_PARTIAL))
00477                 $partial = true;
00478             else
00479                 $partial = false;
00480 
00481             if(!$wbxmlproblem && !self::$decoder->getElementEndTag()) // end sync
00482                 return false;
00483         }
00484         // we did not receive a SYNCHRONIZE block - assume empty sync
00485         else {
00486             $emtpysync = true;
00487         }
00488         // END SYNCHRONIZE
00489 
00490         // check heartbeat/wait time
00491         if (isset($hbinterval)) {
00492             if ($hbinterval < 60 || $hbinterval > 3540) {
00493                 $status = SYNC_STATUS_INVALIDWAITORHBVALUE;
00494                 ZLog::Write(LOGLEVEL_WARN, sprintf("HandleSync(): Invalid heartbeat or wait value '%s'", $hbinterval));
00495             }
00496         }
00497 
00498         // Partial & Empty Syncs need saved data to proceed with synchronization
00499         if ($status == SYNC_STATUS_SUCCESS && (! $sc->HasCollections() || $partial === true )) {
00500             ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Partial or Empty sync requested. Retrieving data of synchronized folders."));
00501 
00502             // Load all collections - do not overwrite existing (received!), laod states and check permissions
00503             try {
00504                 $sc->LoadAllCollections(false, true, true);
00505             }
00506             catch (StateNotFoundException $snfex) {
00507                 $status = SYNC_STATUS_INVALIDSYNCKEY;
00508                 self::$topCollector->AnnounceInformation("StateNotFoundException", true);
00509             }
00510             catch (StatusException $stex) {
00511                $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED;
00512                self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), true);
00513             }
00514 
00515             // update a few values
00516             foreach($sc as $folderid => $spa) {
00517                 // manually set getchanges parameter for this collection
00518                 $sc->AddParameter($spa, "getchanges", true);
00519 
00520                 // set new global windowsize without marking the SPA as changed
00521                 if ($sc->GetGlobalWindowSize())
00522                     $spa->SetWindowSize($sc->GetGlobalWindowSize(), false);
00523 
00524                 // announce WindowSize to DeviceManager
00525                 self::$deviceManager->SetWindowSize($folderid, $spa->GetWindowSize());
00526             }
00527             if (!$sc->HasCollections())
00528                 $status = SYNC_STATUS_SYNCREQUESTINCOMPLETE;
00529         }
00530 
00531         // HEARTBEAT & Empty sync
00532         if ($status == SYNC_STATUS_SUCCESS && (isset($hbinterval) || $emtpysync == true)) {
00533             $interval = (defined('PING_INTERVAL') && PING_INTERVAL > 0) ? PING_INTERVAL : 30;
00534 
00535             if (isset($hbinterval))
00536                 $sc->SetLifetime($hbinterval);
00537 
00538             // states are lazy loaded - we have to make sure that they are there!
00539             foreach($sc as $folderid => $spa) {
00540                 $fad = array();
00541                 // if loading the states fails, we do not enter heartbeat, but we keep $status on SYNC_STATUS_SUCCESS
00542                 // so when the changes are exported the correct folder gets an SYNC_STATUS_INVALIDSYNCKEY
00543                 $loadstatus = $this->loadStates($sc, $spa, $fad);
00544             }
00545 
00546             if ($loadstatus = SYNC_STATUS_SUCCESS) {
00547                 $foundchanges = false;
00548 
00549                 // wait for changes
00550                 try {
00551                     // if doing an empty sync, check only once for changes
00552                     if ($emtpysync) {
00553                         $foundchanges = $sc->CountChanges();
00554                     }
00555                     // wait for changes
00556                     else {
00557                         ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Entering Heartbeat mode"));
00558                         $foundchanges = $sc->CheckForChanges($sc->GetLifetime(), $interval);
00559                     }
00560                 }
00561                 catch (StatusException $stex) {
00562                    $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED;
00563                    self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), true);
00564                 }
00565 
00566                 // in case of an empty sync with no changes, we can reply with an empty response
00567                 if ($emtpysync && !$foundchanges){
00568                     ZLog::Write(LOGLEVEL_DEBUG, "No changes found for empty sync. Replying with empty response");
00569                     return true;
00570                 }
00571 
00572                 if ($foundchanges) {
00573                     foreach ($sc->GetChangedFolderIds() as $folderid => $changecount) {
00574                         // check if there were other sync requests for a folder during the heartbeat
00575                         $spa = $sc->GetCollection($folderid);
00576                         if ($changecount > 0 && $sc->WaitedForChanges() && self::$deviceManager->CheckHearbeatStateIntegrity($spa->GetFolderId(), $spa->GetUuid(), $spa->GetUuidCounter())) {
00577                             ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): heartbeat: found %d changes in '%s' which was already synchronized. Heartbeat aborted!", $changecount, $folderid));
00578                             $status = SYNC_COMMONSTATUS_SYNCSTATEVERSIONINVALID;
00579                         }
00580                         else
00581                             ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): heartbeat: found %d changes in '%s'", $changecount, $folderid));
00582                     }
00583                 }
00584             }
00585         }
00586 
00587         ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Start Output"));
00588 
00589         // Start the output
00590         self::$encoder->startWBXML();
00591         self::$encoder->startTag(SYNC_SYNCHRONIZE);
00592         {
00593             // global status
00594             // SYNC_COMMONSTATUS_* start with values from 101
00595             if ($status != SYNC_COMMONSTATUS_SUCCESS && $status > 100) {
00596                 self::$encoder->startTag(SYNC_STATUS);
00597                     self::$encoder->content($status);
00598                 self::$encoder->endTag();
00599             }
00600             else {
00601                 self::$encoder->startTag(SYNC_FOLDERS);
00602                 {
00603                     foreach($sc as $folderid => $spa) {
00604                         // get actiondata
00605                         $actiondata = $sc->GetParameter($spa, "actiondata");
00606 
00607                         if ($status == SYNC_STATUS_SUCCESS && (!$spa->GetContentClass() || !$spa->GetFolderId())) {
00608                             ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): no content class or folderid found for collection."));
00609                             continue;
00610                         }
00611 
00612                         if (! $sc->GetParameter($spa, "requested"))
00613                             ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): partial sync for folder class '%s' with id '%s'", $spa->GetContentClass(), $spa->GetFolderId()));
00614 
00615                         // initialize exporter to get changecount
00616                         $changecount = 0;
00617                         if (isset($exporter))
00618                             unset($exporter);
00619 
00620                         // TODO we could check against $sc->GetChangedFolderIds() on heartbeat so we do not need to configure all exporter again
00621                         if($status == SYNC_STATUS_SUCCESS && ($sc->GetParameter($spa, "getchanges") || ! $spa->HasSyncKey())) {
00622 
00623                             //make sure the states are loaded
00624                             $status = $this->loadStates($sc, $spa, $actiondata);
00625 
00626                             if($status == SYNC_STATUS_SUCCESS) {
00627                                 try {
00628                                     // Use the state from the importer, as changes may have already happened
00629                                     $exporter = self::$backend->GetExporter($spa->GetFolderId());
00630 
00631                                     if ($exporter === false)
00632                                         throw new StatusException(sprintf("HandleSync() could not get an exporter for folder id '%s'", $spa->GetFolderId()), SYNC_STATUS_FOLDERHIERARCHYCHANGED);
00633                                 }
00634                                 catch (StatusException $stex) {
00635                                    $status = $stex->getCode();
00636                                 }
00637                                 try {
00638                                     // Stream the messages directly to the PDA
00639                                     $streamimporter = new ImportChangesStream(self::$encoder, ZPush::getSyncObjectFromFolderClass($spa->GetContentClass()));
00640 
00641                                     if ($exporter !== false) {
00642                                         $exporter->Config($sc->GetParameter($spa, "state"));
00643                                         $exporter->ConfigContentParameters($spa->GetCPO());
00644                                         $exporter->InitializeExporter($streamimporter);
00645 
00646                                         $changecount = $exporter->GetChangeCount();
00647                                     }
00648                                 }
00649                                 catch (StatusException $stex) {
00650                                     if ($stex->getCode() === SYNC_FSSTATUS_CODEUNKNOWN && $spa->HasSyncKey())
00651                                         $status = SYNC_STATUS_INVALIDSYNCKEY;
00652                                     else
00653                                         $status = $stex->getCode();
00654                                 }
00655 
00656                                 if (! $spa->HasSyncKey())
00657                                     self::$topCollector->AnnounceInformation(sprintf("Exporter registered. %d objects queued.", $changecount), true);
00658                                 else if ($status != SYNC_STATUS_SUCCESS)
00659                                     self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), true);
00660                             }
00661                         }
00662 
00663                         if (isset($hbinterval) && $changecount == 0 && $status == SYNC_STATUS_SUCCESS) {
00664                             ZLog::Write(LOGLEVEL_DEBUG, "No changes found for heartbeat folder. Omitting empty output.");
00665                             continue;
00666                         }
00667 
00668                         // Get a new sync key to output to the client if any changes have been send or will are available
00669                         if (!empty($actiondata["modifyids"]) ||
00670                             !empty($actiondata["clientids"]) ||
00671                             !empty($actiondata["removeids"]) ||
00672                             $changecount > 0 || (! $spa->HasSyncKey() && $status == SYNC_STATUS_SUCCESS))
00673                                 $spa->SetNewSyncKey(self::$deviceManager->GetStateManager()->GetNewSyncKey($spa->GetSyncKey()));
00674 
00675                         self::$encoder->startTag(SYNC_FOLDER);
00676 
00677                         if($spa->HasContentClass()) {
00678                             self::$encoder->startTag(SYNC_FOLDERTYPE);
00679                                 self::$encoder->content($spa->GetContentClass());
00680                             self::$encoder->endTag();
00681                         }
00682 
00683                         self::$encoder->startTag(SYNC_SYNCKEY);
00684                         if($status == SYNC_STATUS_SUCCESS && $spa->HasNewSyncKey())
00685                             self::$encoder->content($spa->GetNewSyncKey());
00686                         else
00687                             self::$encoder->content($spa->GetSyncKey());
00688                         self::$encoder->endTag();
00689 
00690                         self::$encoder->startTag(SYNC_FOLDERID);
00691                             self::$encoder->content($spa->GetFolderId());
00692                         self::$encoder->endTag();
00693 
00694                         self::$encoder->startTag(SYNC_STATUS);
00695                             self::$encoder->content($status);
00696                         self::$encoder->endTag();
00697 
00698                         // announce failing status to the process loop detection
00699                         if ($status !== SYNC_STATUS_SUCCESS)
00700                             self::$deviceManager->AnnounceProcessStatus($spa->GetFolderId(), $status);
00701 
00702                         // Output IDs and status for incoming items & requests
00703                         if($status == SYNC_STATUS_SUCCESS && (
00704                             !empty($actiondata["clientids"]) ||
00705                             !empty($actiondata["modifyids"]) ||
00706                             !empty($actiondata["removeids"]) ||
00707                             !empty($actiondata["fetchids"]) )) {
00708 
00709                             self::$encoder->startTag(SYNC_REPLIES);
00710                             // output result of all new incoming items
00711                             foreach($actiondata["clientids"] as $clientid => $serverid) {
00712                                 self::$encoder->startTag(SYNC_ADD);
00713                                     self::$encoder->startTag(SYNC_CLIENTENTRYID);
00714                                         self::$encoder->content($clientid);
00715                                     self::$encoder->endTag();
00716                                     if ($serverid) {
00717                                         self::$encoder->startTag(SYNC_SERVERENTRYID);
00718                                             self::$encoder->content($serverid);
00719                                         self::$encoder->endTag();
00720                                     }
00721                                     self::$encoder->startTag(SYNC_STATUS);
00722                                         self::$encoder->content((isset($actiondata["statusids"][$clientid])?$actiondata["statusids"][$clientid]:SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR));
00723                                     self::$encoder->endTag();
00724                                 self::$encoder->endTag();
00725                             }
00726 
00727                             // loop through modify operations which were not a success, send status
00728                             foreach($actiondata["modifyids"] as $serverid) {
00729                                 if (isset($actiondata["statusids"][$serverid]) && $actiondata["statusids"][$serverid] !== SYNC_STATUS_SUCCESS) {
00730                                     self::$encoder->startTag(SYNC_MODIFY);
00731                                         self::$encoder->startTag(SYNC_SERVERENTRYID);
00732                                             self::$encoder->content($serverid);
00733                                         self::$encoder->endTag();
00734                                         self::$encoder->startTag(SYNC_STATUS);
00735                                             self::$encoder->content($actiondata["statusids"][$serverid]);
00736                                         self::$encoder->endTag();
00737                                     self::$encoder->endTag();
00738                                 }
00739                             }
00740 
00741                             // loop through remove operations which were not a success, send status
00742                             foreach($actiondata["removeids"] as $serverid) {
00743                                 if (isset($actiondata["statusids"][$serverid]) && $actiondata["statusids"][$serverid] !== SYNC_STATUS_SUCCESS) {
00744                                     self::$encoder->startTag(SYNC_REMOVE);
00745                                         self::$encoder->startTag(SYNC_SERVERENTRYID);
00746                                             self::$encoder->content($serverid);
00747                                         self::$encoder->endTag();
00748                                         self::$encoder->startTag(SYNC_STATUS);
00749                                             self::$encoder->content($actiondata["statusids"][$serverid]);
00750                                         self::$encoder->endTag();
00751                                     self::$encoder->endTag();
00752                                 }
00753                             }
00754 
00755                             if (!empty($actiondata["fetchids"]))
00756                                 self::$topCollector->AnnounceInformation(sprintf("Fetching %d objects ", count($actiondata["fetchids"])), true);
00757 
00758                             foreach($actiondata["fetchids"] as $id) {
00759                                 $data = false;
00760                                 try {
00761                                     $fetchstatus = SYNC_STATUS_SUCCESS;
00762                                     $data = self::$backend->Fetch($spa->GetFolderId(), $id, $spa->GetCPO());
00763 
00764                                     // check if the message is broken
00765                                     if (ZPush::GetDeviceManager(false) && ZPush::GetDeviceManager()->DoNotStreamMessage($id, $data)) {
00766                                         ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): message not to be streamed as requested by DeviceManager.", $id));
00767                                         $fetchstatus = SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR;
00768                                     }
00769                                 }
00770                                 catch (StatusException $stex) {
00771                                    $fetchstatus = $stex->getCode();
00772                                 }
00773 
00774                                 self::$encoder->startTag(SYNC_FETCH);
00775                                     self::$encoder->startTag(SYNC_SERVERENTRYID);
00776                                         self::$encoder->content($id);
00777                                     self::$encoder->endTag();
00778 
00779                                     self::$encoder->startTag(SYNC_STATUS);
00780                                         self::$encoder->content($fetchstatus);
00781                                     self::$encoder->endTag();
00782 
00783                                     if($data !== false && $status == SYNC_STATUS_SUCCESS) {
00784                                         self::$encoder->startTag(SYNC_DATA);
00785                                             $data->Encode(self::$encoder);
00786                                         self::$encoder->endTag();
00787                                     }
00788                                     else
00789                                         ZLog::Write(LOGLEVEL_WARN, sprintf("Unable to Fetch '%s'", $id));
00790                                 self::$encoder->endTag();
00791 
00792                             }
00793                             self::$encoder->endTag();
00794                         }
00795 
00796                         if($sc->GetParameter($spa, "getchanges") && $spa->HasFolderId() && $spa->HasContentClass() && $spa->HasSyncKey()) {
00797                             $windowSize = self::$deviceManager->GetWindowSize($spa->GetFolderId(), $spa->GetContentClass(), $spa->GetUuid(), $spa->GetUuidCounter(), $changecount);
00798 
00799                             if($changecount > $windowSize) {
00800                                 self::$encoder->startTag(SYNC_MOREAVAILABLE, false, true);
00801                             }
00802                         }
00803 
00804                         // Stream outgoing changes
00805                         if($status == SYNC_STATUS_SUCCESS && $sc->GetParameter($spa, "getchanges") === true && $windowSize > 0) {
00806                             self::$topCollector->AnnounceInformation(sprintf("Streaming data of %d objects", (($changecount > $windowSize)?$windowSize:$changecount)));
00807 
00808                             // Output message changes per folder
00809                             self::$encoder->startTag(SYNC_PERFORM);
00810 
00811                             $n = 0;
00812                             while(1) {
00813                                 try {
00814                                     $progress = $exporter->Synchronize();
00815                                     if(!is_array($progress))
00816                                         break;
00817                                     $n++;
00818                                 }
00819                                 catch (SyncObjectBrokenException $mbe) {
00820                                     $brokenSO = $mbe->GetSyncObject();
00821                                     if (!$brokenSO) {
00822                                         ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): Catched SyncObjectBrokenException but broken SyncObject not available. This should be fixed in the backend."));
00823                                     }
00824                                     else {
00825                                         if (!isset($brokenSO->id)) {
00826                                             $brokenSO->id = "Unknown ID";
00827                                             ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): Catched SyncObjectBrokenException but no ID of object set. This should be fixed in the backend."));
00828                                         }
00829                                         self::$deviceManager->AnnounceIgnoredMessage($spa->GetFolderId(), $brokenSO->id, $brokenSO);
00830                                     }
00831                                 }
00832 
00833                                 if($n >= $windowSize) {
00834                                     ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Exported maxItems of messages: %d / %d", $n, $changecount));
00835                                     break;
00836                                 }
00837 
00838                             }
00839 
00840                             // $progress is not an array when exporting the last message
00841                             // so we get the number to display from the streamimporter
00842                             if (isset($streamimporter)) {
00843                                 $n = $streamimporter->GetImportedMessages();
00844                             }
00845 
00846                             self::$encoder->endTag();
00847                             self::$topCollector->AnnounceInformation(sprintf("Outgoing %d objects%s", $n, ($n >= $windowSize)?" of ".$changecount:""), true);
00848                         }
00849 
00850                         self::$encoder->endTag();
00851 
00852                         // Save the sync state for the next time
00853                         if($spa->HasNewSyncKey()) {
00854                             self::$topCollector->AnnounceInformation("Saving state");
00855 
00856                             try {
00857                                 if (isset($exporter) && $exporter)
00858                                     $state = $exporter->GetState();
00859 
00860                                 // nothing exported, but possibly imported - get the importer state
00861                                 else if ($sc->GetParameter($spa, "state") !== null)
00862                                     $state = $sc->GetParameter($spa, "state");
00863 
00864                                 // if a new request without state information (hierarchy) save an empty state
00865                                 else if (! $spa->HasSyncKey())
00866                                     $state = "";
00867                             }
00868                             catch (StatusException $stex) {
00869                                $status = $stex->getCode();
00870                             }
00871 
00872 
00873                             if (isset($state) && $status == SYNC_STATUS_SUCCESS)
00874                                 self::$deviceManager->GetStateManager()->SetSyncState($spa->GetNewSyncKey(), $state, $spa->GetFolderId());
00875                             else
00876                                 ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): error saving '%s' - no state information available", $spa->GetNewSyncKey()));
00877                         }
00878 
00879                         // save SyncParameters
00880                         if ($status == SYNC_STATUS_SUCCESS && empty($actiondata["fetchids"]))
00881                             $sc->SaveCollection($spa);
00882 
00883                     } // END foreach collection
00884                 }
00885                 self::$encoder->endTag(); //SYNC_FOLDERS
00886             }
00887         }
00888         self::$encoder->endTag(); //SYNC_SYNCHRONIZE
00889 
00890         return true;
00891     }
00892 
00904     private function loadStates($sc, $spa, &$actiondata, $loadFailsave = false) {
00905         $status = SYNC_STATUS_SUCCESS;
00906 
00907         if ($sc->GetParameter($spa, "state") == null) {
00908             ZLog::Write(LOGLEVEL_DEBUG, sprintf("Sync->loadStates(): loading states for folder '%s'",$spa->GetFolderId()));
00909 
00910             try {
00911                 $sc->AddParameter($spa, "state", self::$deviceManager->GetStateManager()->GetSyncState($spa->GetSyncKey()));
00912 
00913                 if ($loadFailsave) {
00914                     // if this request was made before, there will be a failstate available
00915                     $actiondata["failstate"] = self::$deviceManager->GetStateManager()->GetSyncFailState();
00916                 }
00917 
00918                 // if this is an additional folder the backend has to be setup correctly
00919                 if (!self::$backend->Setup(ZPush::GetAdditionalSyncFolderStore($spa->GetFolderId())))
00920                     throw new StatusException(sprintf("HandleSync() could not Setup() the backend for folder id '%s'", $spa->GetFolderId()), SYNC_STATUS_FOLDERHIERARCHYCHANGED);
00921             }
00922             catch (StateNotFoundException $snfex) {
00923                 $status = SYNC_STATUS_INVALIDSYNCKEY;
00924                 self::$topCollector->AnnounceInformation("StateNotFoundException", true);
00925             }
00926             catch (StatusException $stex) {
00927                $status = $stex->getCode();
00928                self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), true);
00929             }
00930         }
00931 
00932         return $status;
00933     }
00934 
00946     private function getImporter($sc, $spa, &$actiondata) {
00947         ZLog::Write(LOGLEVEL_DEBUG, "Sync->getImporter(): initialize importer");
00948         $status = SYNC_STATUS_SUCCESS;
00949 
00950         // load the states with failsave data
00951         $status = $this->loadStates($sc, $spa, $actiondata, true);
00952 
00953         try {
00954             // Configure importer with last state
00955             $this->importer = self::$backend->GetImporter($spa->GetFolderId());
00956 
00957             // if something goes wrong, ask the mobile to resync the hierarchy
00958             if ($this->importer === false)
00959                 throw new StatusException(sprintf("Sync->getImporter(): no importer for folder id '%s'", $spa->GetFolderId()), SYNC_STATUS_FOLDERHIERARCHYCHANGED);
00960 
00961             // if there is a valid state obtained after importing changes in a previous loop, we use that state
00962             if ($actiondata["failstate"] && isset($actiondata["failstate"]["failedsyncstate"])) {
00963                 $this->importer->Config($actiondata["failstate"]["failedsyncstate"], $spa->GetConflict());
00964             }
00965             else
00966                 $this->importer->Config($sc->GetParameter($spa, "state"), $spa->GetConflict());
00967         }
00968         catch (StatusException $stex) {
00969            $status = $stex->getCode();
00970         }
00971 
00972         $this->importer->LoadConflicts($spa->GetCPO(), $sc->GetParameter($spa, "state"));
00973 
00974         return $status;
00975     }
00976 
00992     private function importMessage($spa, &$actiondata, $todo, $message, $clientid, $serverid) {
00993         // the importer needs to be available!
00994         if ($this->importer == false)
00995             throw StatusException(sprintf("Sync->importMessage(): importer not available", SYNC_STATUS_SERVERERROR));
00996 
00997         // Detect incoming loop
00998         // messages which were created/removed before will not have the same action executed again
00999         // if a message is edited we perform this action "again", as the message could have been changed on the mobile in the meantime
01000         $ignoreMessage = false;
01001         if ($actiondata["failstate"]) {
01002             // message was ADDED before, do NOT add it again
01003             if ($todo == SYNC_ADD && $actiondata["failstate"]["clientids"][$clientid]) {
01004                 $ignoreMessage = true;
01005 
01006                 // make sure no messages are sent back
01007                 self::$deviceManager->SetWindowSize($spa->GetFolderId(), 0);
01008 
01009                 $actiondata["clientids"][$clientid] = $actiondata["failstate"]["clientids"][$clientid];
01010                 $actiondata["statusids"][$clientid] = $actiondata["failstate"]["statusids"][$clientid];
01011 
01012                 ZLog::Write(LOGLEVEL_WARN, sprintf("Mobile loop detected! Incoming new message '%s' was created on the server before. Replying with known new server id: %s", $clientid, $actiondata["clientids"][$clientid]));
01013             }
01014 
01015             // message was REMOVED before, do NOT attemp to remove it again
01016             if ($todo == SYNC_REMOVE && $actiondata["failstate"]["removeids"][$serverid]) {
01017                 $ignoreMessage = true;
01018 
01019                 // make sure no messages are sent back
01020                 self::$deviceManager->SetWindowSize($spa->GetFolderId(), 0);
01021 
01022                 $actiondata["removeids"][$serverid] = $actiondata["failstate"]["removeids"][$serverid];
01023                 $actiondata["statusids"][$serverid] = $actiondata["failstate"]["statusids"][$serverid];
01024 
01025                 ZLog::Write(LOGLEVEL_WARN, sprintf("Mobile loop detected! Message '%s' was deleted by the mobile before. Replying with known status: %s", $clientid, $actiondata["statusids"][$serverid]));
01026             }
01027         }
01028 
01029         if (!$ignoreMessage) {
01030             switch($todo) {
01031                 case SYNC_MODIFY:
01032                     try {
01033                         $actiondata["modifyids"][] = $serverid;
01034 
01035                         // check incoming message without logging WARN messages about errors
01036                         if (!($message instanceof SyncObject) || !$message->Check(true)) {
01037                             $actiondata["statusids"][$serverid] = SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR;
01038                         }
01039                         else {
01040                             if(isset($message->read)) {
01041                                 // Currently, 'read' is only sent by the PDA when it is ONLY setting the read flag.
01042                                 $this->importer->ImportMessageReadFlag($serverid, $message->read);
01043                             }
01044                             elseif (!isset($message->flag)) {
01045                                 $this->importer->ImportMessageChange($serverid, $message);
01046                             }
01047 
01048                             // email todoflags - some devices send todos flags together with read flags,
01049                             // so they have to be handled separately
01050                             if (isset($message->flag)){
01051                                 $this->importer->ImportMessageChange($serverid, $message);
01052                             }
01053 
01054                             $actiondata["statusids"][$serverid] = SYNC_STATUS_SUCCESS;
01055                         }
01056                     }
01057                     catch (StatusException $stex) {
01058                         $actiondata["statusids"][$serverid] = $stex->getCode();
01059                     }
01060 
01061                     break;
01062                 case SYNC_ADD:
01063                     try {
01064                         // check incoming message without logging WARN messages about errors
01065                         if (!($message instanceof SyncObject) || !$message->Check(true)) {
01066                             $actiondata["clientids"][$clientid] = false;
01067                             $actiondata["statusids"][$clientid] = SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR;
01068                         }
01069                         else {
01070                             $actiondata["clientids"][$clientid] = false;
01071                             $actiondata["clientids"][$clientid] = $this->importer->ImportMessageChange(false, $message);
01072                             $actiondata["statusids"][$clientid] = SYNC_STATUS_SUCCESS;
01073                         }
01074                     }
01075                     catch (StatusException $stex) {
01076                        $actiondata["statusids"][$clientid] = $stex->getCode();
01077                     }
01078                     break;
01079                 case SYNC_REMOVE:
01080                     try {
01081                         $actiondata["removeids"][] = $serverid;
01082                         // if message deletions are to be moved, move them
01083                         if($spa->GetDeletesAsMoves()) {
01084                             $folderid = self::$backend->GetWasteBasket();
01085 
01086                             if($folderid) {
01087                                 $this->importer->ImportMessageMove($serverid, $folderid);
01088                                 $actiondata["statusids"][$serverid] = SYNC_STATUS_SUCCESS;
01089                                 break;
01090                             }
01091                             else
01092                                 ZLog::Write(LOGLEVEL_WARN, "Message should be moved to WasteBasket, but the Backend did not return a destination ID. Message is hard deleted now!");
01093                         }
01094 
01095                         $this->importer->ImportMessageDeletion($serverid);
01096                         $actiondata["statusids"][$serverid] = SYNC_STATUS_SUCCESS;
01097                     }
01098                     catch (StatusException $stex) {
01099                        $actiondata["statusids"][$serverid] = $stex->getCode();
01100                     }
01101                     break;
01102             }
01103             ZLog::Write(LOGLEVEL_DEBUG, "Sync->importMessage(): message imported");
01104         }
01105     }
01106 }
01107 
01108 ?>