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

Revision 7589, 62.1 KB checked in by douglas, 11 years ago (diff)

Ticket #3209 - Integrar módulo de sincronização Z-push ao Expresso

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                        if($spa->HasContentClass()) {
711                            ZLog::Write(LOGLEVEL_DEBUG, sprintf("Folder type: %s", $spa->GetContentClass()));
712                        }
713
714                        self::$encoder->startTag(SYNC_FOLDER);
715
716                        self::$encoder->startTag(SYNC_SYNCKEY);
717                        if($status == SYNC_STATUS_SUCCESS && $spa->HasNewSyncKey())
718                            self::$encoder->content($spa->GetNewSyncKey());
719                        else
720                            self::$encoder->content($spa->GetSyncKey());
721                        self::$encoder->endTag();
722
723                        self::$encoder->startTag(SYNC_FOLDERID);
724                            self::$encoder->content($spa->GetFolderId());
725                        self::$encoder->endTag();
726
727                        self::$encoder->startTag(SYNC_STATUS);
728                            self::$encoder->content($status);
729                        self::$encoder->endTag();
730
731                        // announce failing status to the process loop detection
732                        if ($status !== SYNC_STATUS_SUCCESS)
733                            self::$deviceManager->AnnounceProcessStatus($spa->GetFolderId(), $status);
734
735                        // Output IDs and status for incoming items & requests
736                        if($status == SYNC_STATUS_SUCCESS && (
737                            !empty($actiondata["clientids"]) ||
738                            !empty($actiondata["modifyids"]) ||
739                            !empty($actiondata["removeids"]) ||
740                            !empty($actiondata["fetchids"]) )) {
741
742                            self::$encoder->startTag(SYNC_REPLIES);
743                            // output result of all new incoming items
744                            foreach($actiondata["clientids"] as $clientid => $serverid) {
745                                self::$encoder->startTag(SYNC_ADD);
746                                    self::$encoder->startTag(SYNC_CLIENTENTRYID);
747                                        self::$encoder->content($clientid);
748                                    self::$encoder->endTag();
749                                    if ($serverid) {
750                                        self::$encoder->startTag(SYNC_SERVERENTRYID);
751                                            self::$encoder->content($serverid);
752                                        self::$encoder->endTag();
753                                    }
754                                    self::$encoder->startTag(SYNC_STATUS);
755                                        self::$encoder->content((isset($actiondata["statusids"][$clientid])?$actiondata["statusids"][$clientid]:SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR));
756                                    self::$encoder->endTag();
757                                self::$encoder->endTag();
758                            }
759
760                            // loop through modify operations which were not a success, send status
761                            foreach($actiondata["modifyids"] as $serverid) {
762                                if (isset($actiondata["statusids"][$serverid]) && $actiondata["statusids"][$serverid] !== SYNC_STATUS_SUCCESS) {
763                                    self::$encoder->startTag(SYNC_MODIFY);
764                                        self::$encoder->startTag(SYNC_SERVERENTRYID);
765                                            self::$encoder->content($serverid);
766                                        self::$encoder->endTag();
767                                        self::$encoder->startTag(SYNC_STATUS);
768                                            self::$encoder->content($actiondata["statusids"][$serverid]);
769                                        self::$encoder->endTag();
770                                    self::$encoder->endTag();
771                                }
772                            }
773
774                            // loop through remove operations which were not a success, send status
775                            foreach($actiondata["removeids"] as $serverid) {
776                                if (isset($actiondata["statusids"][$serverid]) && $actiondata["statusids"][$serverid] !== SYNC_STATUS_SUCCESS) {
777                                    self::$encoder->startTag(SYNC_REMOVE);
778                                        self::$encoder->startTag(SYNC_SERVERENTRYID);
779                                            self::$encoder->content($serverid);
780                                        self::$encoder->endTag();
781                                        self::$encoder->startTag(SYNC_STATUS);
782                                            self::$encoder->content($actiondata["statusids"][$serverid]);
783                                        self::$encoder->endTag();
784                                    self::$encoder->endTag();
785                                }
786                            }
787
788                            if (!empty($actiondata["fetchids"]))
789                                self::$topCollector->AnnounceInformation(sprintf("Fetching %d objects ", count($actiondata["fetchids"])), true);
790
791                            foreach($actiondata["fetchids"] as $id) {
792                                $data = false;
793                                try {
794                                    $fetchstatus = SYNC_STATUS_SUCCESS;
795
796                                    // if this is an additional folder the backend has to be setup correctly
797                                    if (!self::$backend->Setup(ZPush::GetAdditionalSyncFolderStore($spa->GetFolderId())))
798                                        throw new StatusException(sprintf("HandleSync(): could not Setup() the backend to fetch in folder id '%s'", $spa->GetFolderId()), SYNC_STATUS_OBJECTNOTFOUND);
799
800                                    $data = self::$backend->Fetch($spa->GetFolderId(), $id, $spa->GetCPO());
801
802                                    // check if the message is broken
803                                    if (ZPush::GetDeviceManager(false) && ZPush::GetDeviceManager()->DoNotStreamMessage($id, $data)) {
804                                        ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): message not to be streamed as requested by DeviceManager.", $id));
805                                        $fetchstatus = SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR;
806                                    }
807                                }
808                                catch (StatusException $stex) {
809                                   $fetchstatus = $stex->getCode();
810                                }
811
812                                self::$encoder->startTag(SYNC_FETCH);
813                                    self::$encoder->startTag(SYNC_SERVERENTRYID);
814                                        self::$encoder->content($id);
815                                    self::$encoder->endTag();
816
817                                    self::$encoder->startTag(SYNC_STATUS);
818                                        self::$encoder->content($fetchstatus);
819                                    self::$encoder->endTag();
820
821                                    if($data !== false && $status == SYNC_STATUS_SUCCESS) {
822                                        self::$encoder->startTag(SYNC_DATA);
823                                            $data->Encode(self::$encoder);
824                                        self::$encoder->endTag();
825                                    }
826                                    else
827                                        ZLog::Write(LOGLEVEL_WARN, sprintf("Unable to Fetch '%s'", $id));
828                                self::$encoder->endTag();
829
830                            }
831                            self::$encoder->endTag();
832                        }
833
834                        if($sc->GetParameter($spa, "getchanges") && $spa->HasFolderId() && $spa->HasContentClass() && $spa->HasSyncKey()) {
835                            $windowSize = self::$deviceManager->GetWindowSize($spa->GetFolderId(), $spa->GetContentClass(), $spa->GetUuid(), $spa->GetUuidCounter(), $changecount);
836
837                            if($changecount > $windowSize) {
838                                self::$encoder->startTag(SYNC_MOREAVAILABLE, false, true);
839                            }
840                        }
841
842                        // Stream outgoing changes
843                        if($status == SYNC_STATUS_SUCCESS && $sc->GetParameter($spa, "getchanges") == true && $windowSize > 0) {
844                            self::$topCollector->AnnounceInformation(sprintf("Streaming data of %d objects", (($changecount > $windowSize)?$windowSize:$changecount)));
845
846                            // Output message changes per folder
847                            self::$encoder->startTag(SYNC_PERFORM);
848
849                            $n = 0;
850                            while(1) {
851                                try {
852                                    $progress = $exporter->Synchronize();
853                                    if(!is_array($progress))
854                                        break;
855                                    $n++;
856                                }
857                                catch (SyncObjectBrokenException $mbe) {
858                                    $brokenSO = $mbe->GetSyncObject();
859                                    if (!$brokenSO) {
860                                        ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): Catched SyncObjectBrokenException but broken SyncObject not available. This should be fixed in the backend."));
861                                    }
862                                    else {
863                                        if (!isset($brokenSO->id)) {
864                                            $brokenSO->id = "Unknown ID";
865                                            ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): Catched SyncObjectBrokenException but no ID of object set. This should be fixed in the backend."));
866                                        }
867                                        self::$deviceManager->AnnounceIgnoredMessage($spa->GetFolderId(), $brokenSO->id, $brokenSO);
868                                    }
869                                }
870
871                                if($n >= $windowSize) {
872                                    ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Exported maxItems of messages: %d / %d", $n, $changecount));
873                                    break;
874                                }
875
876                            }
877
878                            // $progress is not an array when exporting the last message
879                            // so we get the number to display from the streamimporter
880                            if (isset($streamimporter)) {
881                                $n = $streamimporter->GetImportedMessages();
882                            }
883
884                            self::$encoder->endTag();
885                            self::$topCollector->AnnounceInformation(sprintf("Outgoing %d objects%s", $n, ($n >= $windowSize)?" of ".$changecount:""), true);
886                        }
887
888                        self::$encoder->endTag();
889
890                        // Save the sync state for the next time
891                        if($spa->HasNewSyncKey()) {
892                            self::$topCollector->AnnounceInformation("Saving state");
893
894                            try {
895                                if (isset($exporter) && $exporter)
896                                    $state = $exporter->GetState();
897
898                                // nothing exported, but possibly imported - get the importer state
899                                else if ($sc->GetParameter($spa, "state") !== null)
900                                    $state = $sc->GetParameter($spa, "state");
901
902                                // if a new request without state information (hierarchy) save an empty state
903                                else if (! $spa->HasSyncKey())
904                                    $state = "";
905                            }
906                            catch (StatusException $stex) {
907                               $status = $stex->getCode();
908                            }
909
910
911                            if (isset($state) && $status == SYNC_STATUS_SUCCESS)
912                                self::$deviceManager->GetStateManager()->SetSyncState($spa->GetNewSyncKey(), $state, $spa->GetFolderId());
913                            else
914                                ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): error saving '%s' - no state information available", $spa->GetNewSyncKey()));
915                        }
916
917                        // save SyncParameters
918                        if ($status == SYNC_STATUS_SUCCESS && empty($actiondata["fetchids"]))
919                            $sc->SaveCollection($spa);
920
921                    } // END foreach collection
922                }
923                self::$encoder->endTag(); //SYNC_FOLDERS
924            }
925        }
926        self::$encoder->endTag(); //SYNC_SYNCHRONIZE
927
928        return true;
929    }
930
931    /**
932     * Loads the states and writes them into the SyncCollection Object and the actiondata failstate
933     *
934     * @param SyncCollection    $sc             SyncCollection object
935     * @param SyncParameters    $spa            SyncParameters object
936     * @param array             $actiondata     Actiondata array
937     * @param boolean           $loadFailsave   (opt) default false - indicates if the failsave states should be loaded
938     *
939     * @access private
940     * @return status           indicating if there were errors. If no errors, status is SYNC_STATUS_SUCCESS
941     */
942    private function loadStates($sc, $spa, &$actiondata, $loadFailsave = false) {
943        $status = SYNC_STATUS_SUCCESS;
944
945        if ($sc->GetParameter($spa, "state") == null) {
946            ZLog::Write(LOGLEVEL_DEBUG, sprintf("Sync->loadStates(): loading states for folder '%s'",$spa->GetFolderId()));
947
948            try {
949                $sc->AddParameter($spa, "state", self::$deviceManager->GetStateManager()->GetSyncState($spa->GetSyncKey()));
950
951                if ($loadFailsave) {
952                    // if this request was made before, there will be a failstate available
953                    $actiondata["failstate"] = self::$deviceManager->GetStateManager()->GetSyncFailState();
954                }
955
956                // if this is an additional folder the backend has to be setup correctly
957                if (!self::$backend->Setup(ZPush::GetAdditionalSyncFolderStore($spa->GetFolderId())))
958                    throw new StatusException(sprintf("HandleSync() could not Setup() the backend for folder id '%s'", $spa->GetFolderId()), SYNC_STATUS_FOLDERHIERARCHYCHANGED);
959            }
960            catch (StateNotFoundException $snfex) {
961                $status = SYNC_STATUS_INVALIDSYNCKEY;
962                self::$topCollector->AnnounceInformation("StateNotFoundException", true);
963            }
964            catch (StatusException $stex) {
965               $status = $stex->getCode();
966               self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), true);
967            }
968        }
969
970        return $status;
971    }
972
973    /**
974     * Initializes the importer for the SyncParameters folder, loads necessary
975     * states (incl. failsave states) and initializes the conflict detection
976     *
977     * @param SyncCollection    $sc             SyncCollection object
978     * @param SyncParameters    $spa            SyncParameters object
979     * @param array             $actiondata     Actiondata array
980     *
981     * @access private
982     * @return status           indicating if there were errors. If no errors, status is SYNC_STATUS_SUCCESS
983     */
984    private function getImporter($sc, $spa, &$actiondata) {
985        ZLog::Write(LOGLEVEL_DEBUG, "Sync->getImporter(): initialize importer");
986        $status = SYNC_STATUS_SUCCESS;
987
988        // load the states with failsave data
989        $status = $this->loadStates($sc, $spa, $actiondata, true);
990
991        try {
992            // Configure importer with last state
993            $this->importer = self::$backend->GetImporter($spa->GetFolderId());
994
995            // if something goes wrong, ask the mobile to resync the hierarchy
996            if ($this->importer === false)
997                throw new StatusException(sprintf("Sync->getImporter(): no importer for folder id '%s'", $spa->GetFolderId()), SYNC_STATUS_FOLDERHIERARCHYCHANGED);
998
999            // if there is a valid state obtained after importing changes in a previous loop, we use that state
1000            if (isset($actiondata["failstate"]) && isset($actiondata["failstate"]["failedsyncstate"])) {
1001                $this->importer->Config($actiondata["failstate"]["failedsyncstate"], $spa->GetConflict());
1002            }
1003            else
1004                $this->importer->Config($sc->GetParameter($spa, "state"), $spa->GetConflict());
1005
1006            // the CPO is also needed by the importer to check if imported changes
1007            // are inside the sync window - see ZP-258
1008            // TODO ConfigContentParameters needs to be defined in IImportChanges and all implementing importers/backends
1009            // this is currently only supported by the Zarafa Backend
1010            if (method_exists($this->importer, "ConfigContentParameters"))
1011                $this->importer->ConfigContentParameters($spa->GetCPO());
1012        }
1013        catch (StatusException $stex) {
1014           $status = $stex->getCode();
1015        }
1016
1017        $this->importer->LoadConflicts($spa->GetCPO(), $sc->GetParameter($spa, "state"));
1018
1019        return $status;
1020    }
1021
1022    /**
1023     * Imports a message
1024     *
1025     * @param SyncParameters    $spa            SyncParameters object
1026     * @param array             $actiondata     Actiondata array
1027     * @param integer           $todo           WBXML flag indicating how message should be imported.
1028     *                                          Valid values: SYNC_ADD, SYNC_MODIFY, SYNC_REMOVE
1029     * @param SyncObject        $message        SyncObject message to be imported
1030     * @param string            $clientid       Client message identifier
1031     * @param string            $serverid       Server message identifier
1032     * @param string            $foldertype     On sms sync, this says "SMS", else false
1033     * @param integer           $messageCount   Counter of already imported messages
1034     *
1035     * @access private
1036     * @throws StatusException  in case the importer is not available
1037     * @return -                Message related status are returned in the actiondata.
1038     */
1039    private function importMessage($spa, &$actiondata, $todo, $message, $clientid, $serverid, $foldertype, $messageCount) {
1040        // the importer needs to be available!
1041        if ($this->importer == false)
1042            throw StatusException(sprintf("Sync->importMessage(): importer not available", SYNC_STATUS_SERVERERROR));
1043
1044        // mark this state as used, e.g. for HeartBeat
1045        self::$deviceManager->SetHeartbeatStateIntegrity($spa->GetFolderId(), $spa->GetUuid(), $spa->GetUuidCounter());
1046
1047        // Detect incoming loop
1048        // messages which were created/removed before will not have the same action executed again
1049        // if a message is edited we perform this action "again", as the message could have been changed on the mobile in the meantime
1050        $ignoreMessage = false;
1051        if ($actiondata["failstate"]) {
1052            // message was ADDED before, do NOT add it again
1053            if ($todo == SYNC_ADD && isset($actiondata["failstate"]["clientids"][$clientid])) {
1054                $ignoreMessage = true;
1055
1056                // make sure no messages are sent back
1057                self::$deviceManager->SetWindowSize($spa->GetFolderId(), 0);
1058
1059                $actiondata["clientids"][$clientid] = $actiondata["failstate"]["clientids"][$clientid];
1060                $actiondata["statusids"][$clientid] = $actiondata["failstate"]["statusids"][$clientid];
1061
1062                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]));
1063            }
1064
1065            // message was REMOVED before, do NOT attemp to remove it again
1066            if ($todo == SYNC_REMOVE && isset($actiondata["failstate"]["removeids"][$serverid])) {
1067                $ignoreMessage = true;
1068
1069                // make sure no messages are sent back
1070                self::$deviceManager->SetWindowSize($spa->GetFolderId(), 0);
1071
1072                $actiondata["removeids"][$serverid] = $actiondata["failstate"]["removeids"][$serverid];
1073                $actiondata["statusids"][$serverid] = $actiondata["failstate"]["statusids"][$serverid];
1074
1075                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]));
1076            }
1077        }
1078
1079        if (!$ignoreMessage) {
1080            switch($todo) {
1081                case SYNC_MODIFY:
1082                    self::$topCollector->AnnounceInformation(sprintf("Saving modified message %d", $messageCount));
1083                    try {
1084                        $actiondata["modifyids"][] = $serverid;
1085
1086                        // ignore sms messages
1087                        if ($foldertype == "SMS" || stripos($serverid, self::ZPUSHIGNORESMS) !== false) {
1088                            ZLog::Write(LOGLEVEL_DEBUG, "SMS sync are not supported. Ignoring message.");
1089                            // TODO we should update the SMS
1090                            $actiondata["statusids"][$serverid] = SYNC_STATUS_SUCCESS;
1091                        }
1092                        // check incoming message without logging WARN messages about errors
1093                        else if (!($message instanceof SyncObject) || !$message->Check(true)) {
1094                            $actiondata["statusids"][$serverid] = SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR;
1095                        }
1096                        else {
1097                            if(isset($message->read)) {
1098                                // Currently, 'read' is only sent by the PDA when it is ONLY setting the read flag.
1099                                $this->importer->ImportMessageReadFlag($serverid, $message->read);
1100                            }
1101                            elseif (!isset($message->flag)) {
1102                                $this->importer->ImportMessageChange($serverid, $message);
1103                            }
1104
1105                            // email todoflags - some devices send todos flags together with read flags,
1106                            // so they have to be handled separately
1107                            if (isset($message->flag)){
1108                                $this->importer->ImportMessageChange($serverid, $message);
1109                            }
1110
1111                            $actiondata["statusids"][$serverid] = SYNC_STATUS_SUCCESS;
1112                        }
1113                    }
1114                    catch (StatusException $stex) {
1115                        $actiondata["statusids"][$serverid] = $stex->getCode();
1116                    }
1117
1118                    break;
1119                case SYNC_ADD:
1120                    self::$topCollector->AnnounceInformation(sprintf("Creating new message from mobile %d", $messageCount));
1121                    try {
1122                        // ignore sms messages
1123                        if ($foldertype == "SMS") {
1124                            ZLog::Write(LOGLEVEL_DEBUG, "SMS sync are not supported. Ignoring message.");
1125                            // TODO we should create the SMS
1126                            // return a fake serverid which we can identify later
1127                            $actiondata["clientids"][$clientid] = self::ZPUSHIGNORESMS . $clientid;
1128                            $actiondata["statusids"][$clientid] = SYNC_STATUS_SUCCESS;
1129                        }
1130                        // check incoming message without logging WARN messages about errors
1131                        else if (!($message instanceof SyncObject) || !$message->Check(true)) {
1132                            $actiondata["clientids"][$clientid] = false;
1133                            $actiondata["statusids"][$clientid] = SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR;
1134                        }
1135                        else {
1136                            $actiondata["clientids"][$clientid] = false;
1137                            $actiondata["clientids"][$clientid] = $this->importer->ImportMessageChange(false, $message);
1138                            $actiondata["statusids"][$clientid] = SYNC_STATUS_SUCCESS;
1139                        }
1140                    }
1141                    catch (StatusException $stex) {
1142                       $actiondata["statusids"][$clientid] = $stex->getCode();
1143                    }
1144                    break;
1145                case SYNC_REMOVE:
1146                    self::$topCollector->AnnounceInformation(sprintf("Deleting message removed on mobile %d", $messageCount));
1147                    try {
1148                        $actiondata["removeids"][] = $serverid;
1149                        // ignore sms messages
1150                        if ($foldertype == "SMS" || stripos($serverid, self::ZPUSHIGNORESMS) !== false) {
1151                            ZLog::Write(LOGLEVEL_DEBUG, "SMS sync are not supported. Ignoring message.");
1152                            // TODO we should delete the SMS
1153                            $actiondata["statusids"][$serverid] = SYNC_STATUS_SUCCESS;
1154                        }
1155                        else {
1156                            // if message deletions are to be moved, move them
1157                            if($spa->GetDeletesAsMoves() && $foldertype != SYNC_FOLDER_TYPE_APPOINTMENT && $foldertype != SYNC_FOLDER_TYPE_CONTACT ) {
1158
1159                                $folderid = self::$backend->GetWasteBasket();
1160
1161                                if($folderid) {
1162                                    $this->importer->ImportMessageMove($serverid, $folderid);
1163                                    $actiondata["statusids"][$serverid] = SYNC_STATUS_SUCCESS;
1164                                    break;
1165                                }
1166                                else
1167                                    ZLog::Write(LOGLEVEL_WARN, "Message should be moved to WasteBasket, but the Backend did not return a destination ID. Message is hard deleted now!");
1168                            }
1169
1170                            $this->importer->ImportMessageDeletion($serverid);
1171                            $actiondata["statusids"][$serverid] = SYNC_STATUS_SUCCESS;
1172                        }
1173                    }
1174                    catch (StatusException $stex) {
1175                       $actiondata["statusids"][$serverid] = $stex->getCode();
1176                    }
1177                    break;
1178            }
1179            ZLog::Write(LOGLEVEL_DEBUG, "Sync->importMessage(): message imported");
1180        }
1181    }
1182}
1183
1184?>
Note: See TracBrowser for help on using the repository browser.