source: trunk/zpush/lib/request/sync.php @ 7672

Revision 7672, 62.7 KB checked in by cristiano, 11 years ago (diff)

Ticket #3209 - Correção na sincronização de remove

Line 
1<?php
2/***********************************************
3* File      :   sync.php
4* Project   :   Z-Push
5* Descr     :   Provides the SYNC command
6*
7* Created   :   16.02.2012
8*
9* Copyright 2007 - 2012 Zarafa Deutschland GmbH
10*
11* This program is free software: you can redistribute it and/or modify
12* it under the terms of the GNU Affero General Public License, version 3,
13* as published by the Free Software Foundation with the following additional
14* term according to sec. 7:
15*
16* According to sec. 7 of the GNU Affero General Public License, version 3,
17* the terms of the AGPL are supplemented with the following terms:
18*
19* "Zarafa" is a registered trademark of Zarafa B.V.
20* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
21* The licensing of the Program under the AGPL does not imply a trademark license.
22* Therefore any rights, title and interest in our trademarks remain entirely with us.
23*
24* However, if you propagate an unmodified version of the Program you are
25* allowed to use the term "Z-Push" to indicate that you distribute the Program.
26* Furthermore you may use our trademarks where it is necessary to indicate
27* the intended purpose of a product or service provided you use it in accordance
28* with honest practices in industrial or commercial matters.
29* If you want to propagate modified versions of the Program under the name "Z-Push",
30* you may only do so if you have a written permission by Zarafa Deutschland GmbH
31* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
32*
33* This program is distributed in the hope that it will be useful,
34* but WITHOUT ANY WARRANTY; without even the implied warranty of
35* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
36* GNU Affero General Public License for more details.
37*
38* You should have received a copy of the GNU Affero General Public License
39* along with this program.  If not, see <http://www.gnu.org/licenses/>.
40*
41* Consult LICENSE file for details
42************************************************/
43
44class Sync extends RequestProcessor {
45    // Ignored SMS identifier
46    const ZPUSHIGNORESMS = "ZPISMS";
47    private $importer;
48
49    /**
50     * Handles the Sync command
51     * Performs the synchronization of messages
52     *
53     * @param int       $commandCode
54     *
55     * @access public
56     * @return boolean
57     */
58    public function Handle($commandCode) {
59        // Contains all requested folders (containers)
60        $sc = new SyncCollections();
61        $status = SYNC_STATUS_SUCCESS;
62        $wbxmlproblem = false;
63        $emptysync = false;
64
65        // Start Synchronize
66        if(self::$decoder->getElementStartTag(SYNC_SYNCHRONIZE)) {
67
68            // AS 1.0 sends version information in WBXML
69            if(self::$decoder->getElementStartTag(SYNC_VERSION)) {
70                $sync_version = self::$decoder->getElementContent();
71                ZLog::Write(LOGLEVEL_DEBUG, sprintf("WBXML sync version: '%s'", $sync_version));
72                if(!self::$decoder->getElementEndTag())
73                    return false;
74            }
75
76            // Synching specified folders
77            // Android still sends heartbeat sync even if all syncfolders are disabled.
78            // Check if Folders tag is empty (<Folders/>) and only sync if there are
79            // some folders in the request. See ZP-172
80            $startTag = self::$decoder->getElementStartTag(SYNC_FOLDERS);
81            if(isset($startTag[EN_FLAGS]) && $startTag[EN_FLAGS]) {
82                while(self::$decoder->getElementStartTag(SYNC_FOLDER)) {
83                    $actiondata = array();
84                    $actiondata["requested"] = true;
85                    $actiondata["clientids"] = array();
86                    $actiondata["modifyids"] = array();
87                    $actiondata["removeids"] = array();
88                    $actiondata["fetchids"] = array();
89                    $actiondata["statusids"] = array();
90
91                    // read class, synckey and folderid without SyncParameters Object for now
92                    $class = $synckey = $folderid = false;
93
94                    //for AS versions < 2.5
95                    if(self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) {
96                        $class = self::$decoder->getElementContent();
97                        ZLog::Write(LOGLEVEL_DEBUG, sprintf("Sync folder: '%s'", $class));
98
99                        if(!self::$decoder->getElementEndTag())
100                            return false;
101                    }
102
103                    // SyncKey
104                    if(self::$decoder->getElementStartTag(SYNC_SYNCKEY)) {
105                        $synckey = "0";
106                        if (($synckey = self::$decoder->getElementContent()) !== false) {
107                            if(!self::$decoder->getElementEndTag()) {
108                                return false;
109                            }
110                        }
111                    }
112                    else
113                        return false;
114
115                    // FolderId
116                    if(self::$decoder->getElementStartTag(SYNC_FOLDERID)) {
117                        $folderid = self::$decoder->getElementContent();
118
119                        if(!self::$decoder->getElementEndTag())
120                            return false;
121                    }
122
123                    // compatibility mode AS 1.0 - get folderid which was sent during GetHierarchy()
124                    if (! $folderid && $class) {
125                        $folderid = self::$deviceManager->GetFolderIdFromCacheByClass($class);
126                    }
127
128                    // folderid HAS TO BE known by now, so we retrieve the correct SyncParameters object for an update
129                    try {
130                        $spa = self::$deviceManager->GetStateManager()->GetSynchedFolderState($folderid);
131
132                        // TODO remove resync of folders for < Z-Push 2 beta4 users
133                        // this forces a resync of all states previous to Z-Push 2 beta4
134                        if (! $spa instanceof SyncParameters)
135                            throw new StateInvalidException("Saved state are not of type SyncParameters");
136
137                        // new/resync requested
138                        if ($synckey == "0")
139                            $spa->RemoveSyncKey();
140                        else if ($synckey !== false)
141                            $spa->SetSyncKey($synckey);
142                    }
143                    catch (StateInvalidException $stie) {
144                        $spa = new SyncParameters();
145                        $status = SYNC_STATUS_INVALIDSYNCKEY;
146                        self::$topCollector->AnnounceInformation("State invalid - Resync folder", true);
147                        self::$deviceManager->ForceFolderResync($folderid);
148                    }
149
150                    // update folderid.. this might be a new object
151                    $spa->SetFolderId($folderid);
152
153                    if ($class !== false)
154                        $spa->SetContentClass($class);
155
156                    // Get class for as versions >= 12.0
157                    if (! $spa->HasContentClass()) {
158                        try {
159                            $spa->SetContentClass(self::$deviceManager->GetFolderClassFromCacheByID($spa->GetFolderId()));
160                            ZLog::Write(LOGLEVEL_DEBUG, sprintf("GetFolderClassFromCacheByID from Device Manager: '%s' for id:'%s'", $spa->GetContentClass(), $spa->GetFolderId()));
161                        }
162                        catch (NoHierarchyCacheAvailableException $nhca) {
163                            $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED;
164                            self::$deviceManager->ForceFullResync();
165                        }
166                    }
167
168                    // done basic SPA initialization/loading -> add to SyncCollection
169                    $sc->AddCollection($spa);
170                    $sc->AddParameter($spa, "requested", true);
171
172                    if ($spa->HasContentClass())
173                        self::$topCollector->AnnounceInformation(sprintf("%s request", $spa->GetContentClass()), true);
174                    else
175                        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.");
176
177                    // SUPPORTED properties
178                    if(self::$decoder->getElementStartTag(SYNC_SUPPORTED)) {
179                        $supfields = array();
180                        while(1) {
181                            $el = self::$decoder->getElement();
182
183                            if($el[EN_TYPE] == EN_TYPE_ENDTAG)
184                                break;
185                            else
186                                $supfields[] = $el[EN_TAG];
187                        }
188                        self::$deviceManager->SetSupportedFields($spa->GetFolderId(), $supfields);
189                    }
190
191                    // Deletes as moves can be an empty tag as well as have value
192                    if(self::$decoder->getElementStartTag(SYNC_DELETESASMOVES)) {
193                        $spa->SetDeletesAsMoves(true);
194                        if (($dam = self::$decoder->getElementContent()) !== false) {
195                            $spa->SetDeletesAsMoves((boolean)$dam);
196                            if(!self::$decoder->getElementEndTag()) {
197                                return false;
198                            }
199                        }
200                    }
201
202                    // Get changes can be an empty tag as well as have value
203                    // code block partly contributed by dw2412
204                    if(self::$decoder->getElementStartTag(SYNC_GETCHANGES)) {
205                        $sc->AddParameter($spa, "getchanges", true);
206                        if (($gc = self::$decoder->getElementContent()) !== false) {
207                            $sc->AddParameter($spa, "getchanges", $gc);
208                            if(!self::$decoder->getElementEndTag()) {
209                                return false;
210                            }
211                        }
212                    }
213
214                    if(self::$decoder->getElementStartTag(SYNC_WINDOWSIZE)) {
215                        $spa->SetWindowSize(self::$decoder->getElementContent());
216
217                        // also announce the currently requested window size to the DeviceManager
218                        self::$deviceManager->SetWindowSize($spa->GetFolderId(), $spa->GetWindowSize());
219
220                        if(!self::$decoder->getElementEndTag())
221                            return false;
222                    }
223
224                    // conversation mode requested
225                    if(self::$decoder->getElementStartTag(SYNC_CONVERSATIONMODE)) {
226                        $spa->SetConversationMode(true);
227                        if(($conversationmode = self::$decoder->getElementContent()) !== false) {
228                            $spa->SetConversationMode((boolean)$conversationmode);
229                            if(!self::$decoder->getElementEndTag())
230                            return false;
231                        }
232                    }
233
234                    // Do not truncate by default
235                    $spa->SetTruncation(SYNC_TRUNCATION_ALL);
236
237                    while(self::$decoder->getElementStartTag(SYNC_OPTIONS)) {
238                        $firstOption = true;
239                        while(1) {
240                            // foldertype definition
241                            if(self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) {
242                                $foldertype = self::$decoder->getElementContent();
243                                ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): specified options block with foldertype '%s'", $foldertype));
244
245                                // switch the foldertype for the next options
246                                $spa->UseCPO($foldertype);
247
248                                // set to synchronize all changes. The mobile could overwrite this value
249                                $spa->SetFilterType(SYNC_FILTERTYPE_ALL);
250
251                                if(!self::$decoder->getElementEndTag())
252                                    return false;
253                            }
254                            // if no foldertype is defined, use default cpo
255                            else if ($firstOption){
256                                $spa->UseCPO();
257                                // set to synchronize all changes. The mobile could overwrite this value
258                                $spa->SetFilterType(SYNC_FILTERTYPE_ALL);
259                            }
260                            $firstOption = false;
261
262                            if(self::$decoder->getElementStartTag(SYNC_FILTERTYPE)) {
263                                $spa->SetFilterType(self::$decoder->getElementContent());
264                                if(!self::$decoder->getElementEndTag())
265                                    return false;
266                            }
267                            if(self::$decoder->getElementStartTag(SYNC_TRUNCATION)) {
268                                $spa->SetTruncation(self::$decoder->getElementContent());
269                                if(!self::$decoder->getElementEndTag())
270                                    return false;
271                            }
272                            if(self::$decoder->getElementStartTag(SYNC_RTFTRUNCATION)) {
273                                $spa->SetRTFTruncation(self::$decoder->getElementContent());
274                                if(!self::$decoder->getElementEndTag())
275                                    return false;
276                            }
277
278                            if(self::$decoder->getElementStartTag(SYNC_MIMESUPPORT)) {
279                                $spa->SetMimeSupport(self::$decoder->getElementContent());
280                                if(!self::$decoder->getElementEndTag())
281                                    return false;
282                            }
283
284                            if(self::$decoder->getElementStartTag(SYNC_MIMETRUNCATION)) {
285                                $spa->SetMimeTruncation(self::$decoder->getElementContent());
286                                if(!self::$decoder->getElementEndTag())
287                                    return false;
288                            }
289
290                            if(self::$decoder->getElementStartTag(SYNC_CONFLICT)) {
291                                $spa->SetConflict(self::$decoder->getElementContent());
292                                if(!self::$decoder->getElementEndTag())
293                                    return false;
294                            }
295
296                            while (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_BODYPREFERENCE)) {
297                                if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TYPE)) {
298                                    $bptype = self::$decoder->getElementContent();
299                                    $spa->BodyPreference($bptype);
300                                    if(!self::$decoder->getElementEndTag()) {
301                                        return false;
302                                    }
303                                }
304
305                                if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TRUNCATIONSIZE)) {
306                                    $spa->BodyPreference($bptype)->SetTruncationSize(self::$decoder->getElementContent());
307                                    if(!self::$decoder->getElementEndTag())
308                                        return false;
309                                }
310
311                                if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_ALLORNONE)) {
312                                    $spa->BodyPreference($bptype)->SetAllOrNone(self::$decoder->getElementContent());
313                                    if(!self::$decoder->getElementEndTag())
314                                        return false;
315                                }
316
317                                if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_PREVIEW)) {
318                                    $spa->BodyPreference($bptype)->SetPreview(self::$decoder->getElementContent());
319                                    if(!self::$decoder->getElementEndTag())
320                                        return false;
321                                }
322
323                                if(!self::$decoder->getElementEndTag())
324                                    return false;
325                            }
326
327                            $e = self::$decoder->peek();
328                            if($e[EN_TYPE] == EN_TYPE_ENDTAG) {
329                                self::$decoder->getElementEndTag();
330                                break;
331                            }
332                        }
333                    }
334
335                    // limit items to be synchronized to the mobiles if configured
336                    if (defined('SYNC_FILTERTIME_MAX') && SYNC_FILTERTIME_MAX > SYNC_FILTERTYPE_ALL &&
337                        (!$spa->HasFilterType() || $spa->GetFilterType() == SYNC_FILTERTYPE_ALL || $spa->GetFilterType() > SYNC_FILTERTIME_MAX)) {
338                            ZLog::Write(LOGLEVEL_DEBUG, sprintf("SYNC_FILTERTIME_MAX defined. Filter set to value: %s", SYNC_FILTERTIME_MAX));
339                            $spa->SetFilterType(SYNC_FILTERTIME_MAX);
340                    }
341
342                    // set default conflict behavior from config if the device doesn't send a conflict resolution parameter
343                    if (! $spa->HasConflict()) {
344                        $spa->SetConflict(SYNC_CONFLICT_DEFAULT);
345                    }
346
347                    // Check if the hierarchycache is available. If not, trigger a HierarchySync
348                    if (self::$deviceManager->IsHierarchySyncRequired()) {
349                        $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED;
350                        ZLog::Write(LOGLEVEL_DEBUG, "HierarchyCache is also not available. Triggering HierarchySync to device");
351                    }
352
353                    if(self::$decoder->getElementStartTag(SYNC_PERFORM)) {
354                        // We can not proceed here as the content class is unknown
355                        if ($status != SYNC_STATUS_SUCCESS) {
356                            ZLog::Write(LOGLEVEL_WARN, "Ignoring all incoming actions as global status indicates problem.");
357                            $wbxmlproblem = true;
358                            break;
359                        }
360
361                        $performaction = true;
362
363                        // unset the importer
364                        $this->importer = false;
365
366                        $nchanges = 0;
367                        while(1) {
368                            // ADD, MODIFY, REMOVE or FETCH
369                            $element = self::$decoder->getElement();
370
371                            if($element[EN_TYPE] != EN_TYPE_STARTTAG) {
372                                self::$decoder->ungetElement($element);
373                                break;
374                            }
375
376                            if ($status == SYNC_STATUS_SUCCESS)
377                                $nchanges++;
378
379                            // Foldertype sent when synching SMS
380                            if(self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) {
381                                $foldertype = self::$decoder->getElementContent();
382                                ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): incoming data with foldertype '%s'", $foldertype));
383
384                                if(!self::$decoder->getElementEndTag())
385                                return false;
386                            }
387                            else
388                                $foldertype = false;
389
390                            if(self::$decoder->getElementStartTag(SYNC_SERVERENTRYID)) {
391                                $serverid = self::$decoder->getElementContent();
392
393                                if(!self::$decoder->getElementEndTag()) // end serverid
394                                    return false;
395                            }
396                            else
397                                $serverid = false;
398
399                            if(self::$decoder->getElementStartTag(SYNC_CLIENTENTRYID)) {
400                                $clientid = self::$decoder->getElementContent();
401
402                                if(!self::$decoder->getElementEndTag()) // end clientid
403                                    return false;
404                            }
405                            else
406                                $clientid = false;
407
408                            // Get the SyncMessage if sent
409                            if(self::$decoder->getElementStartTag(SYNC_DATA)) {
410                                $message = ZPush::getSyncObjectFromFolderClass($spa->GetContentClass());
411                                $message->Decode(self::$decoder);
412
413                                // set Ghosted fields
414                                $message->emptySupported(self::$deviceManager->GetSupportedFields($spa->GetFolderId()));
415                                if(!self::$decoder->getElementEndTag()) // end applicationdata
416                                    return false;
417                            }
418                            else
419                                $message = false;
420
421                            switch($element[EN_TAG]) {
422                                case SYNC_FETCH:
423                                    array_push($actiondata["fetchids"], $serverid);
424                                    break;
425                                default:
426                                    // get the importer
427                                    if ($this->importer == false)
428                                        $status = $this->getImporter($sc, $spa, $actiondata);
429
430                                    if ($status == SYNC_STATUS_SUCCESS)
431                                        $this->importMessage($spa, $actiondata, $element[EN_TAG], $message, $clientid, $serverid, $foldertype, $nchanges);
432                                    else
433                                        ZLog::Write(LOGLEVEL_WARN, "Ignored incoming change, global status indicates problem.");
434
435                                    break;
436                            }
437
438                            if ($actiondata["fetchids"])
439                                self::$topCollector->AnnounceInformation(sprintf("Fetching %d", $nchanges));
440                            else
441                                self::$topCollector->AnnounceInformation(sprintf("Incoming %d", $nchanges));
442
443                            if(!self::$decoder->getElementEndTag()) // end add/change/delete/move
444                                return false;
445                        }
446
447                        if ($status == SYNC_STATUS_SUCCESS && $this->importer !== false) {
448                            ZLog::Write(LOGLEVEL_INFO, sprintf("Processed '%d' incoming changes", $nchanges));
449                            if (!$actiondata["fetchids"])
450                                self::$topCollector->AnnounceInformation(sprintf("%d incoming", $nchanges), true);
451
452                            try {
453                                // Save the updated state, which is used for the exporter later
454                                $sc->AddParameter($spa, "state", $this->importer->GetState());
455                            }
456                            catch (StatusException $stex) {
457                               $status = $stex->getCode();
458                            }
459                        }
460
461                        if(!self::$decoder->getElementEndTag()) // end PERFORM
462                            return false;
463                    }
464
465                    // save the failsave state
466                    if (!empty($actiondata["statusids"])) {
467                        unset($actiondata["failstate"]);
468                        $actiondata["failedsyncstate"] = $sc->GetParameter($spa, "state");
469                        self::$deviceManager->GetStateManager()->SetSyncFailState($actiondata);
470                    }
471
472                    // save actiondata
473                    $sc->AddParameter($spa, "actiondata", $actiondata);
474
475                    if(!self::$decoder->getElementEndTag()) // end collection
476                        return false;
477
478                    // AS14 does not send GetChanges anymore. We should do it if there were no incoming changes
479                    if (!isset($performaction) && !$sc->GetParameter($spa, "getchanges") && $spa->HasSyncKey())
480                        $sc->AddParameter($spa, "getchanges", true);
481                } // END FOLDER
482
483                if(!$wbxmlproblem && !self::$decoder->getElementEndTag()) // end collections
484                    return false;
485            } // end FOLDERS
486
487            if (self::$decoder->getElementStartTag(SYNC_HEARTBEATINTERVAL)) {
488                $hbinterval = self::$decoder->getElementContent();
489                if(!self::$decoder->getElementEndTag()) // SYNC_HEARTBEATINTERVAL
490                    return false;
491            }
492
493            if (self::$decoder->getElementStartTag(SYNC_WAIT)) {
494                $wait = self::$decoder->getElementContent();
495                if(!self::$decoder->getElementEndTag()) // SYNC_WAIT
496                    return false;
497
498                // internally the heartbeat interval and the wait time are the same
499                // heartbeat is in seconds, wait in minutes
500                $hbinterval = $wait * 60;
501            }
502
503            if (self::$decoder->getElementStartTag(SYNC_WINDOWSIZE)) {
504                $sc->SetGlobalWindowSize(self::$decoder->getElementContent());
505                if(!self::$decoder->getElementEndTag()) // SYNC_WINDOWSIZE
506                    return false;
507            }
508
509            if(self::$decoder->getElementStartTag(SYNC_PARTIAL))
510                $partial = true;
511            else
512                $partial = false;
513
514            if(!$wbxmlproblem && !self::$decoder->getElementEndTag()) // end sync
515                return false;
516        }
517        // we did not receive a SYNCHRONIZE block - assume empty sync
518        else {
519            $emptysync = true;
520        }
521        // END SYNCHRONIZE
522
523        // check heartbeat/wait time
524        if (isset($hbinterval)) {
525            if ($hbinterval < 60 || $hbinterval > 3540) {
526                $status = SYNC_STATUS_INVALIDWAITORHBVALUE;
527                ZLog::Write(LOGLEVEL_WARN, sprintf("HandleSync(): Invalid heartbeat or wait value '%s'", $hbinterval));
528            }
529        }
530
531        // Partial & Empty Syncs need saved data to proceed with synchronization
532        if ($status == SYNC_STATUS_SUCCESS && ($emptysync === true || $partial === true) ) {
533            ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Partial or Empty sync requested. Retrieving data of synchronized folders."));
534
535            // Load all collections - do not overwrite existing (received!), laod states and check permissions
536            try {
537                $sc->LoadAllCollections(false, true, true);
538            }
539            catch (StateNotFoundException $snfex) {
540                $status = SYNC_STATUS_INVALIDSYNCKEY;
541                self::$topCollector->AnnounceInformation("StateNotFoundException", true);
542            }
543            catch (StatusException $stex) {
544               $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED;
545               self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), true);
546            }
547
548            // update a few values
549            foreach($sc as $folderid => $spa) {
550                // manually set getchanges parameter for this collection
551                $sc->AddParameter($spa, "getchanges", true);
552
553                // set new global windowsize without marking the SPA as changed
554                if ($sc->GetGlobalWindowSize())
555                    $spa->SetWindowSize($sc->GetGlobalWindowSize(), false);
556
557                // announce WindowSize to DeviceManager
558                self::$deviceManager->SetWindowSize($folderid, $spa->GetWindowSize());
559            }
560            if (!$sc->HasCollections())
561                $status = SYNC_STATUS_SYNCREQUESTINCOMPLETE;
562        }
563
564        // HEARTBEAT & Empty sync
565        if ($status == SYNC_STATUS_SUCCESS && (isset($hbinterval) || $emptysync == true)) {
566            $interval = (defined('PING_INTERVAL') && PING_INTERVAL > 0) ? PING_INTERVAL : 30;
567
568            if (isset($hbinterval))
569                $sc->SetLifetime($hbinterval);
570
571            // states are lazy loaded - we have to make sure that they are there!
572            $loadstatus = SYNC_STATUS_SUCCESS;
573            foreach($sc as $folderid => $spa) {
574                $fad = array();
575                // if loading the states fails, we do not enter heartbeat, but we keep $status on SYNC_STATUS_SUCCESS
576                // so when the changes are exported the correct folder gets an SYNC_STATUS_INVALIDSYNCKEY
577                if ($loadstatus == SYNC_STATUS_SUCCESS)
578                    $loadstatus = $this->loadStates($sc, $spa, $fad);
579            }
580
581            if ($loadstatus == SYNC_STATUS_SUCCESS) {
582                $foundchanges = false;
583
584                // wait for changes
585                try {
586                    // if doing an empty sync, check only once for changes
587                    if ($emptysync) {
588                        $foundchanges = $sc->CountChanges();
589                    }
590                    // wait for changes
591                    else {
592                        ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Entering Heartbeat mode"));
593                        $foundchanges = $sc->CheckForChanges($sc->GetLifetime(), $interval);
594                    }
595                }
596                catch (StatusException $stex) {
597                   $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED;
598                   self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), true);
599                }
600
601                // in case of an empty sync with no changes, we can reply with an empty response
602                if ($emptysync && !$foundchanges){
603                    ZLog::Write(LOGLEVEL_DEBUG, "No changes found for empty sync. Replying with empty response");
604                    return true;
605                }
606
607                if ($foundchanges) {
608                    foreach ($sc->GetChangedFolderIds() as $folderid => $changecount) {
609                        // check if there were other sync requests for a folder during the heartbeat
610                        $spa = $sc->GetCollection($folderid);
611                        if ($changecount > 0 && $sc->WaitedForChanges() && self::$deviceManager->CheckHearbeatStateIntegrity($spa->GetFolderId(), $spa->GetUuid(), $spa->GetUuidCounter())) {
612                            ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): heartbeat: found %d changes in '%s' which was already synchronized. Heartbeat aborted!", $changecount, $folderid));
613                            $status = SYNC_COMMONSTATUS_SYNCSTATEVERSIONINVALID;
614                        }
615                        else
616                            ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): heartbeat: found %d changes in '%s'", $changecount, $folderid));
617                    }
618                }
619            }
620        }
621
622        ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Start Output"));
623
624        // Start the output
625        self::$encoder->startWBXML();
626        self::$encoder->startTag(SYNC_SYNCHRONIZE);
627        {
628            // global status
629            // SYNC_COMMONSTATUS_* start with values from 101
630            if ($status != SYNC_COMMONSTATUS_SUCCESS && $status > 100) {
631                self::$encoder->startTag(SYNC_STATUS);
632                    self::$encoder->content($status);
633                self::$encoder->endTag();
634            }
635            else {
636                self::$encoder->startTag(SYNC_FOLDERS);
637                {
638                    foreach($sc as $folderid => $spa) {
639                        // get actiondata
640                        $actiondata = $sc->GetParameter($spa, "actiondata");
641
642                        if ($status == SYNC_STATUS_SUCCESS && (!$spa->GetContentClass() || !$spa->GetFolderId())) {
643                            ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): no content class or folderid found for collection."));
644                            continue;
645                        }
646
647                        if (! $sc->GetParameter($spa, "requested"))
648                            ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): partial sync for folder class '%s' with id '%s'", $spa->GetContentClass(), $spa->GetFolderId()));
649
650                        // initialize exporter to get changecount
651                        $changecount = 0;
652                        if (isset($exporter))
653                            unset($exporter);
654
655                        // TODO we could check against $sc->GetChangedFolderIds() on heartbeat so we do not need to configure all exporter again
656                        if($status == SYNC_STATUS_SUCCESS && ($sc->GetParameter($spa, "getchanges") || ! $spa->HasSyncKey())) {
657
658                            //make sure the states are loaded
659                            $status = $this->loadStates($sc, $spa, $actiondata);
660
661                            if($status == SYNC_STATUS_SUCCESS) {
662                                try {
663                                    // Use the state from the importer, as changes may have already happened
664                                    $exporter = self::$backend->GetExporter($spa->GetFolderId());
665
666                                    if ($exporter === false)
667                                        throw new StatusException(sprintf("HandleSync() could not get an exporter for folder id '%s'", $spa->GetFolderId()), SYNC_STATUS_FOLDERHIERARCHYCHANGED);
668                                }
669                                catch (StatusException $stex) {
670                                   $status = $stex->getCode();
671                                }
672                                try {
673                                    // Stream the messages directly to the PDA
674                                    $streamimporter = new ImportChangesStream(self::$encoder, ZPush::getSyncObjectFromFolderClass($spa->GetContentClass()));
675
676                                    if ($exporter !== false) {
677                                        $exporter->Config($sc->GetParameter($spa, "state"));
678                                        $exporter->ConfigContentParameters($spa->GetCPO());
679                                        $exporter->InitializeExporter($streamimporter);
680
681                                        $changecount = $exporter->GetChangeCount();
682                                    }
683                                }
684                                catch (StatusException $stex) {
685                                    if ($stex->getCode() === SYNC_FSSTATUS_CODEUNKNOWN && $spa->HasSyncKey())
686                                        $status = SYNC_STATUS_INVALIDSYNCKEY;
687                                    else
688                                        $status = $stex->getCode();
689                                }
690
691                                if (! $spa->HasSyncKey())
692                                    self::$topCollector->AnnounceInformation(sprintf("Exporter registered. %d objects queued.", $changecount), true);
693                                else if ($status != SYNC_STATUS_SUCCESS)
694                                    self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), true);
695                            }
696                        }
697
698                        if (isset($hbinterval) && $changecount == 0 && $status == SYNC_STATUS_SUCCESS) {
699                            ZLog::Write(LOGLEVEL_DEBUG, "No changes found for heartbeat folder. Omitting empty output.");
700                            continue;
701                        }
702
703                        // Get a new sync key to output to the client if any changes have been send or will are available
704                        if (!empty($actiondata["modifyids"]) ||
705                            !empty($actiondata["clientids"]) ||
706                            !empty($actiondata["removeids"]) ||
707                            $changecount > 0 || (! $spa->HasSyncKey() && $status == SYNC_STATUS_SUCCESS))
708                                $spa->SetNewSyncKey(self::$deviceManager->GetStateManager()->GetNewSyncKey($spa->GetSyncKey()));
709
710                        self::$encoder->startTag(SYNC_FOLDER);
711
712                        if($spa->HasContentClass()) {
713                            ZLog::Write(LOGLEVEL_DEBUG, sprintf("Folder type: %s", $spa->GetContentClass()));
714                            // AS 12.0 devices require content class
715                            if (Request::GetProtocolVersion() < 12.1) {
716                                self::$encoder->startTag(SYNC_FOLDERTYPE);
717                                self::$encoder->content($spa->GetContentClass());
718                                self::$encoder->endTag();
719                            }
720                        }
721
722                        self::$encoder->startTag(SYNC_SYNCKEY);
723                        if($status == SYNC_STATUS_SUCCESS && $spa->HasNewSyncKey())
724                            self::$encoder->content($spa->GetNewSyncKey());
725                        else
726                            self::$encoder->content($spa->GetSyncKey());
727                        self::$encoder->endTag();
728
729                        self::$encoder->startTag(SYNC_FOLDERID);
730                            self::$encoder->content($spa->GetFolderId());
731                        self::$encoder->endTag();
732
733                        self::$encoder->startTag(SYNC_STATUS);
734                            self::$encoder->content($status);
735                        self::$encoder->endTag();
736
737                        // announce failing status to the process loop detection
738                        if ($status !== SYNC_STATUS_SUCCESS)
739                            self::$deviceManager->AnnounceProcessStatus($spa->GetFolderId(), $status);
740
741                        // Output IDs and status for incoming items & requests
742                        if($status == SYNC_STATUS_SUCCESS && (
743                            !empty($actiondata["clientids"]) ||
744                            !empty($actiondata["modifyids"]) ||
745                            !empty($actiondata["removeids"]) ||
746                            !empty($actiondata["fetchids"]) )) {
747
748                            self::$encoder->startTag(SYNC_REPLIES);
749                            // output result of all new incoming items
750                            foreach($actiondata["clientids"] as $clientid => $serverid) {
751                                self::$encoder->startTag(SYNC_ADD);
752                                    self::$encoder->startTag(SYNC_CLIENTENTRYID);
753                                        self::$encoder->content($clientid);
754                                    self::$encoder->endTag();
755                                    if ($serverid) {
756                                        self::$encoder->startTag(SYNC_SERVERENTRYID);
757                                            self::$encoder->content($serverid);
758                                        self::$encoder->endTag();
759                                    }
760                                    self::$encoder->startTag(SYNC_STATUS);
761                                        self::$encoder->content((isset($actiondata["statusids"][$clientid])?$actiondata["statusids"][$clientid]:SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR));
762                                    self::$encoder->endTag();
763                                self::$encoder->endTag();
764                            }
765
766                            // loop through modify operations which were not a success, send status
767                            foreach($actiondata["modifyids"] as $serverid) {
768                                if (isset($actiondata["statusids"][$serverid]) && $actiondata["statusids"][$serverid] !== SYNC_STATUS_SUCCESS) {
769                                    self::$encoder->startTag(SYNC_MODIFY);
770                                        self::$encoder->startTag(SYNC_SERVERENTRYID);
771                                            self::$encoder->content($serverid);
772                                        self::$encoder->endTag();
773                                        self::$encoder->startTag(SYNC_STATUS);
774                                            self::$encoder->content($actiondata["statusids"][$serverid]);
775                                        self::$encoder->endTag();
776                                    self::$encoder->endTag();
777                                }
778                            }
779
780                            // loop through remove operations which were not a success, send status
781                            foreach($actiondata["removeids"] as $serverid) {
782                                if (isset($actiondata["statusids"][$serverid]) && $actiondata["statusids"][$serverid] !== SYNC_STATUS_SUCCESS) {
783                                    self::$encoder->startTag(SYNC_REMOVE);
784                                        self::$encoder->startTag(SYNC_SERVERENTRYID);
785                                            self::$encoder->content($serverid);
786                                        self::$encoder->endTag();
787                                        self::$encoder->startTag(SYNC_STATUS);
788                                            self::$encoder->content($actiondata["statusids"][$serverid]);
789                                        self::$encoder->endTag();
790                                    self::$encoder->endTag();
791                                }
792                            }
793
794                            if (!empty($actiondata["fetchids"]))
795                                self::$topCollector->AnnounceInformation(sprintf("Fetching %d objects ", count($actiondata["fetchids"])), true);
796
797                            foreach($actiondata["fetchids"] as $id) {
798                                $data = false;
799                                try {
800                                    $fetchstatus = SYNC_STATUS_SUCCESS;
801
802                                    // if this is an additional folder the backend has to be setup correctly
803                                    if (!self::$backend->Setup(ZPush::GetAdditionalSyncFolderStore($spa->GetFolderId())))
804                                        throw new StatusException(sprintf("HandleSync(): could not Setup() the backend to fetch in folder id '%s'", $spa->GetFolderId()), SYNC_STATUS_OBJECTNOTFOUND);
805
806                                    $data = self::$backend->Fetch($spa->GetFolderId(), $id, $spa->GetCPO());
807
808                                    // check if the message is broken
809                                    if (ZPush::GetDeviceManager(false) && ZPush::GetDeviceManager()->DoNotStreamMessage($id, $data)) {
810                                        ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): message not to be streamed as requested by DeviceManager.", $id));
811                                        $fetchstatus = SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR;
812                                    }
813                                }
814                                catch (StatusException $stex) {
815                                   $fetchstatus = $stex->getCode();
816                                }
817
818                                self::$encoder->startTag(SYNC_FETCH);
819                                    self::$encoder->startTag(SYNC_SERVERENTRYID);
820                                        self::$encoder->content($id);
821                                    self::$encoder->endTag();
822
823                                    self::$encoder->startTag(SYNC_STATUS);
824                                        self::$encoder->content($fetchstatus);
825                                    self::$encoder->endTag();
826
827                                    if($data !== false && $status == SYNC_STATUS_SUCCESS) {
828                                        self::$encoder->startTag(SYNC_DATA);
829                                            $data->Encode(self::$encoder);
830                                        self::$encoder->endTag();
831                                    }
832                                    else
833                                        ZLog::Write(LOGLEVEL_WARN, sprintf("Unable to Fetch '%s'", $id));
834                                self::$encoder->endTag();
835
836                            }
837                            self::$encoder->endTag();
838                        }
839
840                        if($sc->GetParameter($spa, "getchanges") && $spa->HasFolderId() && $spa->HasContentClass() && $spa->HasSyncKey()) {
841                            $windowSize = self::$deviceManager->GetWindowSize($spa->GetFolderId(), $spa->GetContentClass(), $spa->GetUuid(), $spa->GetUuidCounter(), $changecount);
842
843                            if($changecount > $windowSize) {
844                                self::$encoder->startTag(SYNC_MOREAVAILABLE, false, true);
845                            }
846                        }
847
848                        // Stream outgoing changes
849                        if($status == SYNC_STATUS_SUCCESS && $sc->GetParameter($spa, "getchanges") == true && $windowSize > 0) {
850                            self::$topCollector->AnnounceInformation(sprintf("Streaming data of %d objects", (($changecount > $windowSize)?$windowSize:$changecount)));
851
852                            // Output message changes per folder
853                            self::$encoder->startTag(SYNC_PERFORM);
854
855                            $n = 0;
856                            while(1) {
857                                try {
858                                    $progress = $exporter->Synchronize();
859                                    if(!is_array($progress))
860                                        break;
861                                    $n++;
862                                }
863                                catch (SyncObjectBrokenException $mbe) {
864                                    $brokenSO = $mbe->GetSyncObject();
865                                    if (!$brokenSO) {
866                                        ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): Catched SyncObjectBrokenException but broken SyncObject not available. This should be fixed in the backend."));
867                                    }
868                                    else {
869                                        if (!isset($brokenSO->id)) {
870                                            $brokenSO->id = "Unknown ID";
871                                            ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): Catched SyncObjectBrokenException but no ID of object set. This should be fixed in the backend."));
872                                        }
873                                        self::$deviceManager->AnnounceIgnoredMessage($spa->GetFolderId(), $brokenSO->id, $brokenSO);
874                                    }
875                                }
876
877                                if($n >= $windowSize) {
878                                    ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Exported maxItems of messages: %d / %d", $n, $changecount));
879                                    break;
880                                }
881
882                            }
883
884                            // $progress is not an array when exporting the last message
885                            // so we get the number to display from the streamimporter
886                            if (isset($streamimporter)) {
887                                $n = $streamimporter->GetImportedMessages();
888                            }
889
890                            self::$encoder->endTag();
891                            self::$topCollector->AnnounceInformation(sprintf("Outgoing %d objects%s", $n, ($n >= $windowSize)?" of ".$changecount:""), true);
892                        }
893
894                        self::$encoder->endTag();
895
896                        // Save the sync state for the next time
897                        if($spa->HasNewSyncKey()) {
898                            self::$topCollector->AnnounceInformation("Saving state");
899
900                            try {
901                                if (isset($exporter) && $exporter)
902                                    $state = $exporter->GetState();
903
904                                // nothing exported, but possibly imported - get the importer state
905                                else if ($sc->GetParameter($spa, "state") !== null)
906                                    $state = $sc->GetParameter($spa, "state");
907
908                                // if a new request without state information (hierarchy) save an empty state
909                                else if (! $spa->HasSyncKey())
910                                    $state = "";
911                            }
912                            catch (StatusException $stex) {
913                               $status = $stex->getCode();
914                            }
915
916
917                            if (isset($state) && $status == SYNC_STATUS_SUCCESS)
918                                self::$deviceManager->GetStateManager()->SetSyncState($spa->GetNewSyncKey(), $state, $spa->GetFolderId());
919                            else
920                                ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): error saving '%s' - no state information available", $spa->GetNewSyncKey()));
921                        }
922
923                        // save SyncParameters
924                        if ($status == SYNC_STATUS_SUCCESS && empty($actiondata["fetchids"]))
925                            $sc->SaveCollection($spa);
926
927                    } // END foreach collection
928                }
929                self::$encoder->endTag(); //SYNC_FOLDERS
930            }
931        }
932        self::$encoder->endTag(); //SYNC_SYNCHRONIZE
933
934        return true;
935    }
936
937    /**
938     * Loads the states and writes them into the SyncCollection Object and the actiondata failstate
939     *
940     * @param SyncCollection    $sc             SyncCollection object
941     * @param SyncParameters    $spa            SyncParameters object
942     * @param array             $actiondata     Actiondata array
943     * @param boolean           $loadFailsave   (opt) default false - indicates if the failsave states should be loaded
944     *
945     * @access private
946     * @return status           indicating if there were errors. If no errors, status is SYNC_STATUS_SUCCESS
947     */
948    private function loadStates($sc, $spa, &$actiondata, $loadFailsave = false) {
949        $status = SYNC_STATUS_SUCCESS;
950
951        if ($sc->GetParameter($spa, "state") == null) {
952            ZLog::Write(LOGLEVEL_DEBUG, sprintf("Sync->loadStates(): loading states for folder '%s'",$spa->GetFolderId()));
953
954            try {
955                $sc->AddParameter($spa, "state", self::$deviceManager->GetStateManager()->GetSyncState($spa->GetSyncKey()));
956
957                if ($loadFailsave) {
958                    // if this request was made before, there will be a failstate available
959                    $actiondata["failstate"] = self::$deviceManager->GetStateManager()->GetSyncFailState();
960                }
961
962                // if this is an additional folder the backend has to be setup correctly
963                if (!self::$backend->Setup(ZPush::GetAdditionalSyncFolderStore($spa->GetFolderId())))
964                    throw new StatusException(sprintf("HandleSync() could not Setup() the backend for folder id '%s'", $spa->GetFolderId()), SYNC_STATUS_FOLDERHIERARCHYCHANGED);
965            }
966            catch (StateNotFoundException $snfex) {
967                $status = SYNC_STATUS_INVALIDSYNCKEY;
968                self::$topCollector->AnnounceInformation("StateNotFoundException", true);
969            }
970            catch (StatusException $stex) {
971               $status = $stex->getCode();
972               self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), true);
973            }
974        }
975
976        return $status;
977    }
978
979    /**
980     * Initializes the importer for the SyncParameters folder, loads necessary
981     * states (incl. failsave states) and initializes the conflict detection
982     *
983     * @param SyncCollection    $sc             SyncCollection object
984     * @param SyncParameters    $spa            SyncParameters object
985     * @param array             $actiondata     Actiondata array
986     *
987     * @access private
988     * @return status           indicating if there were errors. If no errors, status is SYNC_STATUS_SUCCESS
989     */
990    private function getImporter($sc, $spa, &$actiondata) {
991        ZLog::Write(LOGLEVEL_DEBUG, "Sync->getImporter(): initialize importer");
992        $status = SYNC_STATUS_SUCCESS;
993
994        // load the states with failsave data
995        $status = $this->loadStates($sc, $spa, $actiondata, true);
996
997        try {
998            // Configure importer with last state
999            $this->importer = self::$backend->GetImporter($spa->GetFolderId());
1000
1001            // if something goes wrong, ask the mobile to resync the hierarchy
1002            if ($this->importer === false)
1003                throw new StatusException(sprintf("Sync->getImporter(): no importer for folder id '%s'", $spa->GetFolderId()), SYNC_STATUS_FOLDERHIERARCHYCHANGED);
1004
1005            // if there is a valid state obtained after importing changes in a previous loop, we use that state
1006            if (isset($actiondata["failstate"]) && isset($actiondata["failstate"]["failedsyncstate"])) {
1007                $this->importer->Config($actiondata["failstate"]["failedsyncstate"], $spa->GetConflict());
1008            }
1009            else
1010                $this->importer->Config($sc->GetParameter($spa, "state"), $spa->GetConflict());
1011
1012            // the CPO is also needed by the importer to check if imported changes
1013            // are inside the sync window - see ZP-258
1014            // TODO ConfigContentParameters needs to be defined in IImportChanges and all implementing importers/backends
1015            // this is currently only supported by the Zarafa Backend
1016            if (method_exists($this->importer, "ConfigContentParameters"))
1017                $this->importer->ConfigContentParameters($spa->GetCPO());
1018        }
1019        catch (StatusException $stex) {
1020           $status = $stex->getCode();
1021        }
1022
1023        $this->importer->LoadConflicts($spa->GetCPO(), $sc->GetParameter($spa, "state"));
1024
1025        return $status;
1026    }
1027
1028    /**
1029     * Imports a message
1030     *
1031     * @param SyncParameters    $spa            SyncParameters object
1032     * @param array             $actiondata     Actiondata array
1033     * @param integer           $todo           WBXML flag indicating how message should be imported.
1034     *                                          Valid values: SYNC_ADD, SYNC_MODIFY, SYNC_REMOVE
1035     * @param SyncObject        $message        SyncObject message to be imported
1036     * @param string            $clientid       Client message identifier
1037     * @param string            $serverid       Server message identifier
1038     * @param string            $foldertype     On sms sync, this says "SMS", else false
1039     * @param integer           $messageCount   Counter of already imported messages
1040     *
1041     * @access private
1042     * @throws StatusException  in case the importer is not available
1043     * @return -                Message related status are returned in the actiondata.
1044     */
1045    private function importMessage($spa, &$actiondata, $todo, $message, $clientid, $serverid, $foldertype, $messageCount) {
1046        // the importer needs to be available!
1047        if ($this->importer == false)
1048            throw StatusException(sprintf("Sync->importMessage(): importer not available", SYNC_STATUS_SERVERERROR));
1049
1050        // mark this state as used, e.g. for HeartBeat
1051        self::$deviceManager->SetHeartbeatStateIntegrity($spa->GetFolderId(), $spa->GetUuid(), $spa->GetUuidCounter());
1052
1053        // Detect incoming loop
1054        // messages which were created/removed before will not have the same action executed again
1055        // if a message is edited we perform this action "again", as the message could have been changed on the mobile in the meantime
1056        $ignoreMessage = false;
1057        if ($actiondata["failstate"]) {
1058            // message was ADDED before, do NOT add it again
1059            if ($todo == SYNC_ADD && isset($actiondata["failstate"]["clientids"][$clientid])) {
1060                $ignoreMessage = true;
1061
1062                // make sure no messages are sent back
1063                self::$deviceManager->SetWindowSize($spa->GetFolderId(), 0);
1064
1065                $actiondata["clientids"][$clientid] = $actiondata["failstate"]["clientids"][$clientid];
1066                $actiondata["statusids"][$clientid] = $actiondata["failstate"]["statusids"][$clientid];
1067
1068                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]));
1069            }
1070
1071            // message was REMOVED before, do NOT attemp to remove it again
1072            if ($todo == SYNC_REMOVE && isset($actiondata["failstate"]["removeids"][$serverid])) {
1073                $ignoreMessage = true;
1074
1075                // make sure no messages are sent back
1076                self::$deviceManager->SetWindowSize($spa->GetFolderId(), 0);
1077
1078                $actiondata["removeids"][$serverid] = $actiondata["failstate"]["removeids"][$serverid];
1079                $actiondata["statusids"][$serverid] = $actiondata["failstate"]["statusids"][$serverid];
1080
1081                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]));
1082            }
1083        }
1084
1085        if (!$ignoreMessage) {
1086            switch($todo) {
1087                case SYNC_MODIFY:
1088                    self::$topCollector->AnnounceInformation(sprintf("Saving modified message %d", $messageCount));
1089                    try {
1090                        $actiondata["modifyids"][] = $serverid;
1091
1092                        // ignore sms messages
1093                        if ($foldertype == "SMS" || stripos($serverid, self::ZPUSHIGNORESMS) !== false) {
1094                            ZLog::Write(LOGLEVEL_DEBUG, "SMS sync are not supported. Ignoring message.");
1095                            // TODO we should update the SMS
1096                            $actiondata["statusids"][$serverid] = SYNC_STATUS_SUCCESS;
1097                        }
1098                        // check incoming message without logging WARN messages about errors
1099                        else if (!($message instanceof SyncObject) || !$message->Check(true)) {
1100                            $actiondata["statusids"][$serverid] = SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR;
1101                        }
1102                        else {
1103                            if(isset($message->read)) {
1104                                // Currently, 'read' is only sent by the PDA when it is ONLY setting the read flag.
1105                                $this->importer->ImportMessageReadFlag($serverid, $message->read);
1106                            }
1107                            elseif (!isset($message->flag)) {
1108                                $this->importer->ImportMessageChange($serverid, $message);
1109                            }
1110
1111                            // email todoflags - some devices send todos flags together with read flags,
1112                            // so they have to be handled separately
1113                            if (isset($message->flag)){
1114                                $this->importer->ImportMessageChange($serverid, $message);
1115                            }
1116
1117                            $actiondata["statusids"][$serverid] = SYNC_STATUS_SUCCESS;
1118                        }
1119                    }
1120                    catch (StatusException $stex) {
1121                        $actiondata["statusids"][$serverid] = $stex->getCode();
1122                    }
1123
1124                    break;
1125                case SYNC_ADD:
1126                    self::$topCollector->AnnounceInformation(sprintf("Creating new message from mobile %d", $messageCount));
1127                    try {
1128                        // ignore sms messages
1129                        if ($foldertype == "SMS") {
1130                            ZLog::Write(LOGLEVEL_DEBUG, "SMS sync are not supported. Ignoring message.");
1131                            // TODO we should create the SMS
1132                            // return a fake serverid which we can identify later
1133                            $actiondata["clientids"][$clientid] = self::ZPUSHIGNORESMS . $clientid;
1134                            $actiondata["statusids"][$clientid] = SYNC_STATUS_SUCCESS;
1135                        }
1136                        // check incoming message without logging WARN messages about errors
1137                        else if (!($message instanceof SyncObject) || !$message->Check(true)) {
1138                            $actiondata["clientids"][$clientid] = false;
1139                            $actiondata["statusids"][$clientid] = SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR;
1140                        }
1141                        else {
1142                            $actiondata["clientids"][$clientid] = false;
1143                            $actiondata["clientids"][$clientid] = $this->importer->ImportMessageChange(false, $message);
1144                            $actiondata["statusids"][$clientid] = SYNC_STATUS_SUCCESS;
1145                        }
1146                    }
1147                    catch (StatusException $stex) {
1148                       $actiondata["statusids"][$clientid] = $stex->getCode();
1149                    }
1150                    break;
1151                case SYNC_REMOVE:
1152                    self::$topCollector->AnnounceInformation(sprintf("Deleting message removed on mobile %d", $messageCount));
1153                    try {
1154                        $actiondata["removeids"][] = $serverid;
1155                        // ignore sms messages
1156                        if ($foldertype == "SMS" || stripos($serverid, self::ZPUSHIGNORESMS) !== false) {
1157                            ZLog::Write(LOGLEVEL_DEBUG, "SMS sync are not supported. Ignoring message.");
1158                            // TODO we should delete the SMS
1159                            $actiondata["statusids"][$serverid] = SYNC_STATUS_SUCCESS;
1160                        }
1161                        else {
1162                            // if message deletions are to be moved, move them
1163                            $folderid = $spa->GetFolderId();
1164
1165                            if($folderid == 'contacts' || substr( $folderid , 0 ,8 ) == 'calendar' || !$spa->GetDeletesAsMoves())
1166                            {
1167                                $this->importer->ImportMessageDeletion($serverid);
1168                                $actiondata["statusids"][$serverid] = SYNC_STATUS_SUCCESS;
1169                            }
1170                            else
1171                            {
1172
1173                                $folderid = self::$backend->GetWasteBasket();
1174
1175                                if($folderid) {
1176                                    $this->importer->ImportMessageMove($serverid, $folderid);
1177                                    $actiondata["statusids"][$serverid] = SYNC_STATUS_SUCCESS;
1178                                    break;
1179                                }
1180                                else
1181                                    ZLog::Write(LOGLEVEL_WARN, "Message should be moved to WasteBasket, but the Backend did not return a destination ID. Message is hard deleted now!");
1182                            }
1183                        }
1184                    }
1185                    catch (StatusException $stex) {
1186                       $actiondata["statusids"][$serverid] = $stex->getCode();
1187                    }
1188                    break;
1189            }
1190            ZLog::Write(LOGLEVEL_DEBUG, "Sync->importMessage(): message imported");
1191        }
1192    }
1193}
1194
1195?>
Note: See TracBrowser for help on using the repository browser.