1: <?php
2: /**
3: * Connector class for importing ActiveSync messages from the wbxml input stream
4: * Contains code written by the Z-Push project. Original file header preserved
5: * below.
6: *
7: * Copyright 2010-2012 Horde LLC (http://www.horde.org/)
8: *
9: * @author Michael J. Rubinsky <mrubinsk@horde.org>
10: * @package ActiveSync
11: */
12:
13: /**
14: * File : streamimporter.php
15: * Project : Z-Push
16: * Descr : Stream import classes
17: *
18: * Created : 01.10.2007
19: *
20: * © Zarafa Deutschland GmbH, www.zarafaserver.de
21: * This file is distributed under GPL-2.0.
22: * Consult COPYING file for details
23: */
24: class Horde_ActiveSync_Connector_Importer
25: {
26: /**
27: * State machine
28: *
29: * @var Horde_ActiveSync_State_Base
30: */
31: protected $_state;
32:
33: /**
34: * The backend driver for communicating with the server we are syncing with.
35: *
36: * @var Horde_ActiveSync_Driver_Base
37: */
38: protected $_backend;
39:
40: /**
41: * Flags
42: *
43: * @var integer
44: */
45: protected $_flags;
46:
47: /**
48: * The server specific folder id
49: *
50: * @var string
51: */
52: protected $_folderId;
53:
54: protected $_logger;
55:
56: /**
57: * Const'r
58: *
59: * @param Horde_ActiveSync_Driver_Base $backend
60: */
61: public function __construct(Horde_ActiveSync_Driver_Base $backend)
62: {
63: $this->_backend = $backend;
64: }
65:
66: /**
67: * Initialize the exporter for this collection
68: *
69: * @param Horde_ActiveSync_State_Base $state The state machine
70: * @param string $folderId The collection's id
71: * @param integer $flags Any flags
72: */
73: public function init(Horde_ActiveSync_State_Base &$state, $folderId, $flags = 0)
74: {
75: $this->_state = &$state;
76: $this->_flags = $flags;
77: $this->_folderId = $folderId;
78: }
79:
80: /**
81: * Setter for a logger instance
82: *
83: * @param Horde_Log_Logger $logger The logger
84: */
85: public function setLogger($logger)
86: {
87: $this->_logger = $logger;
88: }
89:
90: /**
91: * Import a message change from the wbxml stream
92: *
93: * @param mixed $id A server message id or
94: * false if a new message
95: * @param Horde_ActiveSync_Message_Base $message A message object
96: * @param StdClass $device A device descriptor
97: * @param integer $clientid Client id sent from PIM
98: * on message addition.
99: *
100: * @return mixed The server message id or false
101: */
102: public function importMessageChange($id, $message, $device, $clientid)
103: {
104: /* do nothing if it is in a dummy folder */
105: if ($this->_folderId == Horde_ActiveSync::FOLDER_TYPE_DUMMY) {
106: return false;
107: }
108:
109: /* Changing an existing object */
110: if ($id) {
111: /* Check for conflicts */
112: $conflict = $this->_isConflict('change', $this->_folderId, $id);
113:
114: /* Update client state before we attempt to save changes, so we
115: * have a record of the change. This way, if the server change fails
116: * the server copy will be re-sync'd back to the PIM, maintaining
117: * at least some sort of consistency. */
118: $change = array();
119: $change['id'] = $id;
120: // mod is 0 to force a re-synch in the case of server failure. This
121: // is updated after the change succeeds in the next updateState()
122: $change['mod'] = 0;
123: $change['parent'] = $this->_folderId;
124: $change['flags'] = (isset($message->read)) ? $message->read : 0;
125: $this->_state->updateState('change', $change, Horde_ActiveSync::CHANGE_ORIGIN_NA);
126:
127: /* If this is a conflict, see if the server wins */
128: if ($conflict && $this->_flags == Horde_ActiveSync::CONFLICT_OVERWRITE_PIM) {
129: return $id;
130: }
131: } else {
132: if ($uid = $this->_state->isDuplicatePIMAddition($clientid)) {
133: // Already saw this addition, but PIM never received UID
134: return $uid;
135: }
136: }
137:
138: /* Tell the backend about the change */
139: if (!$stat = $this->_backend->changeMessage($this->_folderId, $id, $message, $device)) {
140: return false;
141: }
142: $stat['parent'] = $this->_folderId;
143:
144: /* Record the state of the message */
145: $this->_state->updateState(
146: 'change', $stat, Horde_ActiveSync::CHANGE_ORIGIN_PIM,
147: $this->_backend->getUser(), $clientid);
148:
149: return $stat['id'];
150: }
151:
152: /**
153: * Import a message deletion. This may conflict if the local object has been
154: * modified.
155: *
156: * @param string $id Server message uid
157: *
158: * @return boolean
159: */
160: public function importMessageDeletion($id)
161: {
162: /* Do nothing if it is in a dummy folder */
163: if ($this->_folderId == Horde_ActiveSync::FOLDER_TYPE_DUMMY) {
164: return true;
165: }
166:
167: /* Check for conflict */
168: $conflict = $this->_isConflict('delete', $this->_folderId, $id);
169:
170: /* Update client state */
171: $change = array();
172: $change['id'] = $id;
173: $change['mod'] = time();
174: $change['parent'] = $this->_folderId;
175: $this->_state->updateState('delete', $change, Horde_ActiveSync::CHANGE_ORIGIN_PIM, $this->_backend->getUser());
176:
177: /* If server wins the conflict, don't import change - it will be
178: * detected on next sync and sent back to PIM (since we updated the PIM
179: * state). */
180: if ($conflict && $this->_flags == Horde_ActiveSync::CONFLICT_OVERWRITE_PIM) {
181: return true;
182: }
183:
184: /* Tell backend about the deletion */
185: $this->_backend->deleteMessage($this->_folderId, $id);
186:
187: return true;
188: }
189:
190: /**
191: * Import a change in 'read' flags .. This can never conflict
192: *
193: * @param string $id Server message id
194: * @param ?? $flags The read flags to set
195: */
196: public function importMessageReadFlag($id, $flags)
197: {
198: /* Do nothing if it is a dummy folder */
199: if ($this->_folderId == Horde_ActiveSync::FOLDER_TYPE_DUMMY) {
200: return true;
201: }
202:
203: /* Update client state */
204: $change = array();
205: $change['id'] = $id;
206: $change['flags'] = $flags;
207: $this->_state->updateState('flags', $change, Horde_ActiveSync::CHANGE_ORIGIN_NA);
208:
209: /* Tell backend */
210: $this->_backend->setReadFlag($this->_folderId, $id, $flags);
211:
212: return true;
213: }
214:
215: /**
216: * Perform a message move initiated on the PIM.
217: *
218: * @TODO
219: *
220: * @param string $id The message id
221: * @param $newfolder
222: *
223: * @return boolean
224: */
225: public function importMessageMove($id, $newfolder)
226: {
227: return true;
228: }
229:
230: /**
231: * Import a folder change from the wbxml stream
232: *
233: * @param string $id The folder id
234: * @param string $parent The parent folder id?
235: * @param string $displayname The folder display name
236: * @param <unknown_type> $type The collection type?
237: *
238: * @return boolean
239: */
240: public function importFolderChange($id, $parent, $displayname, $type)
241: {
242: /* do nothing if it is a dummy folder */
243: if ($parent == Horde_ActiveSync::FOLDER_TYPE_DUMMY) {
244: return false;
245: }
246:
247: if ($id) {
248: $change = array();
249: $change['id'] = $id;
250: $change['mod'] = $displayname;
251: $change['parent'] = $parent;
252: $change['flags'] = 0;
253: $this->_state->updateState('change', $change, Horde_ActiveSync::CHANGE_ORIGIN_NA);
254: }
255:
256: /* Tell the backend */
257: $stat = $this->_backend->ChangeFolder($parent, $id, $displayname, $type);
258: if ($stat) {
259: $this->_state->updateState('change', $stat, Horde_ActiveSync::CHANGE_ORIGIN_NA);
260: }
261:
262: return $stat['id'];
263: }
264:
265: /**
266: * Imports a folder deletion from the PIM
267: *
268: * @param string $id The folder id
269: * @param string $parent The folder id of the parent folder
270: *
271: * @return boolean
272: */
273: public function importFolderDeletion($id, $parent)
274: {
275: /* Do nothing if it is a dummy folder */
276: if ($parent == Horde_ActiveSync::FOLDER_TYPE_DUMMY) {
277: return false;
278: }
279:
280: $change = array();
281: $change['id'] = $id;
282:
283: $this->_state->updateState('delete', $change, Horde_ActiveSync::CHANGE_ORIGIN_NA);
284: $this->_backend->deleteFolder($parent, $id);
285:
286: return true;
287: }
288:
289: /**
290: * Check if this change conflicts with server changes
291: * This is only true in the following situations:
292: *
293: * Changed here and changed there
294: * Changed here and deleted there
295: * Deleted here and changed there
296: *
297: * Any other combination of operations can be done
298: * (e.g. change flags & move or move & delete)
299: *
300: * @param string $type The type of change('change', 'delete' etc...)
301: * @param string $folderid The id of the folder this change is from.
302: * @param string $id The uid for the changed message.
303: *
304: * @return boolean
305: */
306: protected function _isConflict($type, $folderid, $id)
307: {
308: $stat = $this->_backend->statMessage($folderid, $id);
309: if (!$stat) {
310: /* Message is gone, if type is change, this is a conflict */
311: if ($type == 'change') {
312: return true;
313: } else {
314: return false;
315: }
316: }
317:
318: return $this->_state->isConflict($stat, $type);
319: }
320:
321: }
322: