Merge branch 'master' of http://git.tine20.org/git/Syncope
[tine20] / tine20 / library / Syncope / lib / Syncope / Command / GetItemEstimate.php
1 <?php
2 /**
3  * Syncope
4  *
5  * @package     Syncope
6  * @subpackage  Command
7  * @license     http://www.tine20.org/licenses/lgpl.html LGPL Version 3
8  * @copyright   Copyright (c) 2008-2012 Metaways Infosystems GmbH (http://www.metaways.de)
9  * @author      Lars Kneschke <l.kneschke@metaways.de>
10  */
11
12 /**
13  * class to handle ActiveSync GetItemEstimate command
14  *
15  * @package     Syncope
16  * @subpackage  Command
17  */
18 class Syncope_Command_GetItemEstimate extends Syncope_Command_Wbxml 
19 {
20     const STATUS_SUCCESS                = 1;
21     const STATUS_INVALID_COLLECTION     = 2;
22     const STATUS_SYNC_STATE_NOT_PRIMED  = 3;
23     const STATUS_INVALID_SYNC_KEY       = 4;
24     
25     protected $_defaultNameSpace    = 'uri:ItemEstimate';
26     protected $_documentElement     = 'GetItemEstimate';
27     
28     /**
29      * list of collections
30      *
31      * @var array
32      */
33     protected $_collections = array();
34     
35     /**
36      */
37     public function handle()
38     {
39         $xml = simplexml_import_dom($this->_inputDom);
40         
41         foreach ($xml->Collections->Collection as $xmlCollection) {
42             
43             // fetch values from a different namespace
44             $airSyncValues  = $xmlCollection->children('uri:AirSync');
45             
46             $collectionData = array(
47                 'syncKey'       => (int)$airSyncValues->SyncKey,
48                 'class'         => (string) $xmlCollection->Class,
49                 'collectionId'  => (string) $xmlCollection->CollectionId,
50                 'filterType'    => isset($airSyncValues->FilterType) ? (int)$airSyncValues->FilterType : 0
51             );
52             
53             if ($this->_logger instanceof Zend_Log) 
54                 $this->_logger->info(__METHOD__ . '::' . __LINE__ . " synckey is {$collectionData['syncKey']} class: {$collectionData['class']} collectionid: {$collectionData['collectionId']} filtertype: {$collectionData['filterType']}");
55             
56             try {
57                 // does the folder exist?
58                 $collectionData['folder'] = $this->_folderBackend->getFolder($this->_device, $collectionData['collectionId']);
59                 $collectionData['folder']->lastfiltertype = $collectionData['filterType'];
60                 
61                 if($collectionData['syncKey'] === 0) {
62                     $collectionData['syncState'] = new Syncope_Model_SyncState(array(
63                         'device_id' => $this->_device,
64                         'counter'   => 0,
65                         'type'      => $collectionData['folder'],
66                         'lastsync'  => $this->_syncTimeStamp
67                     ));
68                     
69                     // reset sync state for this folder
70                     $this->_syncStateBackend->resetState($this->_device, $collectionData['folder']);
71                     $this->_contentStateBackend->resetState($this->_device, $collectionData['folder']);
72                     
73                 } else {
74                     $collectionData['syncState'] = $this->_syncStateBackend->validate($this->_device, $collectionData['folder'], $collectionData['syncKey']);
75                 }                
76             } catch (Syncope_Exception_NotFound $senf) {
77                 if ($this->_logger instanceof Zend_Log)
78                     $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " " . $senf->getMessage());
79             }
80             
81             $this->_collections[$collectionData['collectionId']] = $collectionData;
82         }
83     }    
84     
85     /**
86      * (non-PHPdoc)
87      * @see Syncope_Command_Wbxml::getResponse()
88      */
89     public function getResponse()
90     {
91         $itemEstimate = $this->_outputDom->documentElement;
92         
93         foreach($this->_collections as $collectionData) {
94             $response = $itemEstimate->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Response'));
95
96             // invalid collectionid provided
97             if (empty($collectionData['folder'])) {
98                 if ($this->_logger instanceof Zend_Log)
99                     $this->_logger->warn(__METHOD__ . '::' . __LINE__ . " folder does not exist");
100                 
101                 $response->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Status', self::STATUS_INVALID_COLLECTION));
102                 $collection = $response->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Collection'));
103                 $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Class', $collectionData['class']));
104                 $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'CollectionId', $collectionData['collectionId']));
105                 $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Estimate', 0));
106                 
107             } elseif (! ($collectionData['syncState'] instanceof Syncope_Model_ISyncState)) {
108                 if ($this->_logger instanceof Zend_Log)
109                     $this->_logger->warn(__METHOD__ . '::' . __LINE__ . " invalid synckey ${collectionData['syncKey']} provided");
110                 /*
111                  * Android phones (and maybe others) don't take care about status 4(INVALID_SYNC_KEY)
112                  * To solve the problem we always return status 1(SUCCESS) even the sync key is invalid with Estimate set to 1.
113                  * This way the phone gets forced to sync. Handling invalid synckeys during sync command works without any problems.
114                  * 
115                     $response->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Status', self::STATUS_INVALID_SYNC_KEY));
116                     $collection = $response->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Collection'));
117                     $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Class', $collectionData['class']));
118                     $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'CollectionId', $collectionData['collectionId']));  
119                     $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Estimate', 0));
120                  */
121                                                               
122                 $response->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Status', self::STATUS_SUCCESS));
123                 $collection = $response->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Collection'));
124                 $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Class', $collectionData['class']));
125                 $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'CollectionId', $collectionData['collectionId']));  
126                 $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Estimate', 1));                                              
127             } else {
128                 $dataController = Syncope_Data_Factory::factory($collectionData['folder']->class, $this->_device, $this->_syncTimeStamp);
129                 
130                 $response->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Status', self::STATUS_SUCCESS));
131                 $collection = $response->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Collection'));
132                 $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Class', $collectionData['class']));
133                 $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'CollectionId', $collectionData['collectionId']));
134                 if($collectionData['syncState']->counter === 0) {
135                     // this is the first sync. in most cases there are data on the server.
136                     $count = count($dataController->getServerEntries($collectionData['collectionId'], $collectionData['filterType']));
137                 } else {
138                     $count = $this->_getItemEstimate($dataController, $collectionData);
139                 }
140                 $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Estimate', $count));
141             }
142             
143             // folderState can be NULL in case of not existing folder
144             if (isset($collectionData['folder'])) {
145                 $this->_folderBackend->update($collectionData['folder']);
146             }
147         }
148                 
149         return $this->_outputDom;
150     }
151
152     /**
153      * return number of chnaged entries
154      * 
155      * @param $_dataController
156      * @param array $_collectionData
157      * @param $_lastSyncTimeStamp
158      * @return int number of changed entries
159      */
160     protected function _getItemEstimate($_dataController, $_collectionData)
161     {
162         $dataController = Syncope_Data_Factory::factory($_collectionData['folder']->class , $this->_device, $this->_syncTimeStamp);
163         
164         $allClientEntries = $this->_contentStateBackend->getFolderState($this->_device, $_collectionData['folder']);
165         $allServerEntries = $_dataController->getServerEntries($_collectionData['collectionId'], $_collectionData['filterType']);
166         
167         $addedEntries       = array_diff($allServerEntries, $allClientEntries);
168         $deletedEntries     = array_diff($allClientEntries, $allServerEntries);
169         $changedEntries     = $_dataController->getChangedEntries($_collectionData['collectionId'], $_collectionData['syncState']->lastsync, $this->_syncTimeStamp);
170         
171         return count($addedEntries) + count($deletedEntries) + count($changedEntries);
172     }
173 }