Back to index

d-push  2.0
Public Member Functions | Static Public Member Functions | Static Protected Attributes | Private Member Functions | Private Attributes
Sync Class Reference
Inheritance diagram for Sync:
Inheritance graph
[legend]
Collaboration diagram for Sync:
Collaboration graph
[legend]

List of all members.

Public Member Functions

 Handle ($commandCode)
 Handles the Sync command Performs the synchronization of messages.

Static Public Member Functions

static Authenticate ()
 Authenticates the remote user The sent HTTP authentication information is used to on Backend->Logon().
static isUserAuthenticated ()
 Indicates if the user was "authenticated".
static Initialize ()
 Initialize the RequestProcessor.
static HandleRequest ()
 Loads the command handler and processes a command sent from the mobile.

Static Protected Attributes

static $backend
static $deviceManager
static $topCollector
static $decoder
static $encoder
static $userIsAuthenticated

Private Member Functions

 loadStates ($sc, $spa, &$actiondata, $loadFailsave=false)
 Loads the states and writes them into the SyncCollection Object and the actiondata failstate.
 getImporter ($sc, $spa, &$actiondata)
 Initializes the importer for the SyncParameters folder, loads necessary states (incl.
 importMessage ($spa, &$actiondata, $todo, $message, $clientid, $serverid)
 Imports a message.

Private Attributes

 $importer

Detailed Description

Definition at line 44 of file sync.php.


Member Function Documentation

static RequestProcessor::Authenticate ( ) [static, inherited]

Authenticates the remote user The sent HTTP authentication information is used to on Backend->Logon().

As second step the GET-User verified by Backend->Setup() for permission check Request::GetGETUser() is usually the same as the Request::GetAuthUser(). If the GETUser is different from the AuthUser, the AuthUser MUST HAVE admin permissions on GETUsers data store. Only then the Setup() will be sucessfull. This allows the user 'john' to do operations as user 'joe' if he has sufficient privileges.

public

Returns:
Exceptions:
AuthenticationRequiredException

Definition at line 72 of file requestprocessor.php.

                                          {
        self::$userIsAuthenticated = false;

        $backend = ZPush::GetBackend();
        if($backend->Logon(Request::GetAuthUser(), Request::GetAuthDomain(), Request::GetAuthPassword()) == false)
            throw new AuthenticationRequiredException("Access denied. Username or password incorrect");

        // mark this request as "authenticated"
        self::$userIsAuthenticated = true;

        // check Auth-User's permissions on GETUser's store
        if($backend->Setup(Request::GetGETUser(), true) == false)
            throw new AuthenticationRequiredException(sprintf("Not enough privileges of '%s' to setup for user '%s': Permission denied", Request::GetAuthUser(), Request::GetGETUser()));
    }

Here is the call graph for this function:

Here is the caller graph for this function:

Sync::getImporter ( sc,
spa,
&$  actiondata 
) [private]

Initializes the importer for the SyncParameters folder, loads necessary states (incl.

failsave states) and initializes the conflict detection

Parameters:
SyncCollection$scSyncCollection object
SyncParameters$spaSyncParameters object
array$actiondataActiondata array

private

Returns:
status indicating if there were errors. If no errors, status is SYNC_STATUS_SUCCESS

Definition at line 946 of file sync.php.

                                                          {
        ZLog::Write(LOGLEVEL_DEBUG, "Sync->getImporter(): initialize importer");
        $status = SYNC_STATUS_SUCCESS;

        // load the states with failsave data
        $status = $this->loadStates($sc, $spa, $actiondata, true);

        try {
            // Configure importer with last state
            $this->importer = self::$backend->GetImporter($spa->GetFolderId());

            // if something goes wrong, ask the mobile to resync the hierarchy
            if ($this->importer === false)
                throw new StatusException(sprintf("Sync->getImporter(): no importer for folder id '%s'", $spa->GetFolderId()), SYNC_STATUS_FOLDERHIERARCHYCHANGED);

            // if there is a valid state obtained after importing changes in a previous loop, we use that state
            if ($actiondata["failstate"] && isset($actiondata["failstate"]["failedsyncstate"])) {
                $this->importer->Config($actiondata["failstate"]["failedsyncstate"], $spa->GetConflict());
            }
            else
                $this->importer->Config($sc->GetParameter($spa, "state"), $spa->GetConflict());
        }
        catch (StatusException $stex) {
           $status = $stex->getCode();
        }

        $this->importer->LoadConflicts($spa->GetCPO(), $sc->GetParameter($spa, "state"));

        return $status;
    }

Here is the call graph for this function:

Here is the caller graph for this function:

Sync::Handle ( commandCode)

Handles the Sync command Performs the synchronization of messages.

Parameters:
int$commandCodepublic
Returns:
boolean

Reimplemented from RequestProcessor.

Definition at line 56 of file sync.php.

                                         {
        // Contains all requested folders (containers)
        $sc = new SyncCollections();
        $status = SYNC_STATUS_SUCCESS;
        $wbxmlproblem = false;
        $emtpysync = false;

        // Start Synchronize
        if(self::$decoder->getElementStartTag(SYNC_SYNCHRONIZE)) {

            // AS 1.0 sends version information in WBXML
            if(self::$decoder->getElementStartTag(SYNC_VERSION)) {
                $sync_version = self::$decoder->getElementContent();
                ZLog::Write(LOGLEVEL_DEBUG, sprintf("WBXML sync version: '%s'", $sync_version));
                if(!self::$decoder->getElementEndTag())
                    return false;
            }

            // Synching specified folders
            if(self::$decoder->getElementStartTag(SYNC_FOLDERS)) {
                while(self::$decoder->getElementStartTag(SYNC_FOLDER)) {
                    $actiondata = array();
                    $actiondata["requested"] = true;
                    $actiondata["clientids"] = array();
                    $actiondata["modifyids"] = array();
                    $actiondata["removeids"] = array();
                    $actiondata["fetchids"] = array();
                    $actiondata["statusids"] = array();

                    // read class, synckey and folderid without SyncParameters Object for now
                    $class = $synckey = $folderid = false;

                    //for AS versions < 2.5
                    if(self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) {
                        $class = self::$decoder->getElementContent();
                        ZLog::Write(LOGLEVEL_DEBUG, sprintf("Sync folder: '%s'", $class));

                        if(!self::$decoder->getElementEndTag())
                            return false;
                    }

                    // SyncKey
                    if(!self::$decoder->getElementStartTag(SYNC_SYNCKEY))
                        return false;
                    $synckey = self::$decoder->getElementContent();
                    if(!self::$decoder->getElementEndTag())
                        return false;

                    // FolderId
                    if(self::$decoder->getElementStartTag(SYNC_FOLDERID)) {
                        $folderid = self::$decoder->getElementContent();

                        if(!self::$decoder->getElementEndTag())
                            return false;
                    }

                    // compatibility mode AS 1.0 - get folderid which was sent during GetHierarchy()
                    if (! $folderid && $class) {
                        $folderid = self::$deviceManager->GetFolderIdFromCacheByClass($class);
                    }

                    // folderid HAS TO BE known by now, so we retrieve the correct SyncParameters object for an update
                    try {
                        $spa = self::$deviceManager->GetStateManager()->GetSynchedFolderState($folderid);

                        // TODO remove resync of folders for < Z-Push 2 beta4 users
                        // this forces a resync of all states previous to Z-Push 2 beta4
                        if (! $spa instanceof SyncParameters)
                            throw new StateInvalidException("Saved state are not of type SyncParameters");

                        // new/resync requested
                        if ($synckey == "0")
                            $spa->RemoveSyncKey();
                        else if ($synckey !== false)
                            $spa->SetSyncKey($synckey);
                    }
                    catch (StateInvalidException $stie) {
                        $spa = new SyncParameters();
                        $status = SYNC_STATUS_INVALIDSYNCKEY;
                        self::$topCollector->AnnounceInformation("State invalid - Resync folder", true);
                        self::$deviceManager->ForceFolderResync($folderid);
                    }

                    // update folderid.. this might be a new object
                    $spa->SetFolderId($folderid);

                    if ($class !== false)
                        $spa->SetContentClass($class);

                    // Get class for as versions >= 12.0
                    if (! $spa->HasContentClass()) {
                        try {
                            $spa->SetContentClass(self::$deviceManager->GetFolderClassFromCacheByID($spa->GetFolderId()));
                            ZLog::Write(LOGLEVEL_DEBUG, sprintf("GetFolderClassFromCacheByID from Device Manager: '%s' for id:'%s'", $spa->GetContentClass(), $spa->GetFolderId()));
                        }
                        catch (NoHierarchyCacheAvailableException $nhca) {
                            $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED;
                            self::$deviceManager->ForceFullResync();
                        }
                    }

                    // done basic SPA initialization/loading -> add to SyncCollection
                    $sc->AddCollection($spa);
                    $sc->AddParameter($spa, "requested", true);

                    if ($spa->HasContentClass())
                        self::$topCollector->AnnounceInformation(sprintf("%s request", $spa->GetContentClass()), true);
                    else
                        ZLog::Write(LOGLEVEL_WARN, "Not possible to determine class of request. Request did not contain class and apparently there is an issue with the HierarchyCache.");

                    // SUPPORTED properties
                    if(self::$decoder->getElementStartTag(SYNC_SUPPORTED)) {
                        $supfields = array();
                        while(1) {
                            $el = self::$decoder->getElement();

                            if($el[EN_TYPE] == EN_TYPE_ENDTAG)
                                break;
                            else
                                $supfields[] = $el[EN_TAG];
                        }
                        self::$deviceManager->SetSupportedFields($spa->GetFolderId(), $supfields);
                    }

                    // Deletes as moves can be an empty tag as well as have value
                    if(self::$decoder->getElementStartTag(SYNC_DELETESASMOVES)) {
                        $spa->SetDeletesAsMoves(true);
                        if (($dam = self::$decoder->getElementContent()) !== false) {
                            $spa->SetDeletesAsMoves((boolean)$dam);
                            if(!self::$decoder->getElementEndTag()) {
                                return false;
                            }
                        }
                    }

                    // Get changes can be an empty tag as well as have value
                    // code block partly contributed by dw2412
                    if(self::$decoder->getElementStartTag(SYNC_GETCHANGES)) {
                        $sc->AddParameter($spa, "getchanges", true);
                        if (($gc = self::$decoder->getElementContent()) !== false) {
                            $sc->AddParameter($spa, "getchanges", $gc);
                            if(!self::$decoder->getElementEndTag()) {
                                return false;
                            }
                        }
                    }

                    if(self::$decoder->getElementStartTag(SYNC_WINDOWSIZE)) {
                        $spa->SetWindowSize(self::$decoder->getElementContent());

                        // also announce the currently requested window size to the DeviceManager
                        self::$deviceManager->SetWindowSize($spa->GetFolderId(), $spa->GetWindowSize());

                        if(!self::$decoder->getElementEndTag())
                            return false;
                    }

                    // conversation mode requested
                    if(self::$decoder->getElementStartTag(SYNC_CONVERSATIONMODE)) {
                        $spa->SetConversationMode(true);
                        if(($conversationmode = self::$decoder->getElementContent()) !== false) {
                            $spa->SetConversationMode((boolean)$conversationmode);
                            if(!self::$decoder->getElementEndTag())
                            return false;
                        }
                    }

                    // Do not truncate by default
                    $spa->SetTruncation(SYNC_TRUNCATION_ALL);
                    // set to synchronize all changes. The mobile could overwrite this value
                    $spa->SetFilterType(SYNC_FILTERTYPE_ALL);

                    while(self::$decoder->getElementStartTag(SYNC_OPTIONS)) {
                        while(1) {
                            if(self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) {
                                $foldertype = self::$decoder->getElementContent();
                                ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): specified options block with foldertype '%s'", $foldertype));

                                // switch the foldertype for the next options
                                $spa->UseCPO($foldertype);
                                if(!self::$decoder->getElementEndTag())
                                return false;
                            }

                            if(self::$decoder->getElementStartTag(SYNC_FILTERTYPE)) {
                                $spa->SetFilterType(self::$decoder->getElementContent());
                                if(!self::$decoder->getElementEndTag())
                                    return false;
                            }
                            if(self::$decoder->getElementStartTag(SYNC_TRUNCATION)) {
                                $spa->SetTruncation(self::$decoder->getElementContent());
                                if(!self::$decoder->getElementEndTag())
                                    return false;
                            }
                            if(self::$decoder->getElementStartTag(SYNC_RTFTRUNCATION)) {
                                $spa->SetRTFTruncation(self::$decoder->getElementContent());
                                if(!self::$decoder->getElementEndTag())
                                    return false;
                            }

                            if(self::$decoder->getElementStartTag(SYNC_MIMESUPPORT)) {
                                $spa->SetMimeSupport(self::$decoder->getElementContent());
                                if(!self::$decoder->getElementEndTag())
                                    return false;
                            }

                            if(self::$decoder->getElementStartTag(SYNC_MIMETRUNCATION)) {
                                $spa->SetMimeTruncation(self::$decoder->getElementContent());
                                if(!self::$decoder->getElementEndTag())
                                    return false;
                            }

                            if(self::$decoder->getElementStartTag(SYNC_CONFLICT)) {
                                $spa->SetConflict(self::$decoder->getElementContent());
                                if(!self::$decoder->getElementEndTag())
                                    return false;
                            }

                            while (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_BODYPREFERENCE)) {
                                if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TYPE)) {
                                    $bptype = self::$decoder->getElementContent();
                                    $spa->BodyPreference($bptype);
                                    if(!self::$decoder->getElementEndTag()) {
                                        return false;
                                    }
                                }

                                if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TRUNCATIONSIZE)) {
                                    $spa->BodyPreference($bptype)->SetTruncationSize(self::$decoder->getElementContent());
                                    if(!self::$decoder->getElementEndTag())
                                        return false;
                                }

                                if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_ALLORNONE)) {
                                    $spa->BodyPreference($bptype)->SetAllOrNone(self::$decoder->getElementContent());
                                    if(!self::$decoder->getElementEndTag())
                                        return false;
                                }

                                if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_PREVIEW)) {
                                    $spa->BodyPreference($bptype)->SetPreview(self::$decoder->getElementContent());
                                    if(!self::$decoder->getElementEndTag())
                                        return false;
                                }

                                if(!self::$decoder->getElementEndTag())
                                    return false;
                            }

                            $e = self::$decoder->peek();
                            if($e[EN_TYPE] == EN_TYPE_ENDTAG) {
                                self::$decoder->getElementEndTag();
                                break;
                            }
                        }
                    }

                    // limit items to be synchronized to the mobiles if configured
                    if (defined('SYNC_FILTERTIME_MAX') && SYNC_FILTERTIME_MAX > SYNC_FILTERTYPE_ALL &&
                        (!$spa->HasFilterType() || $spa->GetFilterType() == SYNC_FILTERTYPE_ALL || $spa->GetFilterType() > SYNC_FILTERTIME_MAX)) {
                            ZLog::Write(LOGLEVEL_DEBUG, sprintf("SYNC_FILTERTIME_MAX defined. Filter set to value: %s", SYNC_FILTERTIME_MAX));
                            $spa->SetFilterType(SYNC_FILTERTIME_MAX);
                    }

                    // set default conflict behavior from config if the device doesn't send a conflict resolution parameter
                    if (! $spa->HasConflict()) {
                        $spa->SetConflict(SYNC_CONFLICT_DEFAULT);
                    }

                    // Check if the hierarchycache is available. If not, trigger a HierarchySync
                    if (self::$deviceManager->IsHierarchySyncRequired()) {
                        $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED;
                        ZLog::Write(LOGLEVEL_DEBUG, "HierarchyCache is also not available. Triggering HierarchySync to device");
                    }

                    if(self::$decoder->getElementStartTag(SYNC_PERFORM)) {
                        // We can not proceed here as the content class is unknown
                        if ($status != SYNC_STATUS_SUCCESS) {
                            ZLog::Write(LOGLEVEL_WARN, "Ignoring all incoming actions as global status indicates problem.");
                            $wbxmlproblem = true;
                            break;
                        }

                        $performaction = true;

                        // unset the importer
                        $this->importer = false;

                        $nchanges = 0;
                        while(1) {
                            // ADD, MODIFY, REMOVE or FETCH
                            $element = self::$decoder->getElement();

                            if($element[EN_TYPE] != EN_TYPE_STARTTAG) {
                                self::$decoder->ungetElement($element);
                                break;
                            }

                            if ($status == SYNC_STATUS_SUCCESS)
                                $nchanges++;

                            if(self::$decoder->getElementStartTag(SYNC_SERVERENTRYID)) {
                                $serverid = self::$decoder->getElementContent();

                                if(!self::$decoder->getElementEndTag()) // end serverid
                                    return false;
                            }
                            else
                                $serverid = false;

                            if(self::$decoder->getElementStartTag(SYNC_CLIENTENTRYID)) {
                                $clientid = self::$decoder->getElementContent();

                                if(!self::$decoder->getElementEndTag()) // end clientid
                                    return false;
                            }
                            else
                                $clientid = false;

                            // Get the SyncMessage if sent
                            if(self::$decoder->getElementStartTag(SYNC_DATA)) {
                                $message = ZPush::getSyncObjectFromFolderClass($spa->GetContentClass());
                                $message->Decode(self::$decoder);

                                // set Ghosted fields
                                $message->emptySupported(self::$deviceManager->GetSupportedFields($spa->GetFolderId()));
                                if(!self::$decoder->getElementEndTag()) // end applicationdata
                                    return false;
                            }
                            else
                                $message = false;

                            if ($status != SYNC_STATUS_SUCCESS) {
                                ZLog::Write(LOGLEVEL_WARN, "Ignored incoming change, global status indicates problem.");
                                continue;
                            }

                            switch($element[EN_TAG]) {
                                case SYNC_FETCH:
                                    array_push($actiondata["fetchids"], $serverid);
                                    break;
                                default:
                                    // get the importer
                                    if ($this->importer == false)
                                        $status = $this->getImporter($sc, $spa, $actiondata);

                                    if ($status == SYNC_STATUS_SUCCESS)
                                        $this->importMessage($spa, $actiondata, $element[EN_TAG], $message, $clientid, $serverid);

                                    break;
                            }

                            if ($actiondata["fetchids"])
                                self::$topCollector->AnnounceInformation(sprintf("Fetching %d", $nchanges),true);
                            else
                                self::$topCollector->AnnounceInformation(sprintf("Incoming %d", $nchanges),($nchanges>0)?true:false);

                            if(!self::$decoder->getElementEndTag()) // end add/change/delete/move
                                return false;
                        }

                        if ($status == SYNC_STATUS_SUCCESS && $this->importer !== false) {
                            ZLog::Write(LOGLEVEL_INFO, sprintf("Processed '%d' incoming changes", $nchanges));
                            try {
                                // Save the updated state, which is used for the exporter later
                                $sc->AddParameter($spa, "state", $this->importer->GetState());
                            }
                            catch (StatusException $stex) {
                               $status = $stex->getCode();
                            }
                        }

                        if(!self::$decoder->getElementEndTag()) // end PERFORM
                            return false;
                    }

                    // save the failsave state
                    if (!empty($actiondata["statusids"])) {
                        unset($actiondata["failstate"]);
                        $actiondata["failedsyncstate"] = $sc->GetParameter($spa, "state");
                        self::$deviceManager->GetStateManager()->SetSyncFailState($actiondata);
                    }

                    // save actiondata
                    $sc->AddParameter($spa, "actiondata", $actiondata);

                    if(!self::$decoder->getElementEndTag()) // end collection
                        return false;

                    // AS14 does not send GetChanges anymore. We should do it if there were no incoming changes
                    if (!isset($performaction) && !$sc->GetParameter($spa, "getchanges") && $spa->HasSyncKey())
                        $sc->AddParameter($spa, "getchanges", true);
                } // END FOLDER

                if(!$wbxmlproblem && !self::$decoder->getElementEndTag()) // end collections
                    return false;
            } // end FOLDERS

            if (self::$decoder->getElementStartTag(SYNC_HEARTBEATINTERVAL)) {
                $hbinterval = self::$decoder->getElementContent();
                if(!self::$decoder->getElementEndTag()) // SYNC_HEARTBEATINTERVAL
                    return false;
            }

            if (self::$decoder->getElementStartTag(SYNC_WAIT)) {
                $wait = self::$decoder->getElementContent();
                if(!self::$decoder->getElementEndTag()) // SYNC_WAIT
                    return false;

                // internally the heartbeat interval and the wait time are the same
                // heartbeat is in seconds, wait in minutes
                $hbinterval = $wait * 60;
            }

            if (self::$decoder->getElementStartTag(SYNC_WINDOWSIZE)) {
                $sc->SetGlobalWindowSize(self::$decoder->getElementContent());
                if(!self::$decoder->getElementEndTag()) // SYNC_WINDOWSIZE
                    return false;
            }

            if(self::$decoder->getElementStartTag(SYNC_PARTIAL))
                $partial = true;
            else
                $partial = false;

            if(!$wbxmlproblem && !self::$decoder->getElementEndTag()) // end sync
                return false;
        }
        // we did not receive a SYNCHRONIZE block - assume empty sync
        else {
            $emtpysync = true;
        }
        // END SYNCHRONIZE

        // check heartbeat/wait time
        if (isset($hbinterval)) {
            if ($hbinterval < 60 || $hbinterval > 3540) {
                $status = SYNC_STATUS_INVALIDWAITORHBVALUE;
                ZLog::Write(LOGLEVEL_WARN, sprintf("HandleSync(): Invalid heartbeat or wait value '%s'", $hbinterval));
            }
        }

        // Partial & Empty Syncs need saved data to proceed with synchronization
        if ($status == SYNC_STATUS_SUCCESS && (! $sc->HasCollections() || $partial === true )) {
            ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Partial or Empty sync requested. Retrieving data of synchronized folders."));

            // Load all collections - do not overwrite existing (received!), laod states and check permissions
            try {
                $sc->LoadAllCollections(false, true, true);
            }
            catch (StateNotFoundException $snfex) {
                $status = SYNC_STATUS_INVALIDSYNCKEY;
                self::$topCollector->AnnounceInformation("StateNotFoundException", true);
            }
            catch (StatusException $stex) {
               $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED;
               self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), true);
            }

            // update a few values
            foreach($sc as $folderid => $spa) {
                // manually set getchanges parameter for this collection
                $sc->AddParameter($spa, "getchanges", true);

                // set new global windowsize without marking the SPA as changed
                if ($sc->GetGlobalWindowSize())
                    $spa->SetWindowSize($sc->GetGlobalWindowSize(), false);

                // announce WindowSize to DeviceManager
                self::$deviceManager->SetWindowSize($folderid, $spa->GetWindowSize());
            }
            if (!$sc->HasCollections())
                $status = SYNC_STATUS_SYNCREQUESTINCOMPLETE;
        }

        // HEARTBEAT & Empty sync
        if ($status == SYNC_STATUS_SUCCESS && (isset($hbinterval) || $emtpysync == true)) {
            $interval = (defined('PING_INTERVAL') && PING_INTERVAL > 0) ? PING_INTERVAL : 30;

            if (isset($hbinterval))
                $sc->SetLifetime($hbinterval);

            // states are lazy loaded - we have to make sure that they are there!
            foreach($sc as $folderid => $spa) {
                $fad = array();
                // if loading the states fails, we do not enter heartbeat, but we keep $status on SYNC_STATUS_SUCCESS
                // so when the changes are exported the correct folder gets an SYNC_STATUS_INVALIDSYNCKEY
                $loadstatus = $this->loadStates($sc, $spa, $fad);
            }

            if ($loadstatus = SYNC_STATUS_SUCCESS) {
                $foundchanges = false;

                // wait for changes
                try {
                    // if doing an empty sync, check only once for changes
                    if ($emtpysync) {
                        $foundchanges = $sc->CountChanges();
                    }
                    // wait for changes
                    else {
                        ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Entering Heartbeat mode"));
                        $foundchanges = $sc->CheckForChanges($sc->GetLifetime(), $interval);
                    }
                }
                catch (StatusException $stex) {
                   $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED;
                   self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), true);
                }

                // in case of an empty sync with no changes, we can reply with an empty response
                if ($emtpysync && !$foundchanges){
                    ZLog::Write(LOGLEVEL_DEBUG, "No changes found for empty sync. Replying with empty response");
                    return true;
                }

                if ($foundchanges) {
                    foreach ($sc->GetChangedFolderIds() as $folderid => $changecount) {
                        // check if there were other sync requests for a folder during the heartbeat
                        $spa = $sc->GetCollection($folderid);
                        if ($changecount > 0 && $sc->WaitedForChanges() && self::$deviceManager->CheckHearbeatStateIntegrity($spa->GetFolderId(), $spa->GetUuid(), $spa->GetUuidCounter())) {
                            ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): heartbeat: found %d changes in '%s' which was already synchronized. Heartbeat aborted!", $changecount, $folderid));
                            $status = SYNC_COMMONSTATUS_SYNCSTATEVERSIONINVALID;
                        }
                        else
                            ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): heartbeat: found %d changes in '%s'", $changecount, $folderid));
                    }
                }
            }
        }

        ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Start Output"));

        // Start the output
        self::$encoder->startWBXML();
        self::$encoder->startTag(SYNC_SYNCHRONIZE);
        {
            // global status
            // SYNC_COMMONSTATUS_* start with values from 101
            if ($status != SYNC_COMMONSTATUS_SUCCESS && $status > 100) {
                self::$encoder->startTag(SYNC_STATUS);
                    self::$encoder->content($status);
                self::$encoder->endTag();
            }
            else {
                self::$encoder->startTag(SYNC_FOLDERS);
                {
                    foreach($sc as $folderid => $spa) {
                        // get actiondata
                        $actiondata = $sc->GetParameter($spa, "actiondata");

                        if ($status == SYNC_STATUS_SUCCESS && (!$spa->GetContentClass() || !$spa->GetFolderId())) {
                            ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): no content class or folderid found for collection."));
                            continue;
                        }

                        if (! $sc->GetParameter($spa, "requested"))
                            ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): partial sync for folder class '%s' with id '%s'", $spa->GetContentClass(), $spa->GetFolderId()));

                        // initialize exporter to get changecount
                        $changecount = 0;
                        if (isset($exporter))
                            unset($exporter);

                        // TODO we could check against $sc->GetChangedFolderIds() on heartbeat so we do not need to configure all exporter again
                        if($status == SYNC_STATUS_SUCCESS && ($sc->GetParameter($spa, "getchanges") || ! $spa->HasSyncKey())) {

                            //make sure the states are loaded
                            $status = $this->loadStates($sc, $spa, $actiondata);

                            if($status == SYNC_STATUS_SUCCESS) {
                                try {
                                    // Use the state from the importer, as changes may have already happened
                                    $exporter = self::$backend->GetExporter($spa->GetFolderId());

                                    if ($exporter === false)
                                        throw new StatusException(sprintf("HandleSync() could not get an exporter for folder id '%s'", $spa->GetFolderId()), SYNC_STATUS_FOLDERHIERARCHYCHANGED);
                                }
                                catch (StatusException $stex) {
                                   $status = $stex->getCode();
                                }
                                try {
                                    // Stream the messages directly to the PDA
                                    $streamimporter = new ImportChangesStream(self::$encoder, ZPush::getSyncObjectFromFolderClass($spa->GetContentClass()));

                                    if ($exporter !== false) {
                                        $exporter->Config($sc->GetParameter($spa, "state"));
                                        $exporter->ConfigContentParameters($spa->GetCPO());
                                        $exporter->InitializeExporter($streamimporter);

                                        $changecount = $exporter->GetChangeCount();
                                    }
                                }
                                catch (StatusException $stex) {
                                    if ($stex->getCode() === SYNC_FSSTATUS_CODEUNKNOWN && $spa->HasSyncKey())
                                        $status = SYNC_STATUS_INVALIDSYNCKEY;
                                    else
                                        $status = $stex->getCode();
                                }

                                if (! $spa->HasSyncKey())
                                    self::$topCollector->AnnounceInformation(sprintf("Exporter registered. %d objects queued.", $changecount), true);
                                else if ($status != SYNC_STATUS_SUCCESS)
                                    self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), true);
                            }
                        }

                        if (isset($hbinterval) && $changecount == 0 && $status == SYNC_STATUS_SUCCESS) {
                            ZLog::Write(LOGLEVEL_DEBUG, "No changes found for heartbeat folder. Omitting empty output.");
                            continue;
                        }

                        // Get a new sync key to output to the client if any changes have been send or will are available
                        if (!empty($actiondata["modifyids"]) ||
                            !empty($actiondata["clientids"]) ||
                            !empty($actiondata["removeids"]) ||
                            $changecount > 0 || (! $spa->HasSyncKey() && $status == SYNC_STATUS_SUCCESS))
                                $spa->SetNewSyncKey(self::$deviceManager->GetStateManager()->GetNewSyncKey($spa->GetSyncKey()));

                        self::$encoder->startTag(SYNC_FOLDER);

                        if($spa->HasContentClass()) {
                            self::$encoder->startTag(SYNC_FOLDERTYPE);
                                self::$encoder->content($spa->GetContentClass());
                            self::$encoder->endTag();
                        }

                        self::$encoder->startTag(SYNC_SYNCKEY);
                        if($status == SYNC_STATUS_SUCCESS && $spa->HasNewSyncKey())
                            self::$encoder->content($spa->GetNewSyncKey());
                        else
                            self::$encoder->content($spa->GetSyncKey());
                        self::$encoder->endTag();

                        self::$encoder->startTag(SYNC_FOLDERID);
                            self::$encoder->content($spa->GetFolderId());
                        self::$encoder->endTag();

                        self::$encoder->startTag(SYNC_STATUS);
                            self::$encoder->content($status);
                        self::$encoder->endTag();

                        // announce failing status to the process loop detection
                        if ($status !== SYNC_STATUS_SUCCESS)
                            self::$deviceManager->AnnounceProcessStatus($spa->GetFolderId(), $status);

                        // Output IDs and status for incoming items & requests
                        if($status == SYNC_STATUS_SUCCESS && (
                            !empty($actiondata["clientids"]) ||
                            !empty($actiondata["modifyids"]) ||
                            !empty($actiondata["removeids"]) ||
                            !empty($actiondata["fetchids"]) )) {

                            self::$encoder->startTag(SYNC_REPLIES);
                            // output result of all new incoming items
                            foreach($actiondata["clientids"] as $clientid => $serverid) {
                                self::$encoder->startTag(SYNC_ADD);
                                    self::$encoder->startTag(SYNC_CLIENTENTRYID);
                                        self::$encoder->content($clientid);
                                    self::$encoder->endTag();
                                    if ($serverid) {
                                        self::$encoder->startTag(SYNC_SERVERENTRYID);
                                            self::$encoder->content($serverid);
                                        self::$encoder->endTag();
                                    }
                                    self::$encoder->startTag(SYNC_STATUS);
                                        self::$encoder->content((isset($actiondata["statusids"][$clientid])?$actiondata["statusids"][$clientid]:SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR));
                                    self::$encoder->endTag();
                                self::$encoder->endTag();
                            }

                            // loop through modify operations which were not a success, send status
                            foreach($actiondata["modifyids"] as $serverid) {
                                if (isset($actiondata["statusids"][$serverid]) && $actiondata["statusids"][$serverid] !== SYNC_STATUS_SUCCESS) {
                                    self::$encoder->startTag(SYNC_MODIFY);
                                        self::$encoder->startTag(SYNC_SERVERENTRYID);
                                            self::$encoder->content($serverid);
                                        self::$encoder->endTag();
                                        self::$encoder->startTag(SYNC_STATUS);
                                            self::$encoder->content($actiondata["statusids"][$serverid]);
                                        self::$encoder->endTag();
                                    self::$encoder->endTag();
                                }
                            }

                            // loop through remove operations which were not a success, send status
                            foreach($actiondata["removeids"] as $serverid) {
                                if (isset($actiondata["statusids"][$serverid]) && $actiondata["statusids"][$serverid] !== SYNC_STATUS_SUCCESS) {
                                    self::$encoder->startTag(SYNC_REMOVE);
                                        self::$encoder->startTag(SYNC_SERVERENTRYID);
                                            self::$encoder->content($serverid);
                                        self::$encoder->endTag();
                                        self::$encoder->startTag(SYNC_STATUS);
                                            self::$encoder->content($actiondata["statusids"][$serverid]);
                                        self::$encoder->endTag();
                                    self::$encoder->endTag();
                                }
                            }

                            if (!empty($actiondata["fetchids"]))
                                self::$topCollector->AnnounceInformation(sprintf("Fetching %d objects ", count($actiondata["fetchids"])), true);

                            foreach($actiondata["fetchids"] as $id) {
                                $data = false;
                                try {
                                    $fetchstatus = SYNC_STATUS_SUCCESS;
                                    $data = self::$backend->Fetch($spa->GetFolderId(), $id, $spa->GetCPO());

                                    // check if the message is broken
                                    if (ZPush::GetDeviceManager(false) && ZPush::GetDeviceManager()->DoNotStreamMessage($id, $data)) {
                                        ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): message not to be streamed as requested by DeviceManager.", $id));
                                        $fetchstatus = SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR;
                                    }
                                }
                                catch (StatusException $stex) {
                                   $fetchstatus = $stex->getCode();
                                }

                                self::$encoder->startTag(SYNC_FETCH);
                                    self::$encoder->startTag(SYNC_SERVERENTRYID);
                                        self::$encoder->content($id);
                                    self::$encoder->endTag();

                                    self::$encoder->startTag(SYNC_STATUS);
                                        self::$encoder->content($fetchstatus);
                                    self::$encoder->endTag();

                                    if($data !== false && $status == SYNC_STATUS_SUCCESS) {
                                        self::$encoder->startTag(SYNC_DATA);
                                            $data->Encode(self::$encoder);
                                        self::$encoder->endTag();
                                    }
                                    else
                                        ZLog::Write(LOGLEVEL_WARN, sprintf("Unable to Fetch '%s'", $id));
                                self::$encoder->endTag();

                            }
                            self::$encoder->endTag();
                        }

                        if($sc->GetParameter($spa, "getchanges") && $spa->HasFolderId() && $spa->HasContentClass() && $spa->HasSyncKey()) {
                            $windowSize = self::$deviceManager->GetWindowSize($spa->GetFolderId(), $spa->GetContentClass(), $spa->GetUuid(), $spa->GetUuidCounter(), $changecount);

                            if($changecount > $windowSize) {
                                self::$encoder->startTag(SYNC_MOREAVAILABLE, false, true);
                            }
                        }

                        // Stream outgoing changes
                        if($status == SYNC_STATUS_SUCCESS && $sc->GetParameter($spa, "getchanges") === true && $windowSize > 0) {
                            self::$topCollector->AnnounceInformation(sprintf("Streaming data of %d objects", (($changecount > $windowSize)?$windowSize:$changecount)));

                            // Output message changes per folder
                            self::$encoder->startTag(SYNC_PERFORM);

                            $n = 0;
                            while(1) {
                                try {
                                    $progress = $exporter->Synchronize();
                                    if(!is_array($progress))
                                        break;
                                    $n++;
                                }
                                catch (SyncObjectBrokenException $mbe) {
                                    $brokenSO = $mbe->GetSyncObject();
                                    if (!$brokenSO) {
                                        ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): Catched SyncObjectBrokenException but broken SyncObject not available. This should be fixed in the backend."));
                                    }
                                    else {
                                        if (!isset($brokenSO->id)) {
                                            $brokenSO->id = "Unknown ID";
                                            ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): Catched SyncObjectBrokenException but no ID of object set. This should be fixed in the backend."));
                                        }
                                        self::$deviceManager->AnnounceIgnoredMessage($spa->GetFolderId(), $brokenSO->id, $brokenSO);
                                    }
                                }

                                if($n >= $windowSize) {
                                    ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Exported maxItems of messages: %d / %d", $n, $changecount));
                                    break;
                                }

                            }

                            // $progress is not an array when exporting the last message
                            // so we get the number to display from the streamimporter
                            if (isset($streamimporter)) {
                                $n = $streamimporter->GetImportedMessages();
                            }

                            self::$encoder->endTag();
                            self::$topCollector->AnnounceInformation(sprintf("Outgoing %d objects%s", $n, ($n >= $windowSize)?" of ".$changecount:""), true);
                        }

                        self::$encoder->endTag();

                        // Save the sync state for the next time
                        if($spa->HasNewSyncKey()) {
                            self::$topCollector->AnnounceInformation("Saving state");

                            try {
                                if (isset($exporter) && $exporter)
                                    $state = $exporter->GetState();

                                // nothing exported, but possibly imported - get the importer state
                                else if ($sc->GetParameter($spa, "state") !== null)
                                    $state = $sc->GetParameter($spa, "state");

                                // if a new request without state information (hierarchy) save an empty state
                                else if (! $spa->HasSyncKey())
                                    $state = "";
                            }
                            catch (StatusException $stex) {
                               $status = $stex->getCode();
                            }


                            if (isset($state) && $status == SYNC_STATUS_SUCCESS)
                                self::$deviceManager->GetStateManager()->SetSyncState($spa->GetNewSyncKey(), $state, $spa->GetFolderId());
                            else
                                ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): error saving '%s' - no state information available", $spa->GetNewSyncKey()));
                        }

                        // save SyncParameters
                        if ($status == SYNC_STATUS_SUCCESS && empty($actiondata["fetchids"]))
                            $sc->SaveCollection($spa);

                    } // END foreach collection
                }
                self::$encoder->endTag(); //SYNC_FOLDERS
            }
        }
        self::$encoder->endTag(); //SYNC_SYNCHRONIZE

        return true;
    }

Here is the call graph for this function:

static RequestProcessor::HandleRequest ( ) [static, inherited]

Loads the command handler and processes a command sent from the mobile.

public

Returns:
boolean

Definition at line 122 of file requestprocessor.php.

                                           {
        $handler = ZPush::GetRequestHandlerForCommand(Request::GetCommandCode());

        // TODO handle WBXML exceptions here and print stack
        return $handler->Handle(Request::GetCommandCode());
    }

Here is the call graph for this function:

Sync::importMessage ( spa,
&$  actiondata,
todo,
message,
clientid,
serverid 
) [private]

Imports a message.

Parameters:
SyncParameters$spaSyncParameters object
array$actiondataActiondata array
integer$todoWBXML flag indicating how message should be imported. Valid values: SYNC_ADD, SYNC_MODIFY, SYNC_REMOVE
SyncObject$messageSyncObject message to be imported
string$clientidClient message identifier
string$serveridServer message identifier

private

Exceptions:
StatusExceptionin case the importer is not available
Returns:
- Message related status are returned in the actiondata.

Definition at line 992 of file sync.php.

                                                                                              {
        // the importer needs to be available!
        if ($this->importer == false)
            throw StatusException(sprintf("Sync->importMessage(): importer not available", SYNC_STATUS_SERVERERROR));

        // Detect incoming loop
        // messages which were created/removed before will not have the same action executed again
        // if a message is edited we perform this action "again", as the message could have been changed on the mobile in the meantime
        $ignoreMessage = false;
        if ($actiondata["failstate"]) {
            // message was ADDED before, do NOT add it again
            if ($todo == SYNC_ADD && $actiondata["failstate"]["clientids"][$clientid]) {
                $ignoreMessage = true;

                // make sure no messages are sent back
                self::$deviceManager->SetWindowSize($spa->GetFolderId(), 0);

                $actiondata["clientids"][$clientid] = $actiondata["failstate"]["clientids"][$clientid];
                $actiondata["statusids"][$clientid] = $actiondata["failstate"]["statusids"][$clientid];

                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]));
            }

            // message was REMOVED before, do NOT attemp to remove it again
            if ($todo == SYNC_REMOVE && $actiondata["failstate"]["removeids"][$serverid]) {
                $ignoreMessage = true;

                // make sure no messages are sent back
                self::$deviceManager->SetWindowSize($spa->GetFolderId(), 0);

                $actiondata["removeids"][$serverid] = $actiondata["failstate"]["removeids"][$serverid];
                $actiondata["statusids"][$serverid] = $actiondata["failstate"]["statusids"][$serverid];

                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]));
            }
        }

        if (!$ignoreMessage) {
            switch($todo) {
                case SYNC_MODIFY:
                    try {
                        $actiondata["modifyids"][] = $serverid;

                        // check incoming message without logging WARN messages about errors
                        if (!($message instanceof SyncObject) || !$message->Check(true)) {
                            $actiondata["statusids"][$serverid] = SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR;
                        }
                        else {
                            if(isset($message->read)) {
                                // Currently, 'read' is only sent by the PDA when it is ONLY setting the read flag.
                                $this->importer->ImportMessageReadFlag($serverid, $message->read);
                            }
                            elseif (!isset($message->flag)) {
                                $this->importer->ImportMessageChange($serverid, $message);
                            }

                            // email todoflags - some devices send todos flags together with read flags,
                            // so they have to be handled separately
                            if (isset($message->flag)){
                                $this->importer->ImportMessageChange($serverid, $message);
                            }

                            $actiondata["statusids"][$serverid] = SYNC_STATUS_SUCCESS;
                        }
                    }
                    catch (StatusException $stex) {
                        $actiondata["statusids"][$serverid] = $stex->getCode();
                    }

                    break;
                case SYNC_ADD:
                    try {
                        // check incoming message without logging WARN messages about errors
                        if (!($message instanceof SyncObject) || !$message->Check(true)) {
                            $actiondata["clientids"][$clientid] = false;
                            $actiondata["statusids"][$clientid] = SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR;
                        }
                        else {
                            $actiondata["clientids"][$clientid] = false;
                            $actiondata["clientids"][$clientid] = $this->importer->ImportMessageChange(false, $message);
                            $actiondata["statusids"][$clientid] = SYNC_STATUS_SUCCESS;
                        }
                    }
                    catch (StatusException $stex) {
                       $actiondata["statusids"][$clientid] = $stex->getCode();
                    }
                    break;
                case SYNC_REMOVE:
                    try {
                        $actiondata["removeids"][] = $serverid;
                        // if message deletions are to be moved, move them
                        if($spa->GetDeletesAsMoves()) {
                            $folderid = self::$backend->GetWasteBasket();

                            if($folderid) {
                                $this->importer->ImportMessageMove($serverid, $folderid);
                                $actiondata["statusids"][$serverid] = SYNC_STATUS_SUCCESS;
                                break;
                            }
                            else
                                ZLog::Write(LOGLEVEL_WARN, "Message should be moved to WasteBasket, but the Backend did not return a destination ID. Message is hard deleted now!");
                        }

                        $this->importer->ImportMessageDeletion($serverid);
                        $actiondata["statusids"][$serverid] = SYNC_STATUS_SUCCESS;
                    }
                    catch (StatusException $stex) {
                       $actiondata["statusids"][$serverid] = $stex->getCode();
                    }
                    break;
            }
            ZLog::Write(LOGLEVEL_DEBUG, "Sync->importMessage(): message imported");
        }
    }

Here is the call graph for this function:

Here is the caller graph for this function:

static RequestProcessor::Initialize ( ) [static, inherited]

Initialize the RequestProcessor.

public

Returns:

Definition at line 105 of file requestprocessor.php.

                                        {
        self::$backend = ZPush::GetBackend();
        self::$deviceManager = ZPush::GetDeviceManager();
        self::$topCollector = ZPush::GetTopCollector();

        if (!ZPush::CommandNeedsPlainInput(Request::GetCommandCode()))
            self::$decoder = new WBXMLDecoder(Request::GetInputStream());

        self::$encoder = new WBXMLEncoder(Request::GetOutputStream());
    }

Here is the call graph for this function:

static RequestProcessor::isUserAuthenticated ( ) [static, inherited]

Indicates if the user was "authenticated".

public

Returns:
boolean

Definition at line 93 of file requestprocessor.php.

                                                 {
        if (!isset(self::$userIsAuthenticated))
            return false;
        return self::$userIsAuthenticated;
    }

Here is the caller graph for this function:

Sync::loadStates ( sc,
spa,
&$  actiondata,
loadFailsave = false 
) [private]

Loads the states and writes them into the SyncCollection Object and the actiondata failstate.

Parameters:
SyncCollection$scSyncCollection object
SyncParameters$spaSyncParameters object
array$actiondataActiondata array
boolean$loadFailsave(opt) default false - indicates if the failsave states should be loaded

private

Returns:
status indicating if there were errors. If no errors, status is SYNC_STATUS_SUCCESS

Definition at line 904 of file sync.php.

                                                                                {
        $status = SYNC_STATUS_SUCCESS;

        if ($sc->GetParameter($spa, "state") == null) {
            ZLog::Write(LOGLEVEL_DEBUG, sprintf("Sync->loadStates(): loading states for folder '%s'",$spa->GetFolderId()));

            try {
                $sc->AddParameter($spa, "state", self::$deviceManager->GetStateManager()->GetSyncState($spa->GetSyncKey()));

                if ($loadFailsave) {
                    // if this request was made before, there will be a failstate available
                    $actiondata["failstate"] = self::$deviceManager->GetStateManager()->GetSyncFailState();
                }

                // if this is an additional folder the backend has to be setup correctly
                if (!self::$backend->Setup(ZPush::GetAdditionalSyncFolderStore($spa->GetFolderId())))
                    throw new StatusException(sprintf("HandleSync() could not Setup() the backend for folder id '%s'", $spa->GetFolderId()), SYNC_STATUS_FOLDERHIERARCHYCHANGED);
            }
            catch (StateNotFoundException $snfex) {
                $status = SYNC_STATUS_INVALIDSYNCKEY;
                self::$topCollector->AnnounceInformation("StateNotFoundException", true);
            }
            catch (StatusException $stex) {
               $status = $stex->getCode();
               self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), true);
            }
        }

        return $status;
    }

Here is the call graph for this function:

Here is the caller graph for this function:


Member Data Documentation

RequestProcessor::$backend [static, protected, inherited]

Definition at line 52 of file requestprocessor.php.

RequestProcessor::$decoder [static, protected, inherited]

Definition at line 55 of file requestprocessor.php.

RequestProcessor::$deviceManager [static, protected, inherited]

Definition at line 53 of file requestprocessor.php.

RequestProcessor::$encoder [static, protected, inherited]

Definition at line 56 of file requestprocessor.php.

Sync::$importer [private]

Definition at line 45 of file sync.php.

RequestProcessor::$topCollector [static, protected, inherited]

Definition at line 54 of file requestprocessor.php.

RequestProcessor::$userIsAuthenticated [static, protected, inherited]

Definition at line 57 of file requestprocessor.php.


The documentation for this class was generated from the following file: