PageRenderTime 36ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/framework/SyncMl/lib/Horde/SyncMl/Command/Sync.php

https://github.com/wrobel/horde
PHP | 339 lines | 179 code | 31 blank | 129 comment | 38 complexity | 7239141868b085ef6103437de4531231 MD5 | raw file
Possible License(s): BSD-2-Clause, AGPL-1.0, LGPL-2.1, LGPL-3.0, BSD-3-Clause, LGPL-2.0, GPL-2.0
  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. * Source database of the <Sync> command.
  37. *
  38. * @var string
  39. */
  40. protected $_sourceURI;
  41. /**
  42. * Target database of the <Sync> command.
  43. *
  44. * @var string
  45. */
  46. protected $_targetURI;
  47. /**
  48. * Horde_SyncMl_SyncElement object for the currently parsed sync command.
  49. *
  50. * @var Horde_SyncMl_SyncElement
  51. */
  52. protected $_curItem;
  53. /**
  54. * List of all Horde_SyncMl_SyncElement objects that have parsed.
  55. *
  56. * @var array
  57. */
  58. protected $_syncElements = array();
  59. /**
  60. * The MIME content type of the currently parsed sync command as specified
  61. * by the <Type> element inside a <Meta> section.
  62. *
  63. * @var string
  64. */
  65. protected $_contentType = 'text/plain';
  66. /**
  67. * Encoding format of the content as specified in the <Meta><Format>
  68. * element, like 'b64'.
  69. *
  70. * @var string
  71. */
  72. protected $_contentFormat = 'chr';
  73. /**
  74. * The command ID (<CmdID>) of the currently parsed sync command.
  75. *
  76. * This is different from the command ID of the <Sync> command itself.
  77. *
  78. * @var integer
  79. */
  80. protected $_itemCmdID;
  81. /**
  82. * Name of the currently parsed sync command, like 'Add'.
  83. *
  84. * @var string
  85. */
  86. protected $_elementType;
  87. /**
  88. * Whether a <MoreData> element has indicated that the sync command is
  89. * split into several SyncML message chunks.
  90. *
  91. * @var boolean
  92. */
  93. protected $_itemMoreData;
  94. /**
  95. * The size of the data item of the currently parsed sync command in bytes
  96. * as specified by a <Size> element.
  97. *
  98. * @var integer
  99. */
  100. protected $_itemSize;
  101. /**
  102. * Start element handler for the XML parser, delegated from
  103. * Horde_SyncMl_ContentHandler::startElement().
  104. *
  105. * @param string $uri The namespace URI of the element.
  106. * @param string $element The element tag name.
  107. * @param array $attrs A hash with the element's attributes.
  108. */
  109. public function startElement($uri, $element, $attrs)
  110. {
  111. parent::startElement($uri, $element, $attrs);
  112. $state = $GLOBALS['backend']->state;
  113. switch (count($this->_stack)) {
  114. case 2:
  115. if ($element == 'Replace' ||
  116. $element == 'Add' ||
  117. $element == 'Delete') {
  118. $this->_contentType = 'text/plain';
  119. $this->_elementType = $element;
  120. $this->_itemSize = null;
  121. }
  122. break;
  123. case 3:
  124. if ($element == 'Item') {
  125. if (isset($state->curSyncItem)) {
  126. // Copy from state in case of <MoreData>.
  127. $this->_curItem = $state->curSyncItem;
  128. // Set CmdID to the current CmdId, not the initial one
  129. // from the first message.
  130. $this->_curItem->cmdID = $this->_itemCmdID;
  131. unset($state->curSyncItem);
  132. } else {
  133. $this->_curItem = new Horde_SyncMl_SyncElement(
  134. $state->getSync($this->_targetURI),
  135. $this->_elementType,
  136. $this->_itemCmdID,
  137. $this->_itemSize);
  138. }
  139. $this->_itemMoreData = false;
  140. }
  141. }
  142. }
  143. /**
  144. * End element handler for the XML parser, delegated from
  145. * Horde_SyncMl_ContentHandler::endElement().
  146. *
  147. * @param string $uri The namespace URI of the element.
  148. * @param string $element The element tag name.
  149. */
  150. public function endElement($uri, $element)
  151. {
  152. switch (count($this->_stack)) {
  153. case 3:
  154. switch ($element) {
  155. case 'LocURI':
  156. if (!isset($this->_currentSyncElement)) {
  157. if ($this->_stack[1] == 'Source') {
  158. $this->_sourceURI = trim($this->_chars);
  159. } elseif ($this->_stack[1] == 'Target') {
  160. $this->_targetURI = trim($this->_chars);
  161. }
  162. }
  163. break;
  164. case 'Item':
  165. if ($this->_itemMoreData) {
  166. // Store to continue in next session.
  167. $GLOBALS['backend']->state->curSyncItem = $this->_curItem;
  168. } else {
  169. // Finished. Store to syncElements[].
  170. if (empty($this->_curItem->contentType)) {
  171. $this->_curItem->contentType = $this->_contentType;
  172. }
  173. if (empty($this->_curItem->contentFormat)) {
  174. $this->_curItem->contentFormat = $this->_contentFormat;
  175. }
  176. $this->_syncElements[] = $this->_curItem;
  177. // @todo: check if size matches strlen(content) when
  178. // size>0, esp. in case of <MoreData>.
  179. unset($this->_curItem);
  180. }
  181. break;
  182. case 'CmdID':
  183. $this->_itemCmdID = trim($this->_chars);
  184. break;
  185. }
  186. break;
  187. case 4:
  188. switch ($element) {
  189. case 'Format':
  190. if ($this->_stack[2] == 'Meta') {
  191. $this->_contentFormat = trim($this->_chars);
  192. }
  193. break;
  194. case 'Type':
  195. if ($this->_stack[2] == 'Meta') {
  196. $this->_contentType = trim($this->_chars);
  197. }
  198. break;
  199. case 'Data':
  200. // Don't trim, because we have to check the raw content's size.
  201. $this->_curItem->content .= $this->_chars;
  202. break;
  203. case 'MoreData':
  204. $this->_itemMoreData = true;
  205. break;
  206. case 'Size':
  207. $this->_itemSize = $this->_chars;
  208. break;
  209. }
  210. break;
  211. case 5:
  212. switch ($element) {
  213. case 'LocURI':
  214. if ($this->_stack[3] == 'Source') {
  215. $this->_curItem->cuid = trim($this->_chars);
  216. } elseif ($this->_stack[3] == 'Target') {
  217. // Not used: we ignore "suid proposals" from client.
  218. }
  219. break;
  220. case 'Format':
  221. if ($this->_stack[3] == 'Meta') {
  222. $this->_curItem->contentFormat = trim($this->_chars);
  223. }
  224. break;
  225. case 'Type':
  226. $this->_curItem->contentType = trim($this->_chars);
  227. break;
  228. }
  229. break;
  230. case 6:
  231. if ($element == 'Type') {
  232. $this->_curItem->contentType = trim($this->_chars);
  233. }
  234. break;
  235. }
  236. parent::endElement($uri, $element);
  237. }
  238. /**
  239. * Implements the actual business logic of the Sync command.
  240. */
  241. public function handleCommand($debug = false)
  242. {
  243. $state = $GLOBALS['backend']->state;
  244. // Handle unauthenticated first.
  245. if (!$state->authenticated) {
  246. $this->_outputHandler->outputStatus($this->_cmdID, $this->_cmdName,
  247. Horde_SyncMl::RESPONSE_INVALID_CREDENTIALS);
  248. return;
  249. }
  250. if ($debug) {
  251. $sync = &$state->getSync($this->_targetURI);
  252. $sync = new Horde_SyncMl_Sync(Horde_SyncMl::ALERT_TWO_WAY,
  253. $this->_targetURI,
  254. $this->_sourceURI,
  255. 0, 0, 0);
  256. } else {
  257. $sync = &$state->getSync($this->_targetURI);
  258. $sync->addSyncReceived();
  259. if (!is_object($sync)) {
  260. $GLOBALS['backend']->logMessage(
  261. 'No sync object found for URI ' . $this->_targetURI, 'ERR');
  262. // @todo: create meaningful status code here.
  263. }
  264. }
  265. /* @todo: Check: do we send a status for every sync or only once after
  266. * one sync is completed?
  267. * SE K750 expects Status response to be sent before Sync output
  268. * by server is produced. */
  269. $this->_outputHandler->outputStatus($this->_cmdID, $this->_cmdName,
  270. Horde_SyncMl::RESPONSE_OK,
  271. $this->_targetURI,
  272. $this->_sourceURI);
  273. // Here's where client modifications are processed.
  274. $device = $state->getDevice();
  275. $omit = $device->omitIndividualSyncStatus();
  276. foreach ($this->_syncElements as $item) {
  277. $result = $sync->handleClientSyncItem($this->_outputHandler, $item);
  278. if (!$omit) {
  279. $this->_outputStatus($item);
  280. }
  281. }
  282. if ($this->_itemMoreData) {
  283. // Last item had <MoreData> element, produce appropriate response.
  284. $this->_outputHandler->outputStatus(
  285. $state->curSyncItem->cmdID,
  286. $state->curSyncItem->elementType,
  287. Horde_SyncMl::RESPONSE_CHUNKED_ITEM_ACCEPTED_AND_BUFFERED,
  288. '',
  289. $state->curSyncItem->cuid);
  290. // @todo: check if we have to send Alert NEXT_MESSAGE here!
  291. }
  292. }
  293. /**
  294. * Creates the <Status> response for one Add|Replace|Delete SyncElement.
  295. *
  296. * @param Horde_SyncMl_SyncElement $element The element for which the status is
  297. * to be created.
  298. */
  299. protected function _outputStatus($element)
  300. {
  301. // @todo: produce valid status
  302. $this->_outputHandler->outputStatus($element->cmdID,
  303. $element->elementType,
  304. $element->responseCode,
  305. '',
  306. $element->cuid);
  307. }
  308. }