Overview

Packages

  • None
  • SyncMl

Classes

  • Horde_SyncMl
  • Horde_SyncMl_Backend
  • Horde_SyncMl_Backend_Horde
  • Horde_SyncMl_Command
  • Horde_SyncMl_Command_Alert
  • Horde_SyncMl_Command_Final
  • Horde_SyncMl_Command_Get
  • Horde_SyncMl_Command_Map
  • Horde_SyncMl_Command_Put
  • Horde_SyncMl_Command_Replace
  • Horde_SyncMl_Command_Results
  • Horde_SyncMl_Command_Status
  • Horde_SyncMl_Command_Sync
  • Horde_SyncMl_Command_SyncHdr
  • Horde_SyncMl_ContentHandler
  • Horde_SyncMl_DataStore
  • Horde_SyncMl_Device
  • Horde_SyncMl_Device_Nokia
  • Horde_SyncMl_Device_P800
  • Horde_SyncMl_Device_sync4j
  • Horde_SyncMl_Device_Sync4JMozilla
  • Horde_SyncMl_Device_Synthesis
  • Horde_SyncMl_DeviceInfo
  • Horde_SyncMl_Property
  • Horde_SyncMl_PropertyParameter
  • Horde_SyncMl_State
  • Horde_SyncMl_Sync
  • Horde_SyncMl_SyncElement
  • Horde_SyncMl_Translation
  • Horde_SyncMl_XmlOutput
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * SyncML Backend for the Horde Application framework.
  4:  *
  5:  * Copyright 2005-2012 Horde LLC (http://www.horde.org/)
  6:  *
  7:  * See the enclosed file COPYING for license information (LGPL). If you did not
  8:  * receive this file, see http://www.horde.org/licenses/lgpl21.
  9:  *
 10:  * @author  Karsten Fourmont <karsten@horde.org>
 11:  * @package SyncMl
 12:  */
 13: class Horde_SyncMl_Backend_Horde extends Horde_SyncMl_Backend
 14: {
 15:     /**
 16:      * A database instance.
 17:      *
 18:      * @var Horde_Db_Adapter_Base
 19:      */
 20:     protected $_db;
 21: 
 22:     /**
 23:      * The session ID used in the Horde session.
 24:      *
 25:      * @var string
 26:      */
 27:     protected $_sessionId;
 28: 
 29:     /**
 30:      * Constructor.
 31:      *
 32:      * Initializes the logger.
 33:      *
 34:      * @param array $params  Any parameters the backend might need.
 35:      */
 36:     public function __construct($params)
 37:     {
 38:         parent::__construct($params);
 39: 
 40:         $this->_db = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Db')->create();
 41:     }
 42: 
 43:     /**
 44:      * Sets the user used for this session.
 45:      *
 46:      * @param string $user  A user name.
 47:      */
 48:     public function setUser($user)
 49:     {
 50:         parent::setUser($user);
 51: 
 52:         if ($this->_backendMode == Horde_SyncMl_Backend::MODE_TEST) {
 53:             /* After a session the user gets automatically logged out, so we
 54:              * have to login again. */
 55:             $GLOBALS['registry']->setAuth($this->_user, array());
 56:         }
 57:     }
 58: 
 59:     /**
 60:      * Starts a PHP session.
 61:      *
 62:      * @param string $syncDeviceID  The device ID.
 63:      * @param string $session_id    The session ID to use.
 64:      * @param integer $backendMode  The backend mode, one of the
 65:      *                              Horde_SyncMl_Backend::MODE_* constants.
 66:      */
 67:     public function sessionStart($syncDeviceID, $sessionId,
 68:                                  $backendMode = Horde_SyncMl_Backend::MODE_SERVER)
 69:     {
 70:         $this->_backendMode = $backendMode;
 71:         $this->_syncDeviceID = $syncDeviceID;
 72:         $this->_sessionId = md5($syncDeviceID . $sessionId);
 73: 
 74:         /* Only the server needs to start a session. */
 75:         if ($this->_backendMode == Horde_SyncMl_Backend::MODE_SERVER) {
 76:             /* Reload the Horde SessionHandler if necessary. */
 77:             $GLOBALS['session']->setup(true, null, $this->_sessionId);
 78:             $this->state = $GLOBALS['session']->get('horde', 'syncml');
 79:         }
 80:     }
 81: 
 82:     public function close()
 83:     {
 84:         if ($this->state) {
 85:             $GLOBALS['session']->set('horde', 'syncml', $this->state);
 86:         }
 87:         parent::close();
 88:     }
 89: 
 90:     protected function _fastsync($databaseURI, $from_ts, $to_ts)
 91:     {
 92:         global $registry;
 93: 
 94:         $results = array(
 95:             'adds' => array(),
 96:             'mods' => array(),
 97:             'dels' => array());
 98: 
 99:         $map = array(
100:             'adds' => 'add',
101:             'dels' => 'delete',
102:             'mods' => 'modify');
103: 
104:         $database = $this->normalize($databaseURI);
105: 
106:         // Get ALL server changes from backend
107:         try {
108:             $changes = $registry->{$database}->getChanges($from_ts, $to_ts);
109:         } catch (Horde_Exception $e) {
110:             $this->logMessage(
111:                 sprintf(
112:                     ' %s getChanges() failed during _fastSync: %s', $database, $e->getMessage()),
113:                 'ERR');
114:         }
115: 
116:         $add_ts = array();
117:         foreach (array_keys($results) as $type) {
118:             foreach ($changes[$map[$type]] as $suid) {
119:                 // Only server needs to check for client sent entries:
120:                 if ($this->_backendMode != Horde_SyncMl_Backend::MODE_SERVER) {
121:                     switch ($type) {
122:                     case 'adds':
123:                         $id = 0;
124:                         break;
125:                     case 'mods':
126:                     case 'dels':
127:                         $id = $suid;
128:                     }
129:                     $results[$type][$suid] = $id;
130:                     continue;
131:                 }
132: 
133:                 try {
134:                     $change_ts = $registry->{$database}->getActionTimestamp(
135:                         $suid, $map[$type], Horde_SyncMl_Backend::getParameter($databaseURI, 'source'));
136:                 } catch (Horde_Exception $e) {
137:                     $this->logMessage($e->getMessage(), 'ERR');
138:                     return;
139:                 }
140:                 // If added, then deleted all since last sync, don't bother
141:                 // sending change
142:                 if ($type == 'adds') {
143:                     $add_ts[$suid] = $change_ts;
144:                 } elseif ($type == 'dels') {
145:                     if (isset($results['adds'][$suid]) && $add_ts[$suid] < $change_ts) {
146:                         unset($results['adds'][$suid]);
147:                         continue;
148:                     }
149:                     if (isset($results['mods'][$suid])) {
150:                         unset($results['mods'][$suid]);
151:                     }
152:                 }
153: 
154:                 $sync_ts = $this->_getChangeTS($database, $suid);
155:                 if ($sync_ts && $sync_ts >= $change_ts) {
156:                     // Change was done by us upon request of client.  Don't
157:                     // mirror that back to the client.
158:                     $this->logMessage(
159:                         "Added to server from client: $suid ignored", 'DEBUG');
160:                     continue;
161:                 }
162: 
163:                 // Sanity check and prepare list of changes
164:                 if ($type != 'adds') {
165:                     $cuid = $this->_getCuid($database, $suid);
166:                     if (empty($cuid) && $type == 'mods') {
167:                         $this->logMessage(
168:                             "Unable to create change for server id $suid: client id not found in map, adding instead.", 'WARN');
169:                         $results['adds'][$suid] = 0;
170:                         continue;
171:                     } elseif (empty($cuid) && $type == 'dels') {
172:                          $this->logMessage(
173:                             "Unable to create delete for server id $suid: client id not found in map", 'WARN');
174:                         continue;
175:                     } else {
176:                         $id = $cuid;
177:                     }
178:                 } else {
179:                     $id = 0;
180:                 }
181:                 $results[$type][$suid] = $id;
182:             }
183:         }
184: 
185:         return $results;
186:     }
187: 
188:     protected function _slowsync($databaseURI, $from_ts, $to_ts)
189:     {
190:         global $registry;
191: 
192:         $results = array(
193:             'adds' => array(),
194:             'dels' => array(),
195:             'mods' => array());
196: 
197:         $database = $this->normalize($databaseURI);
198: 
199:         // Return all db entries directly rather than bother history. But
200:         // first check if we only want to sync data from a given start
201:         // date:
202:         $start = trim(Horde_SyncMl_Backend::getParameter($databaseURI, 'start'));
203:         try {
204:             if (!empty($start)) {
205:                 if (strlen($start) == 4) {
206:                     $start .= '0101000000';
207:                 } elseif (strlen($start) == 6) {
208:                     $start .= '01000000';
209:                 } elseif (strlen($start) == 8) {
210:                     $start .= '000000';
211:                 }
212:                 $start = new Horde_Date($start);
213:                 $this->logMessage('Slow-syncing all events starting from ' . (string)$start, 'DEBUG');
214:                 $data = $registry->{$database}->listUids(
215:                             Horde_SyncMl_Backend::getParameter($databaseURI, 'source'), $start);
216:             } else {
217:                 $data = $registry->{$database}->listUids(
218:                             Horde_SyncMl_Backend::getParameter($databaseURI, 'source'));
219:             }
220:         } catch (Horde_Exception $e) {
221:             $this->logMessage(
222:                 "$database/list or $database/listBy failed while retrieving server additions:"
223:                     . $e->getMessage(), 'ERR');
224:             return;
225:         }
226: 
227:         foreach ($data as $suid) {
228:             // Only server needs to check for client sent entries:
229:             if ($this->_backendMode != Horde_SyncMl_Backend::MODE_SERVER) {
230:                 $results['adds'][$suid] = 0;
231:                 continue;
232:             }
233: 
234:             // Ignore if a map entry is present
235:             $cuid = $this->_getCuid($database, $suid);
236:             if ($cuid) {
237:                 $this->logMessage(
238:                     "Added to server from client during SlowSync: $suid ignored", 'DEBUG');
239:                 continue;
240:             }
241: 
242:             try {
243:                 $add_ts  = $registry->{$database}->getActionTimestamp(
244:                     $suid,
245:                     'add',
246:                     Horde_SyncMl_Backend::getParameter($databaseURI, 'source'));
247:             } catch (Horde_Exception $e) {
248:                 $this->logMessage($e->getMessage(), 'ERR');
249:                 return;
250:             }
251: 
252:             $sync_ts = $this->_getChangeTS($database, $suid);
253:             if ($sync_ts && $sync_ts >= $add_ts) {
254:                 // Change was done by us upon request of client.  Don't mirror
255:                 // that back to the client.
256:                 $this->logMessage("Added to server from client: $suid ignored", 'DEBUG');
257:                 continue;
258:             }
259:             $this->logMessage(
260:                 "Adding to client from db $database, server id $suid", 'DEBUG');
261: 
262:             $results['adds'][$suid] = 0;
263:         }
264: 
265:         return $results;
266:     }
267: 
268:     /**
269:      * Returns entries that have been modified in the server database.
270:      *
271:      * @param string $databaseURI  URI of Database to sync. Like calendar,
272:      *                             tasks, contacts or notes. May include
273:      *                             optional parameters:
274:      *                             tasks?options=ignorecompleted.
275:      * @param integer $from_ts     Start timestamp.
276:      * @param integer $to_ts       Exclusive end timestamp. Not yet
277:      *                             implemented.
278:      * @param array &$adds         Output array: hash of adds suid => 0
279:      * @param array &$mods         Output array: hash of modifications
280:      *                             suid => cuid
281:      * @param array &$dels         Output array: hash of deletions suid => cuid
282:      *
283:      * @return boolean true
284:      */
285:     public function getServerChanges($databaseURI, $from_ts, $to_ts, &$adds, &$mods,
286:                               &$dels)
287:     {
288:         global $registry;
289: 
290:         $slowsync = $from_ts == 0;
291: 
292:         if ($slowsync) {
293:             $results = $this->_slowsync($databaseURI, $from_ts, $to_ts);
294:         } else {
295:             $results = $this->_fastSync($databaseURI, $from_ts, $to_ts);
296:         }
297: 
298:         $adds = $results['adds'];
299:         $mods = $results['mods'];
300:         $dels = $results['dels'];
301: 
302:         // @TODO: No need to return true, since errors are now thrown. H5 should
303:         //        remove this.
304:         return true;
305:     }
306: 
307:     /**
308:      * Retrieves an entry from the backend.
309:      *
310:      * @param string $databaseURI  URI of Database to sync. Like calendar,
311:      *                             tasks, contacts or notes. May include
312:      *                             optional parameters:
313:      *                             tasks?options=ignorecompleted.
314:      * @param string $suid         Server unique id of the entry: for horde
315:      *                             this is the guid.
316:      * @param string $contentType  Content-Type: the MIME type in which the
317:      *                             public function should return the data.
318:      * @param array $fields        Hash of field names and Horde_SyncMl_Property
319:      *                             properties with the requested fields.
320:      *
321:      * @return mixed  A string with the data entry or a PEAR_Error object.
322:      */
323:     public function retrieveEntry($databaseURI, $suid, $contentType, $fields)
324:     {
325:         try {
326:             return $GLOBALS['registry']->call(
327:                 $this->normalize($databaseURI) . '/export',
328:                 array('guid' => $suid, 'contentType' => $contentType, 'dummy' => null, 'fields' => $fields));
329:         } catch (Horde_Exception $e) {
330:             return PEAR::raiseError($e->getMessage());
331:         }
332:     }
333: 
334:     /**
335:      * Adds an entry into the server database.
336:      *
337:      * @param string $databaseURI  URI of Database to sync. Like calendar,
338:      *                             tasks, contacts or notes. May include
339:      *                             optional parameters:
340:      *                             tasks?options=ignorecompleted.
341:      * @param string $content      The actual data.
342:      * @param string $contentType  MIME type of the content.
343:      * @param string $cuid         Client ID of this entry.
344:      *
345:      * @return array  PEAR_Error or suid (Horde guid) of new entry
346:      */
347:     public function addEntry($databaseURI, $content, $contentType, $cuid = null)
348:     {
349:         global $registry;
350: 
351:         $database = $this->normalize($databaseURI);
352: 
353:         try {
354:             $suid = $registry->call(
355:                 $database . '/import',
356:                 array($content,
357:                       $contentType,
358:                       Horde_SyncMl_Backend::getParameter($databaseURI, 'source')));
359: 
360:             $this->logMessage(
361:                 "Added to server db $database client id $cuid -> server id $suid", 'DEBUG');
362:             $ts = $registry->call(
363:                 $database . '/getActionTimestamp',
364:                 array($suid,
365:                       'add',
366:                       Horde_SyncMl_Backend::getParameter($databaseURI, 'source')));
367:             if (!$ts) {
368:                 $this->logMessage(
369:                     "Unable to find addition timestamp for server id $suid at $ts", 'ERR');
370:             }
371:             // Only server needs to do a cuid<->suid map
372:             if ($this->_backendMode == Horde_SyncMl_Backend::MODE_SERVER) {
373:                 $this->createUidMap($database, $cuid, $suid, $ts);
374:             }
375:         } catch (Horde_Exception $e) {
376:             // Failed import. Maybe the entry is already there. Check if a
377:             // guid is returned:
378:             /* Not working with exceptions
379:             if ($suid->getDebugInfo()) {
380:                 $suid = $suid->getDebugInfo();
381:                 $this->logMessage(
382:                     'Adding client entry to server: already exists with server id ' . $suid, 'NOTICE');
383:                 if ($this->_backendMode == Horde_SyncMl_Backend::MODE_SERVER) {
384:                     $this->createUidMap($database, $cuid, $suid, 0);
385:                 }
386:             }
387:             */
388: 
389:         }
390: 
391:         return $suid;
392:     }
393: 
394:     /**
395:      * Replaces an entry in the server database.
396:      *
397:      * @param string $databaseURI  URI of Database to sync. Like calendar,
398:      *                             tasks, contacts or notes. May include
399:      *                             optional parameters:
400:      *                             tasks?options=ignorecompleted.
401:      * @param string $content      The actual data.
402:      * @param string $contentType  MIME type of the content.
403:      * @param string $cuid         Client ID of this entry.
404:      *
405:      * @return string  PEAR_Error or server ID (Horde GUID) of modified entry.
406:      */
407:     public function replaceEntry($databaseURI, $content, $contentType, $cuid)
408:     {
409:         global $registry;
410: 
411:         $database = $this->normalize($databaseURI);
412: 
413:         // Only server needs to do a cuid<->suid map
414:         if ($this->_backendMode == Horde_SyncMl_Backend::MODE_SERVER) {
415:             $suid = $this->_getSuid($database, $cuid);
416:         } else {
417:             $suid = $cuid;
418:         }
419: 
420:         if (!$suid) {
421:             return PEAR::raiseError("No map entry found for client id $cuid replacing on server");
422:         }
423: 
424:         // Entry exists: replace current one.
425:         try {
426:             $ok = $registry->call($database . '/replace',
427:                                   array($suid, $content, $contentType));
428:         } catch (Horde_Exception $e) {
429:             return PEAR::raiseError($e->getMessage());
430:         }
431:         $this->logMessage(
432:             "Replaced in server db $database client id $cuid -> server id $suid", 'DEBUG');
433:         $ts = $registry->call(
434:             $database . '/getActionTimestamp',
435:             array($suid,
436:                   'modify',
437:                   Horde_SyncMl_Backend::getParameter($databaseURI,'source')));
438:         // Only server needs to do a cuid<->suid map
439:         if ($this->_backendMode == Horde_SyncMl_Backend::MODE_SERVER) {
440:             $this->createUidMap($database, $cuid, $suid, $ts);
441:         }
442: 
443:         return $suid;
444:     }
445: 
446:     /**
447:      * Deletes an entry from the server database.
448:      *
449:      * @param string $databaseURI  URI of Database to sync. Like calendar,
450:      *                             tasks, contacts or notes. May include
451:      *                             optional parameters:
452:      *                             tasks?options=ignorecompleted.
453:      * @param string $cuid         Client ID of the entry.
454:      *
455:      * @return boolean  True on success or false on failed (item not found).
456:      */
457:     public function deleteEntry($databaseURI, $cuid)
458:     {
459:         global $registry;
460: 
461:         $database = $this->normalize($databaseURI);
462:         // Find server ID for this entry:
463:         // Only server needs to do a cuid<->suid map
464:         if ($this->_backendMode == Horde_SyncMl_Backend::MODE_SERVER) {
465:             try {
466:                 $suid = $this->_getSuid($database, $cuid);
467:             } catch (Horde_Exception $e) {
468:                 return false;
469:             }
470:         } else {
471:             $suid = $cuid;
472:         }
473:         if (empty($suid) || is_a($suid, 'PEAR_Error')) {
474:             return false;
475:         }
476: 
477:         try {
478:             $registry->call($database. '/delete', array($suid));
479:         } catch (Horde_Exception $e) {
480:             return false;
481:         }
482: 
483:         $this->logMessage(
484:             "Deleted in server db $database client id $cuid -> server id $suid", 'DEBUG');
485:         $ts = $registry->call($database . '/getActionTimestamp',
486:                               array($suid, 'delete'));
487:         // We can't remove the mapping entry as we need to keep the timestamp
488:         // information.
489:         // Only server needs to do a cuid<->suid map
490:         if ($this->_backendMode == Horde_SyncMl_Backend::MODE_SERVER) {
491:             $this->createUidMap($database, $cuid, $suid, $ts);
492:         }
493: 
494:         return true;
495:     }
496: 
497:     /**
498:      * Authenticates the user at the backend.
499:      *
500:      * @param string $username    A user name.
501:      * @param string $password    A password.
502:      *
503:      * @return boolean|string  The user name if authentication succeeded, false
504:      *                         otherwise.
505:      */
506:     protected function _checkAuthentication($username, $password)
507:     {
508:         $auth = $GLOBALS['injector']
509:             ->getInstance('Horde_Core_Factory_Auth')
510:             ->create()
511:             ->authenticate($username, array('password' => $password))
512:             ? $GLOBALS['registry']->getAuth()
513:             : false;
514: 
515:         /* Horde is regenerating the session id at login, but we need to keep
516:          * our own, predictable session to not lose state. */
517:         session_id($this->_sessionId);
518: 
519:         return $auth;
520:     }
521: 
522:     /**
523:      * Sets a user as being authenticated at the backend.
524:      *
525:      * @abstract
526:      *
527:      * @param string $username    A user name.
528:      * @param string $credData    Authentication data provided by <Cred><Data>
529:      *                            in the <SyncHdr>.
530:      *
531:      * @return string  The user name.
532:      */
533:     protected function _setAuthenticated($username, $credData)
534:     {
535:         global $registry;
536: 
537:         $registry->setAuth($username, $credData);
538:         return $registry->getAuth();
539:     }
540: 
541:     /**
542:      * Stores Sync anchors after a successful synchronization to allow two-way
543:      * synchronization next time.
544:      *
545:      * The backend has to store the parameters in its persistence engine
546:      * where user, syncDeviceID and database are the keys while client and
547:      * server anchor ar the payload. See readSyncAnchors() for retrieval.
548:      *
549:      * @param string $databaseURI       URI of database to sync. Like calendar,
550:      *                                  tasks, contacts or notes. May include
551:      *                                  optional parameters:
552:      *                                  tasks?options=ignorecompleted.
553:      * @param string $clientAnchorNext  The client anchor as sent by the
554:      *                                  client.
555:      * @param string $serverAnchorNext  The anchor as used internally by the
556:      *                                  server.
557:      */
558:     public function writeSyncAnchors($databaseURI, $clientAnchorNext,
559:                               $serverAnchorNext)
560:     {
561:         $database = $this->normalize($databaseURI);
562: 
563:         $values = array($clientAnchorNext, $serverAnchorNext,
564:                         $this->_syncDeviceID, $database, $this->_user);
565:         if (!$this->readSyncAnchors($databaseURI)) {
566:             $query = 'INSERT INTO horde_syncml_anchors '
567:                 . '(syncml_clientanchor, syncml_serveranchor, '
568:                 . 'syncml_syncpartner, syncml_db, syncml_uid) '
569:                 . 'VALUES (?, ?, ?, ?, ?)';
570:             $this->_db->insert($query, $values);
571:         } else {
572:             $query = 'UPDATE horde_syncml_anchors '
573:                 . 'SET syncml_clientanchor = ?, syncml_serveranchor = ? '
574:                 . 'WHERE syncml_syncpartner = ? AND syncml_db = ? AND '
575:                 . 'syncml_uid = ?';
576:             $this->_db->update($query, $values);
577:         }
578:     }
579: 
580:     /**
581:      * Reads the previously written sync anchors from the database.
582:      *
583:      * @param string $databaseURI  URI of database to sync. Like calendar,
584:      *                             tasks, contacts or notes. May include
585:      *                             optional parameters:
586:      *                             tasks?options=ignorecompleted.
587:      *
588:      * @return mixed  Two-element array with client anchor and server anchor as
589:      *                stored in previous writeSyncAnchor() calls. False if no
590:      *                data found.
591:      */
592:     public function readSyncAnchors($databaseURI)
593:     {
594:         $database = $this->normalize($databaseURI);
595:         $query = 'SELECT syncml_clientanchor, syncml_serveranchor '
596:             . 'FROM horde_syncml_anchors '
597:             . 'WHERE syncml_syncpartner = ? AND syncml_db = ? AND '
598:             . 'syncml_uid = ?';
599:         $values = array($this->_syncDeviceID, $database, $this->_user);
600:         try {
601:             if ($res = $this->_db->selectOne($query, $values)) {
602:                 return array(
603:                     $res['syncml_clientanchor'],
604:                     $res['syncml_serveranchor']
605:                 );
606:             }
607:         } catch (Horde_Db_Exception $e) {}
608: 
609:         return false;
610:     }
611: 
612:     /**
613:      * Returns all previously written sync anchors for a user.
614:      *
615:      * @param string $user  A user name.
616:      *
617:      * @return array  A hash tree with all devices, databases and sync anchors
618:      *                from the specified user.
619:      */
620:     public function getUserAnchors($user)
621:     {
622:         $query = 'SELECT syncml_syncpartner, syncml_db, syncml_clientanchor, '
623:             . 'syncml_serveranchor FROM horde_syncml_anchors '
624:             . 'WHERE syncml_uid = ?';
625:         $values = array($user);
626:         return $this->_db->selectAll($query, $values);
627:     }
628: 
629:     /**
630:      * Deletes previously written sync anchors for a user.
631:      *
632:      * If no device or database are specified, anchors for all devices and/or
633:      * databases will be deleted.
634:      *
635:      * @param string $user      A user name.
636:      * @param string $device    The ID of the client device.
637:      * @param string $database  Normalized URI of database to delete. Like
638:      *                          calendar, tasks, contacts or notes.
639:      *
640:      * @return array
641:      */
642:     public function removeAnchor($user, $device = null, $database = null)
643:     {
644:         $query = 'DELETE FROM horde_syncml_anchors WHERE syncml_uid = ?';
645:         $values = array($user);
646:         if (strlen($device)) {
647:             $query .= ' AND syncml_syncpartner = ?';
648:             $values[] = $device;
649:         }
650:         if (strlen($database)) {
651:             $query .= ' AND syncml_db = ?';
652:             $values[] = $database;
653:         }
654: 
655:         $this->_db->delete($query, $values);
656:     }
657: 
658:     /**
659:      * Deletes previously written sync maps for a user.
660:      *
661:      * If no device or database are specified, maps for all devices and/or
662:      * databases will be deleted.
663:      *
664:      * @param string $user      A user name.
665:      * @param string $device    The ID of the client device.
666:      * @param string $database  Normalized URI of database to delete. Like
667:      *                          calendar, tasks, contacts or notes.
668:      *
669:      * @return array
670:      */
671:     public function removeMaps($user, $device = null, $database = null)
672:     {
673:         $query = 'DELETE FROM horde_syncml_map WHERE syncml_uid = ?';
674:         $values = array($user);
675:         if (strlen($device)) {
676:             $query .= ' AND syncml_syncpartner = ?';
677:             $values[] = $device;
678:         }
679:         if (strlen($database)) {
680:             $query .= ' AND syncml_db = ?';
681:             $values[] = $database;
682:         }
683: 
684:         $this->_db->delete($query, $values);
685:     }
686: 
687:     /**
688:      * Creates a map entry to map between server and client IDs.
689:      *
690:      * If an entry already exists, it is overwritten.
691:      *
692:      * @param string $databaseURI  URI of database to sync. Like calendar,
693:      *                             tasks, contacts or notes. May include
694:      *                             optional parameters:
695:      *                             tasks?options=ignorecompleted.
696:      * @param string $cuid         Client ID of the entry.
697:      * @param string $suid         Server ID of the entry.
698:      * @param integer $timestamp   Optional timestamp. This can be used to
699:      *                             'tag' changes made in the backend during the
700:      *                             sync process. This allows to identify these,
701:      *                             and ensure that these changes are not
702:      *                             replicated back to the client (and thus
703:      *                             duplicated). See key concept "Changes and
704:      *                             timestamps".
705:      */
706:     public function createUidMap($databaseURI, $cuid, $suid, $timestamp = 0)
707:     {
708:         $database = $this->normalize($databaseURI);
709: 
710:         $values = array($suid, (int)$timestamp, $this->_syncDeviceID,
711:                         $database, $this->_user, $cuid);
712:         // Check if entry exists. If not insert, otherwise update.
713:         if (!$this->_getSuid($databaseURI, $cuid)) {
714:             $query = 'INSERT INTO horde_syncml_map '
715:                 . '(syncml_suid, syncml_timestamp, syncml_syncpartner, '
716:                 . 'syncml_db, syncml_uid, syncml_cuid) '
717:                 . 'VALUES (?, ?, ?, ?, ?, ?)';
718:             $this->_db->insert($query, $values);
719:         } else {
720:             $query = 'UPDATE horde_syncml_map '
721:                 . 'SET syncml_suid = ?, syncml_timestamp = ? '
722:                 . 'WHERE syncml_syncpartner = ? AND syncml_db = ? AND '
723:                 . 'syncml_uid = ? AND syncml_cuid = ?';
724:             $this->_db->update($query, $values);
725:         }
726:     }
727: 
728:     /**
729:      * Retrieves the Server ID for a given Client ID from the map.
730:      *
731:      * @param string $databaseURI  URI of database to sync. Like calendar,
732:      *                             tasks, contacts or notes. May include
733:      *                             optional parameters:
734:      *                             tasks?options=ignorecompleted.
735:      * @param string $cuid         The client ID.
736:      *
737:      * @return mixed  The server ID string or false if no entry is found.
738:      */
739:     protected function _getSuid($databaseURI, $cuid)
740:     {
741:         $database = $this->normalize($databaseURI);
742:         $query = 'SELECT syncml_suid FROM horde_syncml_map '
743:             . 'WHERE syncml_syncpartner = ? AND syncml_db = ? AND '
744:             . 'syncml_uid = ? AND syncml_cuid = ?';
745:         $values = array($this->_syncDeviceID, $database, $this->_user, $cuid);
746:         return $this->_db->selectValue($query, $values);
747:     }
748: 
749:     /**
750:      * Retrieves the Client ID for a given Server ID from the map.
751:      *
752:      * @param string $databaseURI  URI of database to sync. Like calendar,
753:      *                             tasks, contacts or notes. May include
754:      *                             optional parameters:
755:      *                             tasks?options=ignorecompleted.
756:      * @param string $suid         The server ID.
757:      *
758:      * @return mixed  The client ID string or false if no entry is found.
759:      */
760:     protected function _getCuid($databaseURI, $suid)
761:     {
762:         $database = $this->normalize($databaseURI);
763: 
764:         $query = 'SELECT syncml_cuid FROM horde_syncml_map '
765:             . 'WHERE syncml_syncpartner = ? AND syncml_db = ? AND '
766:             . 'syncml_uid = ? AND syncml_suid = ?';
767:         $values = array($this->_syncDeviceID, $database, $this->_user, $suid);
768:         return $this->_db->selectValue($query, $values);
769:     }
770: 
771:     /**
772:      * Returns a timestamp stored in the map for a given Server ID.
773:      *
774:      * The timestamp is the timestamp of the last change to this server ID
775:      * that was done inside a sync session (as a result of a change received
776:      * by the server). It's important to distinguish changes in the backend a)
777:      * made by the user during normal operation and b) changes made by SyncML
778:      * to reflect client updates.  When the server is sending its changes it
779:      * is only allowed to send type a). However the history feature in the
780:      * backend my not know if a change is of type a) or type b). So the
781:      * timestamp is used to differentiate between the two.
782:      *
783:      * @param string $databaseURI  URI of database to sync. Like calendar,
784:      *                             tasks, contacts or notes. May include
785:      *                             optional parameters:
786:      *                             tasks?options=ignorecompleted.
787:      * @param string $suid         The server ID.
788:      *
789:      * @return mixed  The previously stored timestamp or false if no entry is
790:      *                found.
791:      */
792:     protected function _getChangeTS($databaseURI, $suid)
793:     {
794:         $database = $this->normalize($databaseURI);
795:         $query = 'SELECT syncml_timestamp FROM horde_syncml_map '
796:             . 'WHERE syncml_syncpartner = ? AND syncml_db = ? AND '
797:             . 'syncml_uid = ? AND syncml_suid = ?';
798:         $values = array($this->_syncDeviceID, $database, $this->_user, $suid);
799:         return $this->_db->selectValue($query, $values);
800:     }
801: 
802:     /**
803:      * Erases all mapping entries for one combination of user, device ID.
804:      *
805:      * This is used during SlowSync so that we really sync everything properly
806:      * and no old mapping entries remain.
807:      *
808:      * @param string $databaseURI  URI of database to sync. Like calendar,
809:      *                             tasks, contacts or notes. May include
810:      *                             optional parameters:
811:      *                             tasks?options=ignorecompleted.
812:      */
813:     public function eraseMap($databaseURI)
814:     {
815:         $database = $this->normalize($databaseURI);
816:         $query = 'DELETE FROM horde_syncml_map '
817:             . 'WHERE syncml_syncpartner = ? AND syncml_db = ? AND '
818:             . 'syncml_uid = ?';
819:         $values = array($this->_syncDeviceID, $database, $this->_user);
820:         $this->_db->delete($query, $values);
821:     }
822: 
823:     /**
824:      * Logs a message in the backend.
825:      *
826:      * @param mixed $message    Either a string or a PEAR_Error object.
827:      * @param string $priority  The priority of the message. One of:
828:      *                           - EMERG
829:      *                           - ALERT
830:      *                           - CRIT
831:      *                           - ERR
832:      *                           - WARN
833:      *                           - NOTICE
834:      *                           - INFO
835:      *                           - DEBUG
836:      */
837:     public function logMessage($message, $priority = 'INFO')
838:     {
839:         $trace = debug_backtrace();
840:         $trace = $trace[1];
841: 
842:         // Internal logging to $this->_logtext.
843:         parent::logMessage($message, $priority);
844: 
845:         // Logging to Horde log:
846:         Horde::logMessage($message, $priority, array('file' => $trace['file'], 'line' => $trace['line']));
847:     }
848: 
849:     /**
850:      * Creates a clean test environment in the backend.
851:      *
852:      * Ensures there's a user with the given credentials and an empty data
853:      * store.
854:      *
855:      * @param string $user This user accout has to be created in the backend.
856:      * @param string $pwd  The password for user $user.
857:      *
858:      * @throws Horde_Exception
859:      */
860:     public function testSetup($user, $pwd)
861:     {
862:         $this->_user = $user;
863:         if (empty($pwd)) {
864:             $pwd = rand() . rand();
865:         }
866: 
867:         /* Get an Auth object. */
868:         $auth = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Auth')->create();
869: 
870:         /* Make this user an admin for the time beeing to allow deletion of
871:          * user data. */
872:         $GLOBALS['conf']['auth']['admins'][] = $user;
873: 
874:         /* Always remove test user first. */
875:         if ($auth->exists($user)) {
876:             $GLOBALS['registry']->removeUser($user);
877:         }
878: 
879:         $auth->addUser($user, array('password' => $pwd));
880:     }
881: 
882:     /**
883:      * Prepares the test start.
884:      *
885:      * @param string $user This user accout has to be created in the backend.
886:      */
887:     public function testStart($user)
888:     {
889:         $this->_user = $user;
890: 
891:         /* Make this user an admin for the time beeing to allow deletion of
892:          * user data. */
893:         $GLOBALS['conf']['auth']['admins'][] = $user;
894: 
895:         $GLOBALS['registry']->setAuth($user, array());
896:     }
897: 
898:     /**
899:      * Tears down the test environment after the test is run.
900:      *
901:      * Should remove the testuser created during testSetup and all its data.
902:      */
903:     public function testTearDown()
904:     {
905:         /* Get an Auth object. */
906:         try {
907:             $auth = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Auth')->create();
908:         } catch (Horde_Exception $e) {
909:             // TODO
910:         }
911: 
912:         /* We need to be logged in to call removeUserData, otherwise we run
913:          * into permission issues. */
914:         $GLOBALS['registry']->setAuth($this->_user, array());
915: 
916:         print "\nCleaning up: removing test user data and test user...";
917:         $registry->removeUser($this->_user);
918: 
919:         print "OK\n";
920:     }
921: }
922: 
API documentation generated by ApiGen