[sync] E62 wbxml

Karsten Fourmont fourmont at gmx.de
Tue Oct 10 23:31:13 PDT 2006


Hi,

strange. Current syncml and wbxml packages should do the trick.

Here's a modified Backend.php. Put this into your pear directory  
reaplcing the existing one.

It basically has one additional logging line at the begin of the auth section.

s.th. like "checking auth for user=xxx with creddata=xxx and credtype=xxx".

And one more thing comes to mind: to be save, clean the session data
rm /var/lib/php/sess_sync*
(or wherever your horde session data resides)

Hopte this helps
  Karsten

Quoting Nathan Mills <nathan at nwacg.net>:

> Yes, I even checked the files in /usr/share/pear to make sure they  
> really did update.  I'll try a different installation, since it's  
> working for you.
>
> Do I need anything other than the updated syncml and wbxml packages  
> to make the new sync code work with the latest horde release? That's  
> not what I've been testing against, but I have an installation of  
> that already up and running.
>
> Thanks,
> -Nathan
>
> - original message -
> Subject:	Re: E62 wbxml
> From:	Karsten Fourmont <fourmont at gmx.de>
> Date:		10/11/2006 6:08 AM
>
> are you sure you did an install-packages.php after you did upgrade to cvs?
>
> Authorisation works fine here with the package you sent me.
>
> Cheers,
>   Karsten
>
> Quoting Nathan Mills <nathan at nwacg.net>:
>
>> Here's the wbxml you requested. I'd be happy to do anything else you
>>  need to help debug.
>>
>> Thanks,
>> -Nathan
>
>
>
>


-------------- next part --------------
<?php
/**
 * SyncML Backend for the Horde Application framework.
 *
 * The backend provides the following functionality:
 *
 * 1) handling of the actual data, i.e.
 *    a) add/replace/delete entries to and retrieve entries from the
 *       backend
 *    b) retrieve history to find out what entries have been changed
 * 2) managing of the map between cliend IDs and server IDs
 * 3) store information about sync anchors (timestamps) of previous
 *    successfuls sync sessions
 * 4) session handling (not yet, still to be done)
 * 5) authorisation (not yet, still to be done)
 * 6) logging
 *
 * Copyright 2005-2006 Karsten Fourmont <karsten at horde.org>
 *
 * See the enclosed file COPYING for license information (LGPL). If you did not
 * receive this file, see http://www.fsf.org/copyleft/lgpl.html.
 *
 * $Horde: framework/SyncML/SyncML/Backend.php,v 1.34 2006/10/08 19:41:11 karsten Exp $
 *
 * @author  Karsten Fourmont <karsten at horde.org>
 * @package SyncML
 */
class SyncML_Backend_Horde {

    var $_datatree;

    /**
     * Cache for Datatree values
     */

    var $_cache;

    var $_logtext;

    function SyncML_Backend_Horde()
    {
        $driver = $GLOBALS['conf']['datatree']['driver'];
        $params = Horde::getDriverConfig('datatree', $driver);
        $params = array_merge($params, array('group' => 'syncml'));

        $this->_datatree =& DataTree::singleton($driver, $params);
        $this->_cache = array();

        // Enable logging to SyncML_debugDir if set in RPC::syncml.php
        if (!empty($GLOBALS['SyncML_debugDir'])
            && is_dir($GLOBALS['SyncML_debugDir'])) {
            $this->_logtext = '';
        } else {
            $this->_logtext = null;
        }
    }

    /* Checks if $dbname is a valid server database for syncing.
     * If false, msg returns an error msg.
     */
    function isValidDB($dbname, &$msg)
    {
        $msg = '';
        switch(strtolower($dbname)) {
            case 'tasks';
            case 'calendar';
            case 'notes';
            case 'contacts';
            case 'events':
            case 'memo':
                return true;
            default:
                $msg = 'Invalid db name: ' . $dbname
                        . '. Try tasks, calendar, notes or contacts.';
                return false;
        }
    }

    /**
     * Retrieves an entry from the backend.
     *
     * @param string $database   Database to scan. i.e.
     *                           calendar/tasks/contacts/notes
     * @param string $suid       Server unique id of the entry: for horde
     *                           this is the guid.
     * @param string contentType Content-Type: the mime type in which the
     *                           function shall return the data
     *
     * @return mixed             A string with the data entry or
     *                           or a PEAR_Error object.
     */
    function retrieveEntry($database, $suid, $contentType)
    {
        return $GLOBALS['registry']->call(
                $this->_serviceForDB($database ) . '/export',
                array('guid' => $suid, 'contentType' => $contentType));
    }

    /**
     * Get entries that have been modified in the server database.
     *
     * @param string $syncIdentifier  Identifies the client device to allow the
     *                                user to sync with different devices.
     *                                Normally the SourceURI from the
     *                                SyncHeader
     * @param string $database        Database to scan. i.e.
     *                                calendar/tasks/contacts/notes
     * @param integer $from_ts        Start timestamp.
     * @param integer $to_ts          Exclusive end timestamp. Not yet
     *                                implemented.
     *
     * @return array  PEAR_Error or assoc array of changes with key=suid,
     *                value=cuid. If no cuid can be found for a suid, value is
     *                null. Then a Sync Add command has to be created to add
     *                this entry in the client database.
     */
    function getServerModifications($syncIdentifier, $database, $from_ts, $to_ts)
    {
        global $registry;

        $database = $this->_serviceForDB($database);
        if ($from_ts == 0) {
            // Complete sync: everything is sent during
            // getServerAdditions. No need to send modifications.
            return array();
        }

        // Get changes.
        $changes = $registry->call($database. '/listBy', array('action' => 'modify', 'timestamp' => $from_ts));
        if (is_a($changes, 'PEAR_Error')) {
            $this->logMessage("SyncML: $database/listBy failed for modify:"
                              . $changes->getMessage(),
                              __FILE__, __LINE__, PEAR_LOG_WARNING);
            return array();
        }

        $r = array();

        foreach ($changes as $suid) {
            $suid_ts = $registry->call($database . '/getActionTimestamp', array($suid, 'modify'));
            $sync_ts = $this->getChangeTS($syncIdentifier, $database, $suid);
            if ($sync_ts && $sync_ts >= $suid_ts) {
                // Change was done by us upon request of client.
                // Don't mirror that back to the client.
                $this->logMessage("change: $suid ignored, came from client", __FILE__, __LINE__, PEAR_LOG_DEBUG);
                continue;
            }
            $cuid = $this->getCuid($syncIdentifier, $database, $suid);
            if (!$cuid) {
                $this->logMessage("Unable to create change for $suid: client-id not found in map. Trying add instead.",
                                  __FILE__, __LINE__, PEAR_LOG_WARNING);
                $r[$suid] = null;
            } else {
                $r[$suid] = $cuid;
            }
        }

        return $r;
    }

    /**
     * Get entries that have been deleted from the server database.
     *
     * @param string $database  Database to scan. i.e.
     *                          calendar/tasks/contacts/notes
     * @param integer $from_ts
     * @param integer $to_ts    Exclusive
     *
     * @return array  PEAR_Error or assoc array of deletions with key=suid,
     *                value=cuid.
     */
    function getServerDeletions($syncIdentifier, $database, $from_ts, $to_ts)
    {
        global $registry;

        $database = $this->_serviceForDB($database);
        if ($from_ts == 0) {
            // Complete sync: everything is sent during
            // getServerAdditions.  No need to send deletions.
            return array();
        }

        // Get deletions.
        $deletes = $registry->call($database. '/listBy', array('action' => 'delete', 'timestamp' => $from_ts));

        if (is_a($deletes, 'PEAR_Error')) {
            $this->logMessage("SyncML: $database/listBy failed for delete:"
                              . $deletes->getMessage(),
                              __FILE__, __LINE__, PEAR_LOG_WARNING);
            return array();
        }

        $r = array();

        foreach ($deletes as $suid) {
            $suid_ts = $registry->call($database. '/getActionTimestamp', array($suid, 'delete'));
            $sync_ts = $this->getChangeTS($syncIdentifier, $database, $suid);
            if ($sync_ts && $sync_ts >= $suid_ts) {
                // Change was done by us upon request of client.
                // Don't mirror that back to the client.
                $this->logMessage("SyncML: delete $suid ignored, came from client", __FILE__, __LINE__, PEAR_LOG_DEBUG);
                continue;
            }
            $cuid = $this->getCuid($syncIdentifier, $database, $suid);
            if (!$cuid) {
                $this->logMessage("Unable to create delete for $suid: locid not found in map",
                                  __FILE__, __LINE__, PEAR_LOG_WARNING);
                continue;
            }

            $r[$suid] = $cuid;
        }

        return $r;
    }

    /**
     * Get entries that have been added to the server database.
     *
     * @param string $database  Database to scan. i.e.
     *                          calendar/tasks/contacts/notes
     * @param integer $from_ts
     * @param integer $to_ts    Exclusive
     *
     * @return array  PEAR_Error or assoc array of deletions with key=suid,
     *                value=0. (array style is chosen to match change & del)
     */
    function getServerAdditions($syncIdentifier, $database, $from_ts, $to_ts)
    {
        global $registry;

        $database = $this->_serviceForDB($database);
        if ($from_ts == 0) {
            // Return all db entries directly rather than bother
            // history:
            $adds = $registry->call($database. '/list');
        } else {
            $adds = $registry->call($database. '/listBy', array('action' => 'add', 'timestamp' => $from_ts));
        }

        if (is_a($adds, 'PEAR_Error')) {
            $this->logMessage("SyncML: $database/listBy failed for add:"
                              . $adds->getMessage(),
                              __FILE__, __LINE__, PEAR_LOG_WARNING);
            return array();
        }

        $r = array();
        foreach ($adds as $suid) {
            $suid_ts = $registry->call($database . '/getActionTimestamp', array($suid, 'add'));
            $sync_ts = $this->getChangeTS($syncIdentifier, $database, $suid);
            if ($sync_ts && $sync_ts >= $suid_ts) {
                // Change was done by us upon request of client.
                // Don't mirror that back to the client.
                $this->logMessage("add: $suid ignored, came from client", __FILE__, __LINE__, PEAR_LOG_DEBUG);
                continue;
            }

            $cuid = $this->getCuid($syncIdentifier, $database, $suid);

            $r[$suid] = 0;
        }

        return $r;
    }

    /**
     * Adds an entry into the server database.
     *
     * @param string $database     Database where to add.
     *                             calendar/tasks/contacts/notes
     * @param string $content      The actual data
     * @param string $contentType  Mimetype of $content
     * @param string $cuid         Client ID of this entry (for map)
     *
     * @return array  PEAR_Error or suid (Horde guid) of new entry
     */
    function importEntry($syncIdentifier, $database, $content, $contentType, $cuid)
    {
        global $registry;

        $database = $this->_serviceForDB($database);
        $tasksandcalendarcombined = false;

        // Checks if the client sends us a vtodo in a calendar sync:
        if ($database == 'calendar'
             && preg_match('/(\r\n|\r|\n)BEGIN[^:]*:VTODO/', "\n" . $content)) {
            $serverdatabase = 'tasks';
        } else {
            $serverdatabase = $database;
        }

        $suid = $registry->call($serverdatabase . '/import',
                                array($content, $contentType));

        $this->logMessage("add to server db $serverdatabase cuid $cuid -> suid $suid", __FILE__, __LINE__, PEAR_LOG_DEBUG);

        if (!is_a($suid, 'PEAR_Error')) {
            $ts = $registry->call($serverdatabase. '/getActionTimestamp', array($suid, 'add'));
            if (!$ts) {
                $this->logMessage('Unable to find add-ts for ' . $suid . ' at ' . $ts, __FILE__, __LINE__, PEAR_LOG_ERR);
            }
            $this->createUidMap($syncIdentifier, $database, $cuid, $suid, $ts);
        }

        return $suid;
    }

    /**
     * Deletes an entry from the server database.
     *
     * @param string $database  Database where to add.
     *                          calendar/tasks/contacts/notes
     * @param string $cuid      Client ID of the entry
     *
     * @return boolean          true on success or false on failed (item not found)
     */
    function deleteEntry($syncIdentifier, $database, $cuid)
    {
        global $registry;

        $database = $this->_serviceForDB($database);
        // Find server ID for this entry:
        $suid = $this->getSuid($syncIdentifier, $database, $cuid);
        if (!is_a($suid, 'PEAR_Error')) {
            $r = $registry->call($database. '/delete', array($suid));
            $ts = $registry->call($database . '/getActionTimestamp', array($suid, 'delete'));
            // We can't remove the mapping entry as we need to keep
            // the timestamp information.
            $this->createUidMap($syncIdentifier, $database, $cuid, $suid, $ts);
        } else {
            return false;
        }

        if (is_a($r, 'PEAR_Error')) {
            return false;
        }

        return true;
    }

    /**
     * Replaces an entry in the server database.
     *
     * @param string $database     Database where to replace.
     *                             calendar/tasks/contacts/notes
     * @param string $content      The actual data
     * @param string $contentType  Mimetype of $content
     * @param string $cuid         Client ID of this entry
     *
     * @return array  PEAR_Error or suid (Horde guid) of modified entry.
     */
    function replaceEntry($syncIdentifier, $database, $content, $contentType, $cuid)
    {
        global $registry;

        $database = $this->_serviceForDB($database);
        // Checks if the client sends us a vtodo in a calendar sync:
        if ($database == 'calendar'
             && preg_match('/(\r\n|\r|\n)BEGIN[^:]*:VTODO/', "\n" . $content)) {
            $serverdatabase = 'tasks';
        } else {
            $serverdatabase = $database;
        }

        $suid = $this->getSuid($syncIdentifier, $database, $cuid);

        $this->logMessage("replace in db $serverdatabase cuid $cuid suid $suid", __FILE__, __LINE__, PEAR_LOG_DEBUG);

        if ($suid) {
            // Entry exists: replace current one.
            $ok = $registry->call($serverdatabase . '/replace',
                                  array($suid, $content, $contentType));
            if (is_a($ok, 'PEAR_Error')) {
                return $ok;
            }
            $ts = $registry->call($serverdatabase . '/getActionTimestamp', array($suid, 'modify'));
            $this->createUidMap($syncIdentifier, $database, $cuid, $suid, $ts);
        } else {
            return PEAR::raiseError('No map entry found');
        }

        return $suid;
    }

    /** checks authorisation
     * returns true on auth accepted, false otherwise
     * For some types of authentications (notably auth:basic) the username
     * gets extracted from the auth data and is then stored in username.
     * For security reason the caller must ensure that this is the username
     * that is used for the session, overriding any username specified in
     * &lt;LocName&gt;.
     */
    function checkAuthorization(&$username,$credData, $credFormat, $credType)
    {
        $this->logMessage('checking auth for user=' . $username
                          . ' with creddata = ' . $credData
                          . ' and credType='  . $credType,
                              __FILE__, __LINE__, PEAR_LOG_DEBUG);

        if (empty($credData) || empty($credType)) {
            return false;
        }

        switch ($credType) {
        case 'syncml:auth-basic':
            list($username,$pwd) = explode(':', base64_decode($credData),2);
            $this->logMessage('checking auth for user=' . $username,
                              __FILE__, __LINE__, PEAR_LOG_DEBUG);
            // empty passwords result in errors for some auth backends:
            // so don't call the auth backend in this case
            if ($pwd === '') {
                return false;
            }
            $auth = &Auth::singleton($GLOBALS['conf']['auth']['driver']);
            return $auth->authenticate($username,
                                        array('password' => $pwd));
        case 'syncml:auth-md5':
            /* syncml:auth-md5 only transfers hash values of passwords.
             * Currently the syncml:auth-md5 hash scheme is not supported
             * by the Horde Auth backend. So we can't use horde to do
             * authentication. Instead here is a very crude direct manual hook:
             * To allow authentication for a user 'dummy' with password 'sync',
             * run
             * php -r 'print base64_encode(pack("H*",md5("dummy:sync")));'
             * from the command line. Then create an entry like
             *  'dummy' => 'ZD1ZeisPeQs0qipHc9tEsw==' in the users array below,
             * where the value is the command line output.
             * This user/password combination is then accepted for md5-auth.
             */
            $users = array(
                  // example for user dummy with pass pass:
                  // 'dummy' => 'ZD1ZeisPeQs0qipHc9tEsw=='
                          );
            if (empty($users[$username])) {
                return false;
            }

            //@TODO: nonce may be specified by client. Use it then.
            $nonce = '';
            if ($this->_md5_base64($users[$username] . ':' . $nonce)
                    === $credData) {
                $auth = &Auth::singleton($GLOBALS['conf']['auth']['driver']);
                $auth->setAuth($username, $credData);
                return true;
            }
            return false;
            break;
        default:
            $this->logMessage('unsupported auth type ' . $credType,
                              __FILE__, __LINE__, PEAR_LOG_ERR);
            return false;
        }
    }

    /**
     * Reads the previously written sync anchors from the database
     * $syncIde
     * @param string $syncIdentifier    ID of the device+user
     * @param string $database          uri of the database
     *
     * @return mixed                    array(clientanchor,serveranchor) or
     *                                  false if no data found
     */
    function readSyncAnchors($syncIdentifier, $database)
    {
        $ob = $this->_getOb($syncIdentifier . ':summary:' . $database);
        if (!is_a($ob, 'DataTreeObject')) {
            return false;
        }

        $clientlast = $ob->get('ClientAnchor');

        /* In older versions we stored clientlast in an array: */
        if (is_array($clientlast)) {
            $clientlast = $clientlast[$database];
        }

        $serverlast = $ob->get('ServerAnchor');

        /* In older versions we stored clientlast in an array: */
        if (is_array($serverlast)) {
            $serverlast = $serverlast[$database];
        }

        return array($clientlast, $serverlast);
    }

    /**
     * After a successful sync: store Sync anchors to allow two way sync
     * on next sync.
     */
    function writeSyncAnchors($syncIdentifier, $database,
                              $clientAnchorNext, $serverAnchorNext)
    {
        $s = $syncIdentifier . ':summary:' . $database;

        $info = &new DataTreeObject($s);
        $info->set('ClientAnchor', $clientAnchorNext);
        $info->set('ServerAnchor', $serverAnchorNext);
        $r = $this->_datatree->add($info);
        if (is_a($r, 'PEAR_Error')) {
            // Object already exists: update instead.
            $this->_datatree->updateData($info);
        }
        $this->_cache[$info->getName()] = $info;
    }

    /**
     * Create a map entries to map between server and client IDs.
     *
     * Puts a given client $cuid and Horde server $suid pair into the
     * map table to allow mapping between the client's and server's
     * IDs.  Actually there are two maps: from the suid to the cuid
     * and vice versa.
     * If an entry already exists, it is overwritten.
     */
    function createUidMap($syncIdentifier, $database, $cuid, $suid, $ts=0)
    {
        // Set $cuid.

        $database = $this->_serviceForDB($database);

        $gid = &new DataTreeObject($syncIdentifier . ':' . $database
                                   . ':suid2cuid:' . $suid);
        $gid->set('datastore', $database);
        $gid->set('cuid', $cuid);
        $gid->set('ts', $ts);

        $r = $this->_datatree->add($gid);
        if (is_a($r, 'PEAR_Error')) {
            // Object already exists: update instead.
            $r = $this->_datatree->updateData($gid);
        }
        $this->_cache[$gid->getName()] = $gid;
        $this->dieOnError($r, __FILE__, __LINE__);

        // Set $globaluid
        $lid = &new DataTreeObject($syncIdentifier . ':' . $database
                                   . ':cuid2suid:' . $cuid);
        $lid->set('suid', $suid);
        $r = $this->_datatree->add($lid);
        if (is_a($r, 'PEAR_Error')) {
            // Object already exists: update instead.
            $r = $this->_datatree->updateData($lid);
        }
        $this->_cache[$lid->getName()] = $lid;
        $this->dieOnError($r, __FILE__, __LINE__);

        // If tasks and events are handled at once, we need to store
        // the map entry in both databases:
        $session =& $_SESSION['SyncML.state'];
        $device =& $session->getDevice();

        if ($device->handleTasksInCalendar()
            && ($database == 'tasks' || $database == 'calendar') ) {
            $database = $database == 'tasks' ? 'calendar' : 'tasks' ; // the other one

            // Set $cuid.
            $gid = &new DataTreeObject($syncIdentifier . ':' . $database
                                       . ':suid2cuid:' . $suid);
            $gid->set('datastore', $database);
            $gid->set('cuid', $cuid);
            $gid->set('ts', $ts);

            $r = $this->_datatree->add($gid);
            if (is_a($r, 'PEAR_Error')) {
                // Object already exists: update instead.
                $r = $this->_datatree->updateData($gid);
            }
            $this->_cache[$gid->getName()] = $gid;
            $this->dieOnError($r, __FILE__, __LINE__);

            // Set $globaluid
            $lid = &new DataTreeObject($syncIdentifier . ':' . $database
                                       . ':cuid2suid:' . $cuid);
            $lid->set('suid', $suid);
            $r = $this->_datatree->add($lid);
            if (is_a($r, 'PEAR_Error')) {
                // Object already exists: update instead.
                $r = $this->_datatree->updateData($lid);
            }
            $this->_cache[$lid->getName()] = $lid;
            $this->dieOnError($r, __FILE__, __LINE__);
        }
    }

    /**
     * Returns the timestamp (if set) of the last change to the
     * obj:guid, that was caused by the client.
     *
     * @access private
     *
     * This is stored to
     * avoid mirroring these changes back to the client.
     */
    function getChangeTS($syncIdentifier, $database, $suid)
    {

        $database = $this->_serviceForDB($database);

        $gid =& $this->_getOb($syncIdentifier . ':' . $database . ':suid2cuid:' . $suid);
        if (is_a($gid, 'PEAR_Error')) {
            return false;
        }

        return $gid->get('ts');
    }

    /**
     * Retrieves the Horde server guid (like
     * kronolith:0d1b415fc124d3427722e95f0e926b75) for a given client
     * cuid. Returns false if no such id is stored yet.
     *
     * @access private
     *
     * Opposite of getLocId which returns the locid for a given guid.
     */
    function getSuid($syncIdentifier, $database, $cuid)
    {
        $database = $this->_serviceForDB($database);

        $lid =& $this->_getOb($syncIdentifier . ':' . $database . ':cuid2suid:' . $cuid);
        if (is_a($lid, 'PEAR_Error')) {
            return false;
        }

        return $lid->get('suid');
    }

    /**
     * Converts a suid server id (i.e. Horde GUID) to a cuid client ID
     * as used by the sync client (like 12) returns false if no such
     * id is stored yet.
     *
     * @access private
     */
    function getCuid($syncIdentifier, $database, $suid)
    {
        $database = $this->_serviceForDB($database);

        $gid =& $this->_getOb($syncIdentifier . ':' . $database  . ':suid2cuid:' . $suid);
        if (is_a($gid, 'PEAR_Error')) {
            return false;
        }

        return $gid->get('cuid');
    }

    /**
     * Erases all mapping entries for one syncIdentifier / database
     * so a SlowSync really syncs everything properly and no old
     * mapping entries remain.
     */
    function eraseMap($syncIdentifier, $database)
    {
        $database = $this->_serviceForDB($database);
        $this->_datatree->remove($syncIdentifier . ':' . $database, true);
    }

    /**
     * Logs a message in the backend.
     *
     * @param mixed $message     Either a string or a PEAR_Error object.
     * @param string $file       What file was the log function called from
     *                           (e.g. __FILE__)?
     * @param integer $line      What line was the log function called from
     *                           (e.g. __LINE__)?
     * @param integer $priority  The priority of the message. One of:
     * <pre>
     * PEAR_LOG_EMERG
     * PEAR_LOG_ALERT
     * PEAR_LOG_CRIT
     * PEAR_LOG_ERR
     * PEAR_LOG_WARNING
     * PEAR_LOG_NOTICE
     * PEAR_LOG_INFO
     * PEAR_LOG_DEBUG
     * </pre>
     */
    function logMessage($message, $file = __FILE__, $line = __LINE__,
                        $priority = PEAR_LOG_INFO)
    {
        // Internal logging to logtext
        if (is_string($this->_logtext)) {
            switch ($priority) {
            case PEAR_LOG_EMERG:
                $t = 'EMERG:  ';
                break;
            case PEAR_LOG_ALERT:
                $t = 'ALERT:  ';
                break;
            case PEAR_LOG_CRIT:
                $t = 'CIRT:   ';
                break;
            case PEAR_LOG_ERR:
                $t = 'ERR:    ';
                break;
            case PEAR_LOG_WARNING:
                $t = 'WARNING:';
                break;
            case PEAR_LOG_NOTICE:
                $t = 'NOTICE: ';
                break;
            case PEAR_LOG_INFO:
                $t = 'INFO:   ';
                break;
            case PEAR_LOG_DEBUG:
                $t = 'DEBUG:  ';
                break;
            default:
                $t = 'UNKNOWN:';
            }
            if (is_string($message)) {
                $t .= $message;
            } elseif(is_a($message, 'PEAR_Error')) {
                $t .= $message->message;
            }
            $this->_logtext .= $t . "\n";
        }

        // Logging to Horde log:
        if (is_string($message)) {
            $message = "SyncML: " . $message;
        }
        Horde::logMessage($message, $file, $line, $priority);
    }

    /**
     * Cleanup function called after all message processing is
     * finished. Allows for things like closing db or flushing logs.
     */
    function close()
    {
        // Write log if debugdir is set in RPC::syncml.php
        if (!empty($GLOBALS['SyncML_debugDir'])
            && is_dir($GLOBALS['SyncML_debugDir'])) {
            $packetNum = @intval(file_get_contents($GLOBALS['SyncML_debugDir']
                                 . '/syncml.packetnum'));
            if (empty($packetNum)) {
                $packetNum = 10;
            }

            $this->logMessage('Finished at ' . date("Y-m-d H:i:s")
                               . '. Packet logged in '
                               . $GLOBALS['SyncML_debugDir'] . '/syncml_server_'
                               . $packetNum . '.(wb)xml',
                                __FILE__, __LINE__, PEAR_LOG_DEBUG);

            $f = @fopen($GLOBALS['SyncML_debugDir'] . '/syncml_log.txt', 'a');
            if ($f) {
                fwrite($f, $this->_logtext . "\n");
                fclose($f);
            }
        }
    }

    /**
     * This is a small helper function that can be included to check
     * whether a given $obj is a PEAR_Error or not. If so, it logs
     * to debug, var_dumps the $obj and exits.
     */
    function dieOnError($obj, $file = __FILE__, $line = __LINE__)
    {
        if (!is_a($obj, 'PEAR_Error')) {
            return;
        }

        $this->logMessage('SyncML: PEAR Error: ' . $obj->getMessage(), $file, $line, PEAR_LOG_ERR);
        $this->close();
        print "PEAR ERROR\n\n";
        var_dump($obj);
    }

    function &_getOb($name)
    {
        if (!isset($this->_cache[$name])) {
            $this->_cache[$name] =& $this->_datatree->getObject($name);
        }
        return $this->_cache[$name];
    }

    function _md5_base64($data)
    {
       return base64_encode(pack('H*',md5($data)));
    }

    /**
     *  Converts a sync database to a valid horde service name.
     *  This allows for automatic conversion of typical default names ("Memo")
     *  to horde service names ("notes").
     */
    function _serviceForDB($serverSyncURI)
    {
        $serverSyncURI = strtolower($serverSyncURI);
        if (strpos($serverSyncURI, 'contact') !== false) {
            return 'contacts';
        } elseif (strpos($serverSyncURI, 'notes') !== false ||
                  strpos($serverSyncURI, 'memo') !== false) {
            return 'notes';
        } elseif (strpos($serverSyncURI, 'tasks') !== false) {
            return 'tasks';
        } elseif (strpos($serverSyncURI, 'calendar') !== false ||
                  strpos($serverSyncURI, 'events') !== false) {
            return 'calendar';
        } else {
            $this->logMessage('Unable to service for uri=' . $serverSyncURI
                              . '! Try (contacts|notes|tasks|calendar)',
                              __FILE__, __LINE__, PEAR_LOG_ERR);
        }

    }
}


More information about the sync mailing list