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:  * The Horde_SyncMl_Command_Sync class provides a SyncML implementation of the
  4:  * Sync command as defined in SyncML Representation Protocol, version 1.1,
  5:  * section 5.5.15.
  6:  *
  7:  * The Sync command is used to indicate a data synchronization operation. The
  8:  * command handler for the Sync command is the central class to dispatch sync
  9:  * messages.
 10:  *
 11:  * During parsing of the received XML, the actual sync commands (Add, Replace,
 12:  * Delete) from the client are stored in the $_syncElements attribute.  When
 13:  * the output method of Horde_SyncMl_Command_Sync is called, these elements are
 14:  * processed and the resulting status messages created.  Then the server
 15:  * modifications are sent back to the client by the handleSync() method which
 16:  * is called from within the output method.
 17:  *
 18:  * Copyright 2005-2012 Horde LLC (http://www.horde.org/)
 19:  *
 20:  * See the enclosed file COPYING for license information (LGPL). If you
 21:  * did not receive this file, see http://www.horde.org/licenses/lgpl21.
 22:  *
 23:  * @author  Karsten Fourmont <karsten@horde.org>
 24:  * @author  Jan Schneider <jan@horde.org>
 25:  * @package SyncMl
 26:  */
 27: class Horde_SyncMl_Command_Sync extends Horde_SyncMl_Command
 28: {
 29:     /**
 30:      * Name of the command.
 31:      *
 32:      * @var string
 33:      */
 34:     protected $_cmdName = 'Sync';
 35: 
 36:     /**
 37:      * Source database of the <Sync> command.
 38:      *
 39:      * @var string
 40:      */
 41:     protected $_sourceURI;
 42: 
 43:     /**
 44:      * Target database of the <Sync> command.
 45:      *
 46:      * @var string
 47:      */
 48:     protected $_targetURI;
 49: 
 50:     /**
 51:      * Horde_SyncMl_SyncElement object for the currently parsed sync command.
 52:      *
 53:      * @var Horde_SyncMl_SyncElement
 54:      */
 55:     protected $_curItem;
 56: 
 57:     /**
 58:      * List of all Horde_SyncMl_SyncElement objects that have parsed.
 59:      *
 60:      * @var array
 61:      */
 62:     protected $_syncElements = array();
 63: 
 64:     /**
 65:      * The MIME content type of the currently parsed sync command as specified
 66:      * by the <Type> element inside a <Meta> section.
 67:      *
 68:      * @var string
 69:      */
 70:     protected $_contentType = 'text/plain';
 71: 
 72:     /**
 73:      * Encoding format of the content as specified in the <Meta><Format>
 74:      * element, like 'b64'.
 75:      *
 76:      * @var string
 77:      */
 78:     protected $_contentFormat = 'chr';
 79: 
 80:     /**
 81:      * The command ID (<CmdID>) of the currently parsed sync command.
 82:      *
 83:      * This is different from the command ID of the <Sync> command itself.
 84:      *
 85:      * @var integer
 86:      */
 87:     protected $_itemCmdID;
 88: 
 89:     /**
 90:      * Name of the currently parsed sync command, like 'Add'.
 91:      *
 92:      * @var string
 93:      */
 94:     protected $_elementType;
 95: 
 96:     /**
 97:      * Whether a <MoreData> element has indicated that the sync command is
 98:      * split into several SyncML message chunks.
 99:      *
100:      * @var boolean
101:      */
102:     protected $_itemMoreData;
103: 
104:     /**
105:      * The size of the data item of the currently parsed sync command in bytes
106:      * as specified by a <Size> element.
107:      *
108:      * @var integer
109:      */
110:     protected $_itemSize;
111: 
112:     /**
113:      * Start element handler for the XML parser, delegated from
114:      * Horde_SyncMl_ContentHandler::startElement().
115:      *
116:      * @param string $uri      The namespace URI of the element.
117:      * @param string $element  The element tag name.
118:      * @param array $attrs     A hash with the element's attributes.
119:      */
120:     public function startElement($uri, $element, $attrs)
121:     {
122:         parent::startElement($uri, $element, $attrs);
123:         $state = $GLOBALS['backend']->state;
124: 
125:         switch (count($this->_stack)) {
126:         case 2:
127:             if ($element == 'Replace' ||
128:                 $element == 'Add' ||
129:                 $element == 'Delete') {
130:                 $this->_contentType = 'text/plain';
131:                 $this->_elementType = $element;
132:                 $this->_itemSize = null;
133:             }
134:             break;
135: 
136:         case 3:
137:             if ($element == 'Item') {
138:                 if (isset($state->curSyncItem)) {
139:                     // Copy from state in case of <MoreData>.
140:                     $this->_curItem = $state->curSyncItem;
141:                     // Set CmdID to the current CmdId, not the initial one
142:                     // from the first message.
143:                     $this->_curItem->cmdID = $this->_itemCmdID;
144:                 } else {
145:                     $this->_curItem = new Horde_SyncMl_SyncElement(
146:                         $state->getSync($this->_targetURI),
147:                         $this->_elementType,
148:                         $this->_itemCmdID,
149:                         $this->_itemSize);
150:                 }
151:                 $this->_itemMoreData = false;
152:             }
153:         }
154:     }
155: 
156:     /**
157:      * End element handler for the XML parser, delegated from
158:      * Horde_SyncMl_ContentHandler::endElement().
159:      *
160:      * @param string $uri      The namespace URI of the element.
161:      * @param string $element  The element tag name.
162:      */
163:     public function endElement($uri, $element)
164:     {
165:         switch (count($this->_stack)) {
166:         case 3:
167:             switch ($element) {
168:             case 'LocURI':
169:                 if (!isset($this->_currentSyncElement)) {
170:                     if ($this->_stack[1] == 'Source') {
171:                         $this->_sourceURI = trim($this->_chars);
172:                     } elseif ($this->_stack[1] == 'Target') {
173:                         $this->_targetURI = trim($this->_chars);
174:                     }
175:                 }
176:                 break;
177: 
178:             case 'Item':
179:                 if ($this->_itemMoreData) {
180:                     // Store to continue in next session.
181:                     $GLOBALS['backend']->state->curSyncItem = $this->_curItem;
182:                 } else {
183:                     // Finished. Store to syncElements[].
184:                     if (empty($this->_curItem->contentType)) {
185:                         $this->_curItem->contentType = $this->_contentType;
186:                     }
187:                     if (empty($this->_curItem->contentFormat)) {
188:                         $this->_curItem->contentFormat = $this->_contentFormat;
189:                     }
190: 
191:                     $this->_syncElements[] = $this->_curItem;
192:                     // @todo: check if size matches strlen(content) when
193:                     // size>0, esp. in case of <MoreData>.
194: 
195:                     // Unset the saved state item if it was not unset in
196:                     // endElement().
197:                     if (isset($GLOBALS['backend']->state->curSyncItem)) {
198:                         unset($GLOBALS['backend']->state->curSyncItem);
199:                     }
200: 
201:                     unset($this->_curItem);
202:                 }
203:                 break;
204: 
205:             case 'CmdID':
206:                 $this->_itemCmdID = trim($this->_chars);
207:                 break;
208:             }
209:             break;
210: 
211:         case 4:
212:             switch ($element) {
213:             case 'Format':
214:                 if ($this->_stack[2] == 'Meta') {
215:                     $this->_contentFormat = trim($this->_chars);
216:                 }
217:                 break;
218:             case 'Type':
219:                 if ($this->_stack[2] == 'Meta') {
220:                     $this->_contentType = trim($this->_chars);
221:                 }
222:                 break;
223:             case 'Data':
224:                 // Trim only if we had a MoreData tag before to not corrupt
225:                 // pictures.
226:                 if (isset($GLOBALS['backend']->state->curSyncItem)) {
227:                     $this->_curItem->content .= ltrim($this->_chars);
228:                     unset($GLOBALS['backend']->state->curSyncItem);
229:                 } else {
230:                     // Don't trim, because we have to check the raw content's
231:                     // size.
232:                     $this->_curItem->content .= $this->_chars;
233:                 }
234:                 break;
235:             case 'MoreData':
236:                 $this->_itemMoreData = true;
237:                 break;
238:             case 'Size':
239:                 $this->_itemSize = $this->_chars;
240:                 break;
241:             }
242:             break;
243: 
244:         case 5:
245:             switch ($element) {
246:             case 'LocURI':
247:                 if ($this->_stack[3] == 'Source') {
248:                     $this->_curItem->cuid = trim($this->_chars);
249:                 } elseif ($this->_stack[3] == 'Target') {
250:                     // Not used: we ignore "suid proposals" from client.
251:                 }
252:                 break;
253: 
254:             case 'Format':
255:                 if ($this->_stack[3] == 'Meta') {
256:                     $this->_curItem->contentFormat = trim($this->_chars);
257:                 }
258:                 break;
259: 
260:             case 'Type':
261:                 $this->_curItem->contentType = trim($this->_chars);
262:                 break;
263:             }
264:             break;
265: 
266:         case 6:
267:             if ($element == 'Type') {
268:                 $this->_curItem->contentType = trim($this->_chars);
269:             }
270:             break;
271:         }
272: 
273:         parent::endElement($uri, $element);
274:     }
275: 
276:     /**
277:      * Implements the actual business logic of the Sync command.
278:      */
279:     public function handleCommand($debug = false)
280:     {
281:         $state = $GLOBALS['backend']->state;
282: 
283:         // Handle unauthenticated first.
284:         if (!$state->authenticated) {
285:             $this->_outputHandler->outputStatus($this->_cmdID, $this->_cmdName,
286:                                                 Horde_SyncMl::RESPONSE_INVALID_CREDENTIALS);
287:             return;
288:         }
289: 
290:         if ($debug) {
291:             $sync = &$state->getSync($this->_targetURI);
292:             $sync = new Horde_SyncMl_Sync(Horde_SyncMl::ALERT_TWO_WAY,
293:                                     $this->_targetURI,
294:                                     $this->_sourceURI,
295:                                     0, 0, 0);
296:         } else {
297:             $sync = &$state->getSync($this->_targetURI);
298:             $sync->addSyncReceived();
299: 
300:             if (!is_object($sync)) {
301:                 $GLOBALS['backend']->logMessage(
302:                     'No sync object found for URI ' . $this->_targetURI, 'ERR');
303:                 // @todo: create meaningful status code here.
304:             }
305:         }
306: 
307:         /* @todo: Check: do we send a status for every sync or only once after
308:          * one sync is completed?
309:          * SE K750 expects Status response to be sent before Sync output
310:          * by server is produced. */
311:         $this->_outputHandler->outputStatus($this->_cmdID, $this->_cmdName,
312:                                             Horde_SyncMl::RESPONSE_OK,
313:                                             $this->_targetURI,
314:                                             $this->_sourceURI);
315: 
316:         // Here's where client modifications are processed.
317:         $device = $state->getDevice();
318:         $omit = $device->omitIndividualSyncStatus();
319:         foreach ($this->_syncElements as $item) {
320:             $result = $sync->handleClientSyncItem($this->_outputHandler, $item);
321:             if (!$omit) {
322:                 $this->_outputStatus($item);
323:             }
324:         }
325: 
326:         if ($this->_itemMoreData) {
327:             // Last item had <MoreData> element, produce appropriate response.
328:             $this->_outputHandler->outputStatus(
329:                 $state->curSyncItem->cmdID,
330:                 $state->curSyncItem->elementType,
331:                 Horde_SyncMl::RESPONSE_CHUNKED_ITEM_ACCEPTED_AND_BUFFERED,
332:                 '',
333:                 $state->curSyncItem->cuid);
334:             // @todo: check if we have to send Alert NEXT_MESSAGE here!
335:         }
336:     }
337: 
338:     /**
339:      * Creates the <Status> response for one Add|Replace|Delete SyncElement.
340:      *
341:      * @param Horde_SyncMl_SyncElement $element  The element for which the status is
342:      *                                     to be created.
343:      */
344:     protected function _outputStatus($element)
345:     {
346:         // @todo: produce valid status
347:         $this->_outputHandler->outputStatus($element->cmdID,
348:                                             $element->elementType,
349:                                             $element->responseCode,
350:                                             '',
351:                                             $element->cuid);
352:     }
353: }
354: 
API documentation generated by ApiGen