lib/ext lib/kolab_sync_data.php

Aleksander Machniak machniak at kolabsys.com
Tue Jan 15 09:32:10 CET 2013


 lib/ext/Syncroton/Command/Ping.php              |   25 --
 lib/ext/Syncroton/Command/Sync.php              |   17 -
 lib/ext/Syncroton/Data/AData.php                |    9 
 lib/ext/Syncroton/Data/IData.php                |   10 
 lib/ext/Syncroton/Model/AEntry.php              |  245 +-------------------
 lib/ext/Syncroton/Model/AXMLEntry.php           |  286 ++++++++++++++++++++++++
 lib/ext/Syncroton/Model/Contact.php             |    2 
 lib/ext/Syncroton/Model/Device.php              |   14 -
 lib/ext/Syncroton/Model/DeviceInformation.php   |    2 
 lib/ext/Syncroton/Model/Email.php               |    2 
 lib/ext/Syncroton/Model/EmailAttachment.php     |    2 
 lib/ext/Syncroton/Model/EmailBody.php           |    2 
 lib/ext/Syncroton/Model/EmailFlag.php           |    2 
 lib/ext/Syncroton/Model/Event.php               |    2 
 lib/ext/Syncroton/Model/EventAttendee.php       |    2 
 lib/ext/Syncroton/Model/EventException.php      |    2 
 lib/ext/Syncroton/Model/EventRecurrence.php     |    2 
 lib/ext/Syncroton/Model/FileReference.php       |    2 
 lib/ext/Syncroton/Model/Folder.php              |    2 
 lib/ext/Syncroton/Model/GAL.php                 |    2 
 lib/ext/Syncroton/Model/GALPicture.php          |    2 
 lib/ext/Syncroton/Model/IDevice.php             |    2 
 lib/ext/Syncroton/Model/IEntry.php              |   26 --
 lib/ext/Syncroton/Model/IXMLEntry.php           |   39 +++
 lib/ext/Syncroton/Model/MeetingResponse.php     |    2 
 lib/ext/Syncroton/Model/Policy.php              |    2 
 lib/ext/Syncroton/Model/SendMail.php            |    2 
 lib/ext/Syncroton/Model/SmartForward.php        |    2 
 lib/ext/Syncroton/Model/SmartReply.php          |    2 
 lib/ext/Syncroton/Model/StoreResponse.php       |    4 
 lib/ext/Syncroton/Model/StoreResponseResult.php |    2 
 lib/ext/Syncroton/Model/SyncCollection.php      |    2 
 lib/ext/Syncroton/Model/Task.php                |    2 
 lib/ext/Syncroton/Model/TaskRecurrence.php      |    2 
 lib/ext/Syncroton/Server.php                    |   17 -
 lib/kolab_sync_data.php                         |    6 
 36 files changed, 414 insertions(+), 332 deletions(-)

New commits:
commit 09e7b779972c5640192a50ed163c7de815ed889e
Author: Aleksander Machniak <alec at alec.pl>
Date:   Tue Jan 15 09:30:54 2013 +0100

    Update Syncroton (with some performance improvements)

diff --git a/lib/ext/Syncroton/Command/Ping.php b/lib/ext/Syncroton/Command/Ping.php
index 9a056e1..9ac1b35 100644
--- a/lib/ext/Syncroton/Command/Ping.php
+++ b/lib/ext/Syncroton/Command/Ping.php
@@ -51,27 +51,23 @@ class Syncroton_Command_Ping extends Syncroton_Command_Wbxml
     {
         $intervalStart = time();
         $status = self::STATUS_NO_CHANGES_FOUND;
-
+        
         // the client does not send a wbxml document, if the Ping parameters did not change compared with the last request
         if ($this->_requestBody instanceof DOMDocument) {
             $xml = simplexml_import_dom($this->_requestBody);
             $xml->registerXPathNamespace('Ping', 'Ping');
 
-            if (isset($xml->HeartBeatInterval)) {
-                $lifeTime = (int)$xml->HeartBeatInterval;
-                if ($this->_device->pinglifetime != $lifeTime) {
-                    $this->_device->pinglifetime = $lifeTime;
-                    $need_update = true;
-                }
+            if(isset($xml->HeartBeatInterval)) {
+                $this->_device->pinglifetime = (int)$xml->HeartBeatInterval;
             }
-
+            
             if (isset($xml->Folders->Folder)) {
                 $folders = array();
                 foreach ($xml->Folders->Folder as $folderXml) {
                     try {
                         // does the folder exist?
                         $folder = $this->_folderBackend->getFolder($this->_device, (string)$folderXml->Id);
-
+                        
                         $folders[$folder->id] = $folder;
                     } catch (Syncroton_Exception_NotFound $senf) {
                         if ($this->_logger instanceof Zend_Log) 
@@ -80,15 +76,10 @@ class Syncroton_Command_Ping extends Syncroton_Command_Wbxml
                         break;
                     }
                 }
-
-                $pingFolders = serialize(array_keys($folders));
-                if ($this->_device->pingfolder != $pingFolders) {
-                    $this->_device->pingfolder = $pingFolders;
-                    $need_update = true;
-                }
+                $this->_device->pingfolder = serialize(array_keys($folders));
             }
 
-            if ($need_update && $status == self::STATUS_NO_CHANGES_FOUND) {
+            if ($this->_device->isDirty() && $status == self::STATUS_NO_CHANGES_FOUND) {
                 $this->_device = $this->_deviceBackend->update($this->_device);
             }
         }
@@ -142,7 +133,7 @@ class Syncroton_Command_Ping extends Syncroton_Command_Wbxml
                             continue;
                         }
                         
-                        $foundChanges = !!$dataController->getCountOfChanges($this->_contentStateBackend, $folder, $syncState);
+                        $foundChanges = $dataController->hasChanges($this->_contentStateBackend, $folder, $syncState);
                         
                     } catch (Syncroton_Exception_NotFound $e) {
                         // folder got never synchronized to client
diff --git a/lib/ext/Syncroton/Command/Sync.php b/lib/ext/Syncroton/Command/Sync.php
index ba28d37..b59bae6 100644
--- a/lib/ext/Syncroton/Command/Sync.php
+++ b/lib/ext/Syncroton/Command/Sync.php
@@ -161,11 +161,10 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
             foreach ($collections as $collection) {
                 $collectionsToSave[$collection->collectionId] = $collection->toArray();
             }
-
-            $lastSyncCollection = Zend_Json::encode($collectionsToSave);
-            if ($this->_device->lastsynccollection != $lastSyncCollection) {
-                $this->_device->lastsynccollection = $lastSyncCollection;
-
+        
+            $this->_device->lastsynccollection = Zend_Json::encode($collectionsToSave);
+        
+            if ($this->_device->isDirty()) {
                 Syncroton_Registry::getDeviceBackend()->update($this->_device);
             }
         }
@@ -492,13 +491,13 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
                         if (($now->getTimestamp() - $collectionData->syncState->lastsync->getTimestamp()) < Syncroton_Registry::getQuietTime()) {
                             continue;
                         }
-                                                
+
                         $dataController = Syncroton_Data_Factory::factory($collectionData->folder->class , $this->_device, $this->_syncTimeStamp);
                         
-                        $estimate = $dataController->getCountOfChanges($this->_contentStateBackend, $collectionData->folder, $collectionData->syncState);
+                        $hasChanges = $dataController->hasChanges($this->_contentStateBackend, $collectionData->folder, $collectionData->syncState);
                         
-                        // countinue immediately if there are changes available
-                        if ($estimate > 0) {
+                        // countinue immediately if there are any changes available
+                        if ($hasChanges) {
                             break 2;
                         }
                     }
diff --git a/lib/ext/Syncroton/Data/AData.php b/lib/ext/Syncroton/Data/AData.php
index 1382f68..97a821d 100644
--- a/lib/ext/Syncroton/Data/AData.php
+++ b/lib/ext/Syncroton/Data/AData.php
@@ -200,6 +200,15 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
         
         return unserialize($entry);
     }
+
+    /**
+     * (non-PHPdoc)
+     * @see Syncroton_Data_IData::hasChanges()
+     */
+    public function hasChanges(Syncroton_Backend_IContent $contentBackend, Syncroton_Model_IFolder $folder, Syncroton_Model_ISyncState $syncState)
+    {
+        return !!$this->getCountOfChanges($contentBackend, $folder, $syncState);
+    }
     
     public function moveItem($_srcFolderId, $_serverId, $_dstFolderId)
     {
diff --git a/lib/ext/Syncroton/Data/IData.php b/lib/ext/Syncroton/Data/IData.php
index 41aba4b..db286d3 100644
--- a/lib/ext/Syncroton/Data/IData.php
+++ b/lib/ext/Syncroton/Data/IData.php
@@ -78,6 +78,16 @@ interface Syncroton_Data_IData
      * @return array
      */
     public function getServerEntries($folderId, $filter);
+
+    /**
+     * return true if any data got modified in the backend
+     * 
+     * @param  Syncroton_Backend_IContent  $contentBackend
+     * @param  Syncroton_Model_IFolder     $folder
+     * @param  Syncroton_Model_ISyncState  $syncState
+     * @return bool
+     */
+    public function hasChanges(Syncroton_Backend_IContent $contentBackend, Syncroton_Model_IFolder $folder, Syncroton_Model_ISyncState $syncState);
     
     public function moveItem($srcFolderId, $serverId, $dstFolderId);
     
diff --git a/lib/ext/Syncroton/Model/AEntry.php b/lib/ext/Syncroton/Model/AEntry.php
index 1a58827..d2ad8be 100644
--- a/lib/ext/Syncroton/Model/AEntry.php
+++ b/lib/ext/Syncroton/Model/AEntry.php
@@ -18,13 +18,9 @@
 
 abstract class Syncroton_Model_AEntry implements Syncroton_Model_IEntry, IteratorAggregate, Countable
 {
-    protected $_xmlBaseElement;
-    
     protected $_elements = array();
     
-    protected $_properties = array();
-    
-    protected $_dateTimeFormat = "Y-m-d\TH:i:s.000\Z";
+    protected $_isDirty;
     
     /**
      * (non-PHPdoc)
@@ -32,63 +28,14 @@ abstract class Syncroton_Model_AEntry implements Syncroton_Model_IEntry, Iterato
      */
     public function __construct($properties = null)
     {
-        if ($properties instanceof SimpleXMLElement) {
-            $this->setFromSimpleXMLElement($properties);
-        } elseif (is_array($properties)) {
+        if (is_array($properties)) {
             $this->setFromArray($properties);
         }
-    }
-    
-    /**
-     * (non-PHPdoc)
-     * @see Syncroton_Model_IEntry::appendXML()
-     */
-    public function appendXML(DOMElement $domParrent, Syncroton_Model_IDevice $device)
-    {
-        $this->_addXMLNamespaces($domParrent);
         
-        foreach($this->_elements as $elementName => $value) {
-            // skip empty values
-            if($value === null || $value === '' || (is_array($value) && empty($value))) {
-                continue;
-            }
-            
-            list ($nameSpace, $elementProperties) = $this->_getElementProperties($elementName);
-            
-            if ($nameSpace == 'Internal') {
-                continue;
-            }
-            
-            $elementVersion = isset($elementProperties['supportedSince']) ? $elementProperties['supportedSince'] : '12.0';
-            
-            if (version_compare($device->acsversion, $elementVersion, '<')) {
-                continue;
-            }
-            
-            $nameSpace = 'uri:' . $nameSpace;
-            
-            if (isset($elementProperties['childElement'])) {
-                $element = $domParrent->ownerDocument->createElementNS($nameSpace, ucfirst($elementName));
-                foreach($value as $subValue) {
-                    $subElement = $domParrent->ownerDocument->createElementNS($nameSpace, ucfirst($elementProperties['childElement']));
-                    
-                    $this->_appendXMLElement($device, $subElement, $elementProperties, $subValue);
-                    
-                    $element->appendChild($subElement);
-                    
-                }
-                $domParrent->appendChild($element);
-            } else {
-                $element = $domParrent->ownerDocument->createElementNS($nameSpace, ucfirst($elementName));
-                
-                $this->_appendXMLElement($device, $element, $elementProperties, $value);
-                
-                $domParrent->appendChild($element);
-            }
-            
-        }
+        $this->_isDirty = false;
     }
     
+    
     /**
      * (non-PHPdoc)
      * @see Countable::count()
@@ -109,18 +56,11 @@ abstract class Syncroton_Model_AEntry implements Syncroton_Model_IEntry, Iterato
     
     /**
      * (non-PHPdoc)
-     * @see Syncroton_Model_IEntry::getProperties()
+     * @see Syncroton_Model_IEntry::isDirty()
      */
-    public function getProperties()
+    public function isDirty()
     {
-        $properties = array();
-        
-        foreach($this->_properties as $namespace => $namespaceProperties) {
-            $properties = array_merge($properties, array_keys($namespaceProperties));
-        }
-        
-        return $properties;
-        
+        return $this->_isDirty;
     }
     
     /**
@@ -138,180 +78,19 @@ abstract class Syncroton_Model_AEntry implements Syncroton_Model_IEntry, Iterato
             }
         }
     }
-    
-    /**
-     * set properties from SimpleXMLElement object
-     *
-     * @param SimpleXMLElement $xmlCollection
-     * @throws InvalidArgumentException
-     */
-    public function setFromSimpleXMLElement(SimpleXMLElement $properties)
-    {
-        if (!in_array($properties->getName(), (array) $this->_xmlBaseElement)) {
-            throw new InvalidArgumentException('Unexpected element name: ' . $properties->getName());
-        }
-    
-        foreach (array_keys($this->_properties) as $namespace) {
-            if ($namespace == 'Internal') {
-                continue;
-            }
-            
-            $this->_parseNamespace($namespace, $properties);
-        }
-    
-        return;
-    }
-    
-    /**
-     * add needed xml namespaces to DomDocument
-     * 
-     * @param unknown_type $domParrent
-     */
-    protected function _addXMLNamespaces(DOMElement $domParrent)
-    {
-        foreach($this->_properties as $namespace => $namespaceProperties) {
-            // don't add default namespace again
-            if($domParrent->ownerDocument->documentElement->namespaceURI != 'uri:'.$namespace) {
-                $domParrent->ownerDocument->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:'.$namespace, 'uri:'.$namespace);
-            }
-        }
-    }
-    
-    protected function _appendXMLElement(Syncroton_Model_IDevice $device, DOMElement $element, $elementProperties, $value)
-    {
-        if ($value instanceof Syncroton_Model_IEntry) {
-            $value->appendXML($element, $device);
-        } else {
-            if ($value instanceof DateTime) {
-                $value = $value->format($this->_dateTimeFormat);
-                
-            } elseif (isset($elementProperties['encoding']) && $elementProperties['encoding'] == 'base64') {
-                if (is_resource($value)) {
-                    rewind($value);
-                    $value = stream_get_contents($value);
-                }
-                $value = base64_encode($value);
-            }
-            
-            if ($elementProperties['type'] == 'byteArray') {
-                $element->setAttributeNS('uri:Syncroton', 'Syncroton:encoding', 'opaque');
-                // encode to base64; the wbxml encoder will base64_decode it again
-                // this way we can also transport data, which would break the xmlparser otherwise
-                $element->appendChild($element->ownerDocument->createCDATASection(base64_encode($value)));
-            } else {
-                // strip off any non printable control characters
-                if (!ctype_print($value)) {
-                    $value = $this->_removeControlChars($value);
-                }
-                $element->appendChild($element->ownerDocument->createTextNode($value));
-            }
-        }
-    }
-    
-    /**
-     * removed control chars from string which are not allowd in XML values
-     *
-     * @param  string|array $_dirty
-     * @return string
-     */
-    protected function _removeControlChars($dirty)
-    {
-        return preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F]/', null, $dirty);
-    }
-    
-    /**
-     * 
-     * @param unknown_type $element
-     * @throws InvalidArgumentException
-     * @return multitype:unknown
-     */
-    protected function _getElementProperties($element)
-    {
-        foreach($this->_properties as $namespace => $namespaceProperties) {
-            if (array_key_exists($element, $namespaceProperties)) {
-                return array($namespace, $namespaceProperties[$element]);
-            }
-        }
-        
-        throw new InvalidArgumentException("$element is no valid property of " . get_class($this));
-    }
-    
-    protected function _parseNamespace($nameSpace, SimpleXMLElement $properties)
-    {
-        // fetch data from Contacts namespace
-        $children = $properties->children("uri:$nameSpace");
         
-        foreach ($children as $elementName => $xmlElement) {
-            $elementName = lcfirst($elementName);
-            
-            if (!isset($this->_properties[$nameSpace][$elementName])) {
-                continue;
-            }
-            
-            list (, $elementProperties) = $this->_getElementProperties($elementName);
-            
-            switch ($elementProperties['type']) {
-                case 'container':
-                    if (isset($elementProperties['childElement'])) {
-                        $property = array();
-                        
-                        $childElement = ucfirst($elementProperties['childElement']);
-                        
-                        foreach ($xmlElement->$childElement as $subXmlElement) {
-                            if (isset($elementProperties['class'])) {
-                                $property[] = new $elementProperties['class']($subXmlElement);
-                            } else {
-                                $property[] = (string) $subXmlElement;
-                            }
-                        }
-                    } else {
-                        $subClassName = isset($elementProperties['class']) ? $elementProperties['class'] : get_class($this) . ucfirst($elementName);
-                        
-                        $property = new $subClassName($xmlElement);
-                    }
-                    
-                    break;
-                    
-                case 'datetime':
-                    $property = new DateTime((string) $xmlElement, new DateTimeZone('UTC'));
-    
-                    break;
-    
-                case 'number':
-                    $property = (int) $xmlElement;
-    
-                    break;
-                    
-                default:
-                    $property = (string) $xmlElement;
-    
-                    break;
-            }
-            
-            if (isset($elementProperties['encoding']) && $elementProperties['encoding'] == 'base64') {
-                $property = base64_decode($property);
-            }
-            
-            $this->$elementName = $property;
-        }
-    }
-    
     public function &__get($name)
     {
-        $this->_getElementProperties($name);
-    
         return $this->_elements[$name];
     }
     
     public function __set($name, $value)
-    {
-        list ($nameSpace, $properties) = $this->_getElementProperties($name);
-        
-        if ($properties['type'] == 'datetime' && !$value instanceof DateTime) {
-            throw new InvalidArgumentException("value for $name must be an instance of DateTime");
+    {
+        if (!array_key_exists($name, $this->_elements) || $this->_elements[$name] != $value) {
+            $this->_elements[$name] = $value;
+            
+            $this->_isDirty = true;
         }
-        
-        $this->_elements[$name] = $value;
     }
     
     public function __isset($name)
diff --git a/lib/ext/Syncroton/Model/AXMLEntry.php b/lib/ext/Syncroton/Model/AXMLEntry.php
new file mode 100644
index 0000000..6dc1e9f
--- /dev/null
+++ b/lib/ext/Syncroton/Model/AXMLEntry.php
@@ -0,0 +1,286 @@
+<?php
+/**
+ * Syncroton
+ *
+ * @package     Syncroton
+ * @subpackage  Model
+ * @license     http://www.tine20.org/licenses/lgpl.html LGPL Version 3
+ * @copyright   Copyright (c) 2012-2012 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Lars Kneschke <l.kneschke at metaways.de>
+ */
+
+/**
+ * abstract class to handle ActiveSync entry
+ *
+ * @package     Syncroton
+ * @subpackage  Model
+ */
+
+abstract class Syncroton_Model_AXMLEntry extends Syncroton_Model_AEntry implements Syncroton_Model_IXMLEntry
+{
+    protected $_xmlBaseElement;
+    
+    protected $_properties = array();
+    
+    protected $_dateTimeFormat = "Y-m-d\TH:i:s.000\Z";
+    
+    /**
+     * (non-PHPdoc)
+     * @see Syncroton_Model_IEntry::__construct()
+     */
+    public function __construct($properties = null)
+    {
+        if ($properties instanceof SimpleXMLElement) {
+            $this->setFromSimpleXMLElement($properties);
+        } elseif (is_array($properties)) {
+            $this->setFromArray($properties);
+        }
+        
+        $this->_isDirty = false;
+    }
+    
+    /**
+     * (non-PHPdoc)
+     * @see Syncroton_Model_IEntry::appendXML()
+     */
+    public function appendXML(DOMElement $domParrent, Syncroton_Model_IDevice $device)
+    {
+        $this->_addXMLNamespaces($domParrent);
+        
+        foreach($this->_elements as $elementName => $value) {
+            // skip empty values
+            if($value === null || $value === '' || (is_array($value) && empty($value))) {
+                continue;
+            }
+            
+            list ($nameSpace, $elementProperties) = $this->_getElementProperties($elementName);
+            
+            if ($nameSpace == 'Internal') {
+                continue;
+            }
+            
+            $elementVersion = isset($elementProperties['supportedSince']) ? $elementProperties['supportedSince'] : '12.0';
+            
+            if (version_compare($device->acsversion, $elementVersion, '<')) {
+                continue;
+            }
+            
+            $nameSpace = 'uri:' . $nameSpace;
+            
+            if (isset($elementProperties['childElement'])) {
+                $element = $domParrent->ownerDocument->createElementNS($nameSpace, ucfirst($elementName));
+                foreach($value as $subValue) {
+                    $subElement = $domParrent->ownerDocument->createElementNS($nameSpace, ucfirst($elementProperties['childElement']));
+                    
+                    $this->_appendXMLElement($device, $subElement, $elementProperties, $subValue);
+                    
+                    $element->appendChild($subElement);
+                    
+                }
+                $domParrent->appendChild($element);
+            } else {
+                $element = $domParrent->ownerDocument->createElementNS($nameSpace, ucfirst($elementName));
+                
+                $this->_appendXMLElement($device, $element, $elementProperties, $value);
+                
+                $domParrent->appendChild($element);
+            }
+            
+        }
+    }
+    
+    /**
+     * (non-PHPdoc)
+     * @see Syncroton_Model_IEntry::getProperties()
+     */
+    public function getProperties()
+    {
+        $properties = array();
+        
+        foreach($this->_properties as $namespace => $namespaceProperties) {
+            $properties = array_merge($properties, array_keys($namespaceProperties));
+        }
+        
+        return $properties;
+        
+    }
+    
+    /**
+     * set properties from SimpleXMLElement object
+     *
+     * @param SimpleXMLElement $xmlCollection
+     * @throws InvalidArgumentException
+     */
+    public function setFromSimpleXMLElement(SimpleXMLElement $properties)
+    {
+        if (!in_array($properties->getName(), (array) $this->_xmlBaseElement)) {
+            throw new InvalidArgumentException('Unexpected element name: ' . $properties->getName());
+        }
+    
+        foreach (array_keys($this->_properties) as $namespace) {
+            if ($namespace == 'Internal') {
+                continue;
+            }
+            
+            $this->_parseNamespace($namespace, $properties);
+        }
+    
+        return;
+    }
+    
+    /**
+     * add needed xml namespaces to DomDocument
+     * 
+     * @param unknown_type $domParrent
+     */
+    protected function _addXMLNamespaces(DOMElement $domParrent)
+    {
+        foreach($this->_properties as $namespace => $namespaceProperties) {
+            // don't add default namespace again
+            if($domParrent->ownerDocument->documentElement->namespaceURI != 'uri:'.$namespace) {
+                $domParrent->ownerDocument->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:'.$namespace, 'uri:'.$namespace);
+            }
+        }
+    }
+    
+    protected function _appendXMLElement(Syncroton_Model_IDevice $device, DOMElement $element, $elementProperties, $value)
+    {
+        if ($value instanceof Syncroton_Model_IEntry) {
+            $value->appendXML($element, $device);
+        } else {
+            if ($value instanceof DateTime) {
+                $value = $value->format($this->_dateTimeFormat);
+                
+            } elseif (isset($elementProperties['encoding']) && $elementProperties['encoding'] == 'base64') {
+                if (is_resource($value)) {
+                    rewind($value);
+                    $value = stream_get_contents($value);
+                }
+                $value = base64_encode($value);
+            }
+            
+            if ($elementProperties['type'] == 'byteArray') {
+                $element->setAttributeNS('uri:Syncroton', 'Syncroton:encoding', 'opaque');
+                // encode to base64; the wbxml encoder will base64_decode it again
+                // this way we can also transport data, which would break the xmlparser otherwise
+                $element->appendChild($element->ownerDocument->createCDATASection(base64_encode($value)));
+            } else {
+                // strip off any non printable control characters
+                if (!ctype_print($value)) {
+                    $value = $this->_removeControlChars($value);
+                }
+                $element->appendChild($element->ownerDocument->createTextNode($value));
+            }
+        }
+    }
+    
+    /**
+     * removed control chars from string which are not allowd in XML values
+     *
+     * @param  string|array $_dirty
+     * @return string
+     */
+    protected function _removeControlChars($dirty)
+    {
+        return preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F]/', null, $dirty);
+    }
+    
+    /**
+     * 
+     * @param unknown_type $element
+     * @throws InvalidArgumentException
+     * @return multitype:unknown
+     */
+    protected function _getElementProperties($element)
+    {
+        foreach($this->_properties as $namespace => $namespaceProperties) {
+            if (array_key_exists($element, $namespaceProperties)) {
+                return array($namespace, $namespaceProperties[$element]);
+            }
+        }
+        
+        throw new InvalidArgumentException("$element is no valid property of " . get_class($this));
+    }
+    
+    protected function _parseNamespace($nameSpace, SimpleXMLElement $properties)
+    {
+        // fetch data from Contacts namespace
+        $children = $properties->children("uri:$nameSpace");
+        
+        foreach ($children as $elementName => $xmlElement) {
+            $elementName = lcfirst($elementName);
+            
+            if (!isset($this->_properties[$nameSpace][$elementName])) {
+                continue;
+            }
+            
+            list (, $elementProperties) = $this->_getElementProperties($elementName);
+            
+            switch ($elementProperties['type']) {
+                case 'container':
+                    if (isset($elementProperties['childElement'])) {
+                        $property = array();
+                        
+                        $childElement = ucfirst($elementProperties['childElement']);
+                        
+                        foreach ($xmlElement->$childElement as $subXmlElement) {
+                            if (isset($elementProperties['class'])) {
+                                $property[] = new $elementProperties['class']($subXmlElement);
+                            } else {
+                                $property[] = (string) $subXmlElement;
+                            }
+                        }
+                    } else {
+                        $subClassName = isset($elementProperties['class']) ? $elementProperties['class'] : get_class($this) . ucfirst($elementName);
+                        
+                        $property = new $subClassName($xmlElement);
+                    }
+                    
+                    break;
+                    
+                case 'datetime':
+                    $property = new DateTime((string) $xmlElement, new DateTimeZone('UTC'));
+    
+                    break;
+    
+                case 'number':
+                    $property = (int) $xmlElement;
+    
+                    break;
+                    
+                default:
+                    $property = (string) $xmlElement;
+    
+                    break;
+            }
+            
+            if (isset($elementProperties['encoding']) && $elementProperties['encoding'] == 'base64') {
+                $property = base64_decode($property);
+            }
+            
+            $this->$elementName = $property;
+        }
+    }
+    
+    public function &__get($name)
+    {
+        $this->_getElementProperties($name);
+    
+        return $this->_elements[$name];
+    }
+    
+    public function __set($name, $value)
+    {
+        list ($nameSpace, $properties) = $this->_getElementProperties($name);
+        
+        if ($properties['type'] == 'datetime' && !$value instanceof DateTime) {
+            throw new InvalidArgumentException("value for $name must be an instance of DateTime");
+        }
+        
+        if (!array_key_exists($name, $this->_elements) || $this->_elements[$name] != $value) {
+            $this->_elements[$name] = $value;
+            
+            $this->_isDirty = true;
+        }
+    }
+}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/Contact.php b/lib/ext/Syncroton/Model/Contact.php
index 16485c2..6b90a07 100644
--- a/lib/ext/Syncroton/Model/Contact.php
+++ b/lib/ext/Syncroton/Model/Contact.php
@@ -24,7 +24,7 @@
  * @property    Syncroton_Model_EmailBody  Body
  */
 
-class Syncroton_Model_Contact extends Syncroton_Model_AEntry
+class Syncroton_Model_Contact extends Syncroton_Model_AXMLEntry
 {
     protected $_xmlBaseElement = 'ApplicationData';
     
diff --git a/lib/ext/Syncroton/Model/Device.php b/lib/ext/Syncroton/Model/Device.php
index 3a0fdd6..a46ef2d 100644
--- a/lib/ext/Syncroton/Model/Device.php
+++ b/lib/ext/Syncroton/Model/Device.php
@@ -14,7 +14,7 @@
  *
  * @package     Model
  */
-class Syncroton_Model_Device implements Syncroton_Model_IDevice
+class Syncroton_Model_Device extends Syncroton_Model_AEntry implements Syncroton_Model_IDevice
 {
     const TYPE_IPHONE          = 'iphone';
     const TYPE_WEBOS           = 'webos';
@@ -22,18 +22,6 @@ class Syncroton_Model_Device implements Syncroton_Model_IDevice
     const TYPE_ANDROID_40      = 'android40';
     const TYPE_SMASUNGGALAXYS2 = 'samsunggti9100'; // Samsung Galaxy S-3
     
-    public function __construct(array $_data = array())
-    {
-        $this->setFromArray($_data);
-    }
-    
-    public function setFromArray(array $_data)
-    {
-        foreach($_data as $key => $value) {
-            $this->$key = $value;
-        }
-    }
-    
     /**
      * Returns major firmware version of this device
      * 
diff --git a/lib/ext/Syncroton/Model/DeviceInformation.php b/lib/ext/Syncroton/Model/DeviceInformation.php
index adab7b0..0a5c516 100644
--- a/lib/ext/Syncroton/Model/DeviceInformation.php
+++ b/lib/ext/Syncroton/Model/DeviceInformation.php
@@ -21,7 +21,7 @@
  * @property    string  phoneNumber
  */
 
-class Syncroton_Model_DeviceInformation extends Syncroton_Model_AEntry
+class Syncroton_Model_DeviceInformation extends Syncroton_Model_AXMLEntry
 {
     protected $_xmlBaseElement = 'Set';
     
diff --git a/lib/ext/Syncroton/Model/Email.php b/lib/ext/Syncroton/Model/Email.php
index 35f2661..29fd495 100644
--- a/lib/ext/Syncroton/Model/Email.php
+++ b/lib/ext/Syncroton/Model/Email.php
@@ -22,7 +22,7 @@
  * @property    DateTime  lastVerbExecutionTime
  * @property    int       read
  */
-class Syncroton_Model_Email extends Syncroton_Model_AEntry
+class Syncroton_Model_Email extends Syncroton_Model_AXMLEntry
 {
     const LASTVERB_UNKNOWN       = 0;
     const LASTVERB_REPLYTOSENDER = 1;
diff --git a/lib/ext/Syncroton/Model/EmailAttachment.php b/lib/ext/Syncroton/Model/EmailAttachment.php
index e415ad4..48d9866 100644
--- a/lib/ext/Syncroton/Model/EmailAttachment.php
+++ b/lib/ext/Syncroton/Model/EmailAttachment.php
@@ -19,7 +19,7 @@
  * @property    string  syncKey
  * @property    int     windowSize
  */
-class Syncroton_Model_EmailAttachment extends Syncroton_Model_AEntry
+class Syncroton_Model_EmailAttachment extends Syncroton_Model_AXMLEntry
 {
     protected $_xmlBaseElement = 'Attachment';
     
diff --git a/lib/ext/Syncroton/Model/EmailBody.php b/lib/ext/Syncroton/Model/EmailBody.php
index 3b0f16b..3d465b8 100644
--- a/lib/ext/Syncroton/Model/EmailBody.php
+++ b/lib/ext/Syncroton/Model/EmailBody.php
@@ -20,7 +20,7 @@
  * @property   string  Type
  */
 
-class Syncroton_Model_EmailBody extends Syncroton_Model_AEntry
+class Syncroton_Model_EmailBody extends Syncroton_Model_AXMLEntry
 {
     const TYPE_PLAINTEXT = 1;
     const TYPE_HTML      = 2;
diff --git a/lib/ext/Syncroton/Model/EmailFlag.php b/lib/ext/Syncroton/Model/EmailFlag.php
index c284987..258fd46 100644
--- a/lib/ext/Syncroton/Model/EmailFlag.php
+++ b/lib/ext/Syncroton/Model/EmailFlag.php
@@ -28,7 +28,7 @@
  * @property    DateTime  UtcDueDate
  * @property    DateTime  UtcStartDate
  */
-class Syncroton_Model_EmailFlag extends Syncroton_Model_AEntry
+class Syncroton_Model_EmailFlag extends Syncroton_Model_AXMLEntry
 {
     const STATUS_CLEARED  = 0;
     const STATUS_COMPLETE = 1;
diff --git a/lib/ext/Syncroton/Model/Event.php b/lib/ext/Syncroton/Model/Event.php
index cf0de60..84fec18 100644
--- a/lib/ext/Syncroton/Model/Event.php
+++ b/lib/ext/Syncroton/Model/Event.php
@@ -20,7 +20,7 @@
  * @property    int     windowSize
  */
 
-class Syncroton_Model_Event extends Syncroton_Model_AEntry
+class Syncroton_Model_Event extends Syncroton_Model_AXMLEntry
 {
     /**
      * busy status constants
diff --git a/lib/ext/Syncroton/Model/EventAttendee.php b/lib/ext/Syncroton/Model/EventAttendee.php
index a439650..82257db 100644
--- a/lib/ext/Syncroton/Model/EventAttendee.php
+++ b/lib/ext/Syncroton/Model/EventAttendee.php
@@ -20,7 +20,7 @@
  * @property    int     windowSize
  */
 
-class Syncroton_Model_EventAttendee extends Syncroton_Model_AEntry
+class Syncroton_Model_EventAttendee extends Syncroton_Model_AXMLEntry
 {
     protected $_xmlBaseElement = 'Attendee';
     
diff --git a/lib/ext/Syncroton/Model/EventException.php b/lib/ext/Syncroton/Model/EventException.php
index 7d7224b..0dab808 100644
--- a/lib/ext/Syncroton/Model/EventException.php
+++ b/lib/ext/Syncroton/Model/EventException.php
@@ -20,7 +20,7 @@
  * @property    int     windowSize
  */
 
-class Syncroton_Model_EventException extends Syncroton_Model_AEntry
+class Syncroton_Model_EventException extends Syncroton_Model_AXMLEntry
 {    
     protected $_xmlBaseElement = 'Exception';
     
diff --git a/lib/ext/Syncroton/Model/EventRecurrence.php b/lib/ext/Syncroton/Model/EventRecurrence.php
index 367a2bc..3fe0c94 100644
--- a/lib/ext/Syncroton/Model/EventRecurrence.php
+++ b/lib/ext/Syncroton/Model/EventRecurrence.php
@@ -25,7 +25,7 @@
  * @property    int       WeekOfMonth
  */
 
-class Syncroton_Model_EventRecurrence extends Syncroton_Model_AEntry
+class Syncroton_Model_EventRecurrence extends Syncroton_Model_AXMLEntry
 {
     protected $_xmlBaseElement = 'Recurrence';
     
diff --git a/lib/ext/Syncroton/Model/FileReference.php b/lib/ext/Syncroton/Model/FileReference.php
index 9f0bbad..b54bb7d 100644
--- a/lib/ext/Syncroton/Model/FileReference.php
+++ b/lib/ext/Syncroton/Model/FileReference.php
@@ -16,7 +16,7 @@
  * @property    string  Data
  */
 
-class Syncroton_Model_FileReference extends Syncroton_Model_AEntry
+class Syncroton_Model_FileReference extends Syncroton_Model_AXMLEntry
 {
     protected $_xmlBaseElement = 'ApplicationData';
     
diff --git a/lib/ext/Syncroton/Model/Folder.php b/lib/ext/Syncroton/Model/Folder.php
index dfabc34..9655f9a 100644
--- a/lib/ext/Syncroton/Model/Folder.php
+++ b/lib/ext/Syncroton/Model/Folder.php
@@ -15,7 +15,7 @@
  * @package     Model
  */
 
-class Syncroton_Model_Folder extends Syncroton_Model_AEntry implements Syncroton_Model_IFolder
+class Syncroton_Model_Folder extends Syncroton_Model_AXMLEntry implements Syncroton_Model_IFolder
 {
     protected $_xmlBaseElement = array('FolderUpdate', 'FolderCreate');
     
diff --git a/lib/ext/Syncroton/Model/GAL.php b/lib/ext/Syncroton/Model/GAL.php
index 917c5e5..64765ce 100644
--- a/lib/ext/Syncroton/Model/GAL.php
+++ b/lib/ext/Syncroton/Model/GAL.php
@@ -29,7 +29,7 @@
  * @property    string    Picture
  * @property    string    Title
  */
-class Syncroton_Model_GAL extends Syncroton_Model_AEntry
+class Syncroton_Model_GAL extends Syncroton_Model_AXMLEntry
 {
     protected $_xmlBaseElement = 'ApplicationData';
 
diff --git a/lib/ext/Syncroton/Model/GALPicture.php b/lib/ext/Syncroton/Model/GALPicture.php
index d7a208a..8122490 100644
--- a/lib/ext/Syncroton/Model/GALPicture.php
+++ b/lib/ext/Syncroton/Model/GALPicture.php
@@ -20,7 +20,7 @@
  * @property    string    Status
  * @property    string    Data
  */
-class Syncroton_Model_GALPicture extends Syncroton_Model_AEntry
+class Syncroton_Model_GALPicture extends Syncroton_Model_AXMLEntry
 {
     const STATUS_SUCCESS   = 1;
     const STATUS_NOPHOTO   = 173;
diff --git a/lib/ext/Syncroton/Model/IDevice.php b/lib/ext/Syncroton/Model/IDevice.php
index e0c59ab..944a1f7 100644
--- a/lib/ext/Syncroton/Model/IDevice.php
+++ b/lib/ext/Syncroton/Model/IDevice.php
@@ -38,7 +38,7 @@
  * @property    string   emailfilter_id
  * @property    string   lastsynccollection
  */
-interface Syncroton_Model_IDevice
+interface Syncroton_Model_IDevice extends Syncroton_Model_IEntry
 {
     /**
      * Returns major firmware version of this device
diff --git a/lib/ext/Syncroton/Model/IEntry.php b/lib/ext/Syncroton/Model/IEntry.php
index 34f6d53..c2c9ce7 100644
--- a/lib/ext/Syncroton/Model/IEntry.php
+++ b/lib/ext/Syncroton/Model/IEntry.php
@@ -29,33 +29,13 @@ interface Syncroton_Model_IEntry
     public function __construct($properties = null);
     
     /**
-     * 
-     * @param DOMElement $_domParrent
-     */
-    /**
-     * 
-     * @param DOMElement $_domParrent
-     * @param Syncroton_Model_IDevice $device
+     * return true if data have got changed after initial data got loaded via constructor
      */
-    public function appendXML(DOMElement $_domParrent, Syncroton_Model_IDevice $device);
-    
-    /**
-     * return array of valid properties
-     *  
-     * @return array
-     */
-    public function getProperties();
+    public function isDirty();
     
     /**
      * 
      * @param array $properties
      */
-    public function setFromArray(array $properties);
-    
-    /**
-     * 
-     * @param SimpleXMLElement $xmlCollection
-     * @throws InvalidArgumentException
-     */
-    public function setFromSimpleXMLElement(SimpleXMLElement $properties);
+    public function setFromArray(array $properties);    
 }
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/IXMLEntry.php b/lib/ext/Syncroton/Model/IXMLEntry.php
new file mode 100644
index 0000000..7b3ad0f
--- /dev/null
+++ b/lib/ext/Syncroton/Model/IXMLEntry.php
@@ -0,0 +1,39 @@
+<?php
+/**
+ * Syncroton
+ *
+ * @package     Model
+ * @license     http://www.tine20.org/licenses/lgpl.html LGPL Version 3
+ * @copyright   Copyright (c) 2012-2013 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Lars Kneschke <l.kneschke at metaways.de>
+ */
+
+/**
+ * class to handle ActiveSync contact
+ *
+ * @package     Model
+ */
+
+interface Syncroton_Model_IXMLEntry extends Syncroton_Model_IEntry
+{
+    /**
+     * 
+     * @param DOMElement $_domParrent
+     * @param Syncroton_Model_IDevice $device
+     */
+    public function appendXML(DOMElement $_domParrent, Syncroton_Model_IDevice $device);
+    
+    /**
+     * return array of valid properties
+     *  
+     * @return array
+     */
+    public function getProperties();
+    
+    /**
+     * 
+     * @param SimpleXMLElement $xmlCollection
+     * @throws InvalidArgumentException
+     */
+    public function setFromSimpleXMLElement(SimpleXMLElement $properties);
+}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/MeetingResponse.php b/lib/ext/Syncroton/Model/MeetingResponse.php
index 4d1e171..fded62d 100644
--- a/lib/ext/Syncroton/Model/MeetingResponse.php
+++ b/lib/ext/Syncroton/Model/MeetingResponse.php
@@ -20,7 +20,7 @@
  * @property    string  longId
  */
 
-class Syncroton_Model_MeetingResponse extends Syncroton_Model_AEntry
+class Syncroton_Model_MeetingResponse extends Syncroton_Model_AXMLEntry
 {
     protected $_xmlBaseElement = 'Request';
     
diff --git a/lib/ext/Syncroton/Model/Policy.php b/lib/ext/Syncroton/Model/Policy.php
index 9b1dcf3..9031d51 100644
--- a/lib/ext/Syncroton/Model/Policy.php
+++ b/lib/ext/Syncroton/Model/Policy.php
@@ -14,7 +14,7 @@
  * @package     Model
  */
 
-class Syncroton_Model_Policy extends Syncroton_Model_AEntry implements Syncroton_Model_IPolicy
+class Syncroton_Model_Policy extends Syncroton_Model_AXMLEntry implements Syncroton_Model_IPolicy
 {
     protected $_xmlBaseElement = 'EASProvisionDoc';
     
diff --git a/lib/ext/Syncroton/Model/SendMail.php b/lib/ext/Syncroton/Model/SendMail.php
index b11d9b9..f8641fd 100644
--- a/lib/ext/Syncroton/Model/SendMail.php
+++ b/lib/ext/Syncroton/Model/SendMail.php
@@ -16,7 +16,7 @@
  * @package    Syncroton
  * @subpackage Model
  */
-class Syncroton_Model_SendMail extends Syncroton_Model_AEntry
+class Syncroton_Model_SendMail extends Syncroton_Model_AXMLEntry
 {
     protected $_properties = array(
         'ComposeMail' => array(
diff --git a/lib/ext/Syncroton/Model/SmartForward.php b/lib/ext/Syncroton/Model/SmartForward.php
index 40ae481..5d34c9e 100644
--- a/lib/ext/Syncroton/Model/SmartForward.php
+++ b/lib/ext/Syncroton/Model/SmartForward.php
@@ -16,7 +16,7 @@
  * @package    Syncroton
  * @subpackage Model
  */
-class Syncroton_Model_SmartForward extends Syncroton_Model_AEntry
+class Syncroton_Model_SmartForward extends Syncroton_Model_AXMLEntry
 {
     protected $_properties = array(
         'ComposeMail' => array(
diff --git a/lib/ext/Syncroton/Model/SmartReply.php b/lib/ext/Syncroton/Model/SmartReply.php
index b61cbc6..856872c 100644
--- a/lib/ext/Syncroton/Model/SmartReply.php
+++ b/lib/ext/Syncroton/Model/SmartReply.php
@@ -16,7 +16,7 @@
  * @package    Syncroton
  * @subpackage Model
  */
-class Syncroton_Model_SmartReply extends Syncroton_Model_AEntry
+class Syncroton_Model_SmartReply extends Syncroton_Model_AXMLEntry
 {
     protected $_properties = array(
         'ComposeMail' => array(
diff --git a/lib/ext/Syncroton/Model/StoreResponse.php b/lib/ext/Syncroton/Model/StoreResponse.php
index 84fe036..bd8855e 100644
--- a/lib/ext/Syncroton/Model/StoreResponse.php
+++ b/lib/ext/Syncroton/Model/StoreResponse.php
@@ -19,7 +19,7 @@
  * @property  array   range
  * @property  int     total
  */
-class Syncroton_Model_StoreResponse extends Syncroton_Model_AEntry
+class Syncroton_Model_StoreResponse extends Syncroton_Model_AXMLEntry
 {
     /**
      * status constants
@@ -51,7 +51,7 @@ class Syncroton_Model_StoreResponse extends Syncroton_Model_AEntry
 
     /**
      * (non-PHPdoc)
-     * @see Syncroton_Model_AEntry::appendXML()
+     * @see Syncroton_Model_AXMLEntry::appendXML()
      */
     public function appendXML(DOMElement $_domParrent, Syncroton_Model_IDevice $device)
     {
diff --git a/lib/ext/Syncroton/Model/StoreResponseResult.php b/lib/ext/Syncroton/Model/StoreResponseResult.php
index b129fa7..e2deb91 100644
--- a/lib/ext/Syncroton/Model/StoreResponseResult.php
+++ b/lib/ext/Syncroton/Model/StoreResponseResult.php
@@ -14,7 +14,7 @@
  * @package     Syncroton
  * @subpackage  Model
  */
-class Syncroton_Model_StoreResponseResult extends Syncroton_Model_AEntry
+class Syncroton_Model_StoreResponseResult extends Syncroton_Model_AXMLEntry
 {
     protected $_xmlBaseElement = 'Result';
 
diff --git a/lib/ext/Syncroton/Model/SyncCollection.php b/lib/ext/Syncroton/Model/SyncCollection.php
index 220226b..b6370dc 100644
--- a/lib/ext/Syncroton/Model/SyncCollection.php
+++ b/lib/ext/Syncroton/Model/SyncCollection.php
@@ -20,7 +20,7 @@
  * @property    int     windowSize
  */
 
-class Syncroton_Model_SyncCollection extends Syncroton_Model_AEntry
+class Syncroton_Model_SyncCollection extends Syncroton_Model_AXMLEntry
 {
     protected $_elements = array(
         'syncState' => null,
diff --git a/lib/ext/Syncroton/Model/Task.php b/lib/ext/Syncroton/Model/Task.php
index c4e032f..8ab473f 100644
--- a/lib/ext/Syncroton/Model/Task.php
+++ b/lib/ext/Syncroton/Model/Task.php
@@ -19,7 +19,7 @@
  * @property    string  syncKey
  * @property    int     windowSize
  */
-class Syncroton_Model_Task extends Syncroton_Model_AEntry
+class Syncroton_Model_Task extends Syncroton_Model_AXMLEntry
 {
     protected $_xmlBaseElement = 'ApplicationData';
     
diff --git a/lib/ext/Syncroton/Model/TaskRecurrence.php b/lib/ext/Syncroton/Model/TaskRecurrence.php
index eb0b4b0..116ce14 100644
--- a/lib/ext/Syncroton/Model/TaskRecurrence.php
+++ b/lib/ext/Syncroton/Model/TaskRecurrence.php
@@ -20,7 +20,7 @@
  * @property    int     windowSize
  */
 
-class Syncroton_Model_TaskRecurrence extends Syncroton_Model_AEntry
+class Syncroton_Model_TaskRecurrence extends Syncroton_Model_AXMLEntry
 {
     protected $_xmlBaseElement = 'Recurrence';
     
diff --git a/lib/ext/Syncroton/Server.php b/lib/ext/Syncroton/Server.php
index 2ae9a75..a938ffb 100644
--- a/lib/ext/Syncroton/Server.php
+++ b/lib/ext/Syncroton/Server.php
@@ -378,19 +378,14 @@ class Syncroton_Server
     {
         try {
             $device = $this->_deviceBackend->getUserDevice($ownerId, $requestParameters['deviceId']);
-
-            if ($device->useragent != $requestParameters['userAgent']) {
-                $device->useragent = $requestParameters['userAgent'];
-                $need_update = true;
-            }
-            if ($device->acsversion != $requestParameters['protocolVersion']) {
-                $device->acsversion = $requestParameters['protocolVersion'];
-                $need_update = true;
-            }
-
-            if ($need_update) {
+        
+            $device->useragent  = $requestParameters['userAgent'];
+            $device->acsversion = $requestParameters['protocolVersion'];
+            
+            if ($device->isDirty()) {
                 $device = $this->_deviceBackend->update($device);
             }
+        
         } catch (Syncroton_Exception_NotFound $senf) {
             $device = $this->_deviceBackend->create(new Syncroton_Model_Device(array(
                 'owner_id'   => $ownerId,
diff --git a/lib/kolab_sync_data.php b/lib/kolab_sync_data.php
index ee97657..1f2e384 100644
--- a/lib/kolab_sync_data.php
+++ b/lib/kolab_sync_data.php
@@ -611,6 +611,12 @@ abstract class kolab_sync_data implements Syncroton_Data_IData
     }
 
 
+    public function hasChanges(Syncroton_Backend_IContent $contentBackend, Syncroton_Model_IFolder $folder, Syncroton_Model_ISyncState $syncState)
+    {
+        return !!$this->getCountOfChanges($contentBackend, $folder, $syncState);
+    }
+
+
     /**
      * Fetches the entry from the backend
      */





More information about the commits mailing list