1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46:
47: class Horde_ActiveSync_State_History extends Horde_ActiveSync_State_Base
48: {
49: 50: 51: 52: 53:
54: protected $_lastSyncTS = 0;
55:
56: 57: 58: 59: 60:
61: protected $_thisSyncTS = 0;
62:
63: 64: 65: 66: 67:
68: protected $_state;
69:
70: 71: 72: 73: 74:
75: protected $_db;
76:
77:
78: protected $_syncStateTable;
79: protected $_syncMapTable;
80: protected $_syncDeviceTable;
81: protected $_syncUsersTable;
82:
83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96:
97: public function __construct($params = array())
98: {
99: parent::__construct($params);
100: if (empty($this->_params['db']) || !($this->_params['db'] instanceof Horde_Db_Adapter)) {
101: throw new InvalidArgumentException('Missing or invalid Horde_Db parameter.');
102: }
103:
104: $this->_syncStateTable = $params['statetable'];
105: $this->_syncMapTable = $params['maptable'];
106: $this->_syncDeviceTable = $params['devicetable'];
107: $this->_syncUsersTable = $params['userstable'];
108: $this->_db = $params['db'];
109: }
110:
111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123:
124: public function loadState($syncKey, $type = null, $id = null)
125: {
126: $this->_type = $type;
127: if ($type == 'foldersync' && empty($id)) {
128: $id = 'foldersync';
129: }
130: if (empty($syncKey)) {
131: $this->_state = array();
132: $this->_resetDeviceState($id);
133: return;
134: }
135: $this->_logger->debug(
136: sprintf('[%s] Loading state for synckey %s',
137: $this->_devId,
138: $syncKey));
139:
140:
141: if (!preg_match('/^s{0,1}\{([0-9A-Za-z-]+)\}([0-9]+)$/', $syncKey, $matches)) {
142: throw new Horde_ActiveSync_Exception('Invalid sync key');
143: }
144: $this->_syncKey = $syncKey;
145:
146:
147: $this->_gc($syncKey);
148:
149:
150: try {
151: $results = $this->_db->selectOne('SELECT sync_data, sync_devid, sync_time FROM '
152: . $this->_syncStateTable . ' WHERE sync_key = ?', array($this->_syncKey));
153: } catch (Horde_Db_Exception $e) {
154: throw new Horde_ActiveSync_Exception($e);
155: }
156: if (!$results) {
157: throw new Horde_ActiveSync_Exception('Sync State Not Found.');
158: }
159:
160:
161: $this->_lastSyncTS = !empty($results['sync_time']) ? $results['sync_time'] : 0;
162:
163:
164:
165: $this->_thisSyncTS = $this->_lastSyncTS;
166:
167:
168: $data = unserialize($results['sync_data']);
169: if ($type == 'foldersync') {
170: $this->_state = ($data !== false) ? $data : array();
171: $this->_logger->debug(
172: sprintf('[%s] Loading FOLDERSYNC state: %s',
173: $this->_devId,
174: print_r($this->_state, true)));
175: } elseif ($type == 'sync') {
176: $this->_changes = ($data !== false) ? $data : null;
177: if ($this->_changes) {
178: $this->_logger->debug(
179: sprintf('[%s] Found %d changes remaining from previous SYNC.',
180: $this->_devId,
181: count($this->_changes)));
182: }
183: }
184: }
185:
186: 187: 188: 189: 190: 191: 192: 193: 194:
195: public function isConflict($stat, $type)
196: {
197:
198: if ($stat['mod'] > $this->_lastSyncTS) {
199: if ($type == 'delete' || $type == 'change') {
200:
201:
202: return true;
203: } else {
204:
205: return false;
206: }
207: }
208: }
209:
210: 211: 212: 213: 214:
215: public function save()
216: {
217:
218: $sql = 'INSERT INTO ' . $this->_syncStateTable
219: . ' (sync_key, sync_data, sync_devid, sync_time, sync_folderid, sync_user)'
220: . ' VALUES (?, ?, ?, ?, ?, ?)';
221:
222:
223: if ($this->_type == 'foldersync') {
224: $data = (isset($this->_state) ? serialize($this->_state) : '');
225: } elseif ($this->_type == 'sync') {
226: $data = (isset($this->_changes) ? serialize(array_values($this->_changes)) : '');
227: } else {
228: $data = '';
229: }
230:
231: $params = array(
232: $this->_syncKey,
233: $data,
234: $this->_devId,
235: $this->_thisSyncTS,
236: !empty($this->_collection['id']) ? $this->_collection['id'] : 'foldersync',
237: $this->_deviceInfo->user);
238: $this->_logger->debug(
239: sprintf('[%s] Saving state: %s', $this->_devId, print_r($params, true)));
240: try {
241: $this->_db->insert($sql, $params);
242: } catch (Horde_Db_Exception $e) {
243:
244: $this->_logger->notice(
245: sprintf('[%s] Error saving state for synckey %s: %s - removing previous sync state and trying again.',
246: $this->_devId,
247: $this->_syncKey,
248: $e->getMessage()));
249: $this->_db->delete('DELETE FROM ' . $this->_syncStateTable . ' WHERE sync_key = ?', array($this->_syncKey));
250: $this->_db->insert($sql, $params);
251: }
252: }
253:
254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275:
276: public function updateState($type, array $change,
277: $origin = Horde_ActiveSync::CHANGE_ORIGIN_NA,
278: $user = null, $clientid = '')
279: {
280: $this->_logger->debug('Updating state during ' . $type);
281: if ($origin == Horde_ActiveSync::CHANGE_ORIGIN_PIM) {
282: $sql = 'INSERT INTO ' . $this->_syncMapTable
283: . ' (message_uid, sync_modtime, sync_key, sync_devid, sync_folderid, sync_user, sync_clientid) '
284: . 'VALUES (?, ?, ?, ?, ?, ?, ?)';
285: try {
286: $this->_db->insert(
287: $sql,
288: array(
289: $change['id'],
290: $change['mod'],
291: $this->_syncKey,
292: $this->_devId,
293: $change['parent'],
294: $user,
295: $clientid)
296: );
297: } catch (Horde_Db_Exception $e) {
298: $this->_logger->err($e->getMessage());
299: throw new Horde_ActiveSync_Exception($e);
300: }
301:
302: } else {
303:
304:
305:
306: foreach ($this->_changes as $key => $value) {
307: if ($value['id'] == $change['id']) {
308: if ($type == 'foldersync') {
309: foreach ($this->_state as $fi => $state) {
310: if ($state['id'] == $value['id']) {
311: unset($this->_state[$fi]);
312: }
313: }
314:
315:
316:
317: $stat = array(
318: 'id' => $value['id'],
319: 'mod' => (empty($value['mod']) ? $value['id'] : $value['mod']),
320: 'parent' => (empty($value['parent']) ? 0 : $value['parent'])
321: );
322: $this->_state[] = $stat;
323: $this->_state = array_values($this->_state);
324: }
325: unset($this->_changes[$key]);
326: break;
327: }
328: }
329: }
330: }
331:
332: 333: 334: 335: 336: 337: 338: 339: 340: 341: 342:
343: public function setFolderData($device, $folders)
344: {
345: if (!is_array($folders) || empty ($folders)) {
346: return false;
347: }
348:
349: $unique_folders = array ();
350: foreach ($folders as $folder) {
351:
352: if ($folder->type == Horde_ActiveSync::FOLDER_TYPE_INBOX) {
353: continue;
354: }
355:
356:
357: if (!array_key_exists($folder->type, $unique_folders) || $folder->parentid == 0) {
358: $unique_folders[$folder->type] = $folder->serverid;
359: }
360: }
361:
362:
363:
364: if (!array_key_exists(Horde_ActiveSync::FOLDER_TYPE_APPOINTMENT, $unique_folders)) {
365: $unique_folders[Horde_ActiveSync::FOLDER_TYPE_APPOINTMENT] = Horde_ActiveSync::FOLDER_TYPE_DUMMY;
366: }
367: if (!array_key_exists(Horde_ActiveSync::FOLDER_TYPE_CONTACT, $unique_folders)) {
368: $unique_folders[Horde_ActiveSync::FOLDER_TYPE_CONTACT] = Horde_ActiveSync::FOLDER_TYPE_DUMMY;
369: }
370:
371:
372: $sql = 'UPDATE ' . $this->_syncUsersTable . ' SET device_folders = ? WHERE device_id = ? AND device_user = ?';
373: try {
374: return $this->_db->update($sql, array(serialize($folders), $device->id, $device->user));
375: } catch (Horde_Db_Exception $e) {
376: throw new Horde_ActiveSync_Exception($e);
377: }
378: }
379:
380: 381: 382: 383: 384: 385: 386: 387:
388: public function getFolderData($device, $class)
389: {
390: $sql = 'SELECT device_folders FROM ' . $this->_syncUsersTable . ' WHERE device_id = ? AND device_user = ?';
391: try {
392: $folders = $this->_db->selectValue($sql, array($device->id, $device->user));
393: } catch (Horde_Db_Exception $e) {
394: throw new Horde_ActiveSync_Exception($e);
395: }
396: if ($folders) {
397: $folders = unserialize($folders);
398: if ($class == "Calendar") {
399: return $folders[Horde_ActiveSync::FOLDER_TYPE_APPOINTMENT];
400: }
401: if ($class == "Contacts") {
402: return $folders[Horde_ActiveSync::FOLDER_TYPE_CONTACT];
403: }
404: }
405:
406: return false;
407: }
408:
409: 410: 411: 412: 413: 414: 415:
416: public function getKnownFolders()
417: {
418: if (!isset($this->_state)) {
419: throw new Horde_ActiveSync_Exception('Sync state not loaded');
420: }
421: $folders = array();
422: foreach ($this->_state as $folder) {
423: $folders[] = $folder['id'];
424: }
425:
426: return $folders;
427: }
428:
429: 430: 431: 432: 433: 434: 435:
436: public function initPingState($device)
437: {
438:
439:
440: if (!isset($this->_pingState) || $this->_devId !== $device->id) {
441: throw new Horde_ActiveSync_Exception('Device not loaded');
442: }
443:
444: return $this->_pingState['collections'];
445: }
446:
447: 448: 449: 450: 451: 452: 453: 454: 455: 456:
457: public function loadDeviceInfo($devId, $user)
458: {
459: $this->_logger->debug('[' . $devId . '] loadDeviceInfo: ' . $user);
460:
461:
462: if ($this->_devId == $devId && !empty($this->_deviceInfo) &&
463: $user == $this->_deviceInfo->user) {
464: return $this->_deviceInfo;
465: }
466:
467: $this->_devId = $devId;
468: $query = 'SELECT device_type, device_agent, '
469: . 'device_rwstatus, device_supported FROM '
470: . $this->_syncDeviceTable . ' WHERE device_id = ?';
471:
472: try {
473: $device = $this->_db->selectOne($query, array($devId));
474: } catch (Horde_Db_Exception $e) {
475: throw new Horde_ActiveSync_Exception($e);
476: }
477:
478: if (!empty($user)) {
479: $query = 'SELECT device_ping, device_policykey FROM ' . $this->_syncUsersTable
480: . ' WHERE device_id = ? AND device_user = ?';
481: try {
482: $duser = $this->_db->selectOne($query, array($devId, $user));
483: } catch (Horde_Db_Exception $e) {
484: throw new Horde_ActiveSync_Exception($e);
485: }
486: } else {
487: $this->resetPingState();
488: }
489:
490: $this->_deviceInfo = new StdClass();
491: if ($device) {
492: $this->_deviceInfo->rwstatus = $device['device_rwstatus'];
493: $this->_deviceInfo->deviceType = $device['device_type'];
494: $this->_deviceInfo->userAgent = $device['device_agent'];
495: $this->_deviceInfo->id = $devId;
496: $this->_deviceInfo->user = $user;
497: $this->_deviceInfo->supported = unserialize($device['device_supported']);
498: if (empty($duser)) {
499: $this->resetPingState();
500: $this->_deviceInfo->policykey = 0;
501: } else {
502: if (empty($duser['device_ping'])) {
503: $this->resetPingState();
504: } else {
505: $this->_pingState = unserialize($duser['device_ping']);
506: }
507: $this->_deviceInfo->policykey =
508: (empty($duser['device_policykey']) ?
509: 0 :
510: $duser['device_policykey']);
511: }
512: } else {
513: throw new Horde_ActiveSync_Exception('Device not found.');
514: }
515:
516: return $this->_deviceInfo;
517: }
518:
519: 520: 521: 522: 523: 524: 525:
526: public function setDeviceInfo($data)
527: {
528:
529: try {
530: if (!$this->deviceExists($data->id)) {
531: $this->_logger->debug('[' . $data->id . '] Device entry does not exist, creating it.');
532: $query = 'INSERT INTO ' . $this->_syncDeviceTable
533: . ' (device_type, device_agent, device_rwstatus, device_id, device_supported)'
534: . ' VALUES(?, ?, ?, ?, ?)';
535: $values = array(
536: $data->deviceType,
537: $data->userAgent,
538: $data->rwstatus,
539: $data->id,
540: (!empty($data->supported) ? serialize($data->supported) : '')
541: );
542: $this->_db->execute($query, $values);
543: }
544: } catch(Horde_Db_Exception $e) {
545: throw new Horde_ActiveSync_Exception($e);
546: }
547:
548: $this->_deviceInfo = $data;
549:
550:
551: try {
552: $query = 'SELECT COUNT(*) FROM ' . $this->_syncUsersTable . ' WHERE device_id = ? AND device_user = ?';
553: $cnt = $this->_db->selectValue($query, array($data->id, $data->user));
554: if (!$cnt) {
555: $this->_logger->debug('[' . $data->id . '] Device entry does not exist for user ' . $data->user . ', creating it.');
556: $query = 'INSERT INTO ' . $this->_syncUsersTable
557: . ' (device_ping, device_id, device_user, device_policykey)'
558: . ' VALUES(?, ?, ?, ?)';
559:
560: $values = array(
561: '',
562: $data->id,
563: $data->user,
564: $data->policykey
565: );
566: $this->_devId = $data->id;
567: return $this->_db->insert($query, $values);
568: } else {
569: return true;
570: }
571: } catch (Horde_Db_Exception $e) {
572: throw new Horde_ActiveSync_Exception($e);
573: }
574: }
575:
576: 577: 578: 579: 580: 581: 582: 583: 584: 585:
586: public function deviceExists($devId, $user = null)
587: {
588: if (!empty($user)) {
589: $query = 'SELECT COUNT(*) FROM ' . $this->_syncUsersTable
590: . ' WHERE device_id = ? AND device_user = ?';
591: $values = array($devId, $user);
592: } else {
593: $query = 'SELECT COUNT(*) FROM ' . $this->_syncDeviceTable . ' WHERE device_id = ?';
594: $values = array($devId);
595: }
596:
597: try {
598: return $this->_db->selectValue($query, $values);
599: } catch (Horde_Db_Exception $e) {
600: throw new Horde_ActiveSync_Exception($e);
601: }
602: }
603:
604: 605: 606: 607: 608: 609:
610: public function listDevices($user = null)
611: {
612: $query = 'SELECT d.device_id AS device_id, device_type, device_agent,'
613: . ' device_policykey, device_rwstatus, device_user FROM '
614: . $this->_syncDeviceTable . ' d INNER JOIN ' . $this->_syncUsersTable
615: . ' u ON d.device_id = u.device_id';
616: $values = array();
617: if (!empty($user)) {
618: $query .= ' WHERE u.device_user = ?';
619: $values[] = $user;
620: }
621:
622: try {
623: return $this->_db->selectAll($query, $values);
624: } catch (Horde_Db_Exception $e) {
625: throw new Horde_ActiveSync_Exception($e);
626: }
627: }
628:
629: 630: 631: 632: 633: 634:
635: public function getLastSyncTimestamp()
636: {
637: if (empty($this->_deviceInfo)) {
638: throw new Horde_ActiveSync_Exception('Device not loaded.');
639: }
640:
641: $sql = 'SELECT MAX(sync_time) FROM ' . $this->_syncStateTable . ' WHERE sync_devid = ? AND sync_user = ?';
642: try {
643: return $this->_db->selectValue($sql, array($this->_devId, $this->_deviceInfo->user));
644: } catch (Horde_Db_Exception $e) {
645: throw new Horde_ActiveSync_Exception($e);
646: }
647: }
648:
649: 650: 651: 652: 653: 654:
655: public function addPingCollections($collections)
656: {
657: if (empty($this->_pingState)) {
658: throw new Horde_ActiveSync_Exception('PING state not initialized');
659: }
660: $this->_pingState['collections'] = array();
661: foreach ($collections as $collection) {
662: $this->_pingState['collections'][$collection['class']] = $collection;
663: }
664: }
665:
666: 667: 668: 669: 670: 671: 672: 673: 674:
675: public function loadPingCollectionState($pingCollection)
676: {
677: if (empty($this->_pingState)) {
678: throw new Horde_ActiveSync_Exception('PING state not initialized');
679: }
680: $haveState = false;
681:
682:
683:
684:
685: $this->_logger->debug('[' . $this->_devId . '] Attempting to load PING state for: ' . $pingCollection['class']);
686:
687: if (!empty($this->_pingState['collections'][$pingCollection['class']])) {
688: $this->_collection = $this->_pingState['collections'][$pingCollection['class']];
689: $this->_collection['synckey'] = $this->_devId;
690: if (!$this->_lastSyncTS = $this->_getLastSyncTS()) {
691: throw new Horde_ActiveSync_Exception_StateGone('Previous syncstate has been removed.');
692: }
693: $this->_logger->debug('[' . $this->_devId . '] Obtained last sync time for ' . $pingCollection['class'] . ' - ' . $this->_lastSyncTS);
694: } else {
695:
696: $this->_logger->info('[' . $this->_devId . '] Empty state for '. $pingCollection['class']);
697:
698:
699: $this->_collection = $pingCollection;
700: $this->_collection['synckey'] = $this->_devId;
701:
702:
703: $this->_pingState['collections'][$this->_collection['class']] = $this->_collection;
704: $this->savePingState();
705:
706:
707: if (!$this->_lastSyncTS = $this->_getLastSyncTS()) {
708: throw new Horde_ActiveSync_Exception_InvalidRequest('No previous SYNC found for collection ' . $pingCollection['class']);
709: }
710: }
711: }
712:
713: 714: 715: 716: 717: 718: 719: 720: 721:
722: public function savePingState()
723: {
724: if (empty($this->_pingState)) {
725: throw new Horde_ActiveSync_Exception('PING state not initialized');
726: }
727:
728: if (!empty($this->_collection)) {
729: $this->_pingState['collections'][$this->_collection['class']] = $this->_collection;
730: }
731:
732: $state = serialize(array('lifetime' => $this->_pingState['lifetime'], 'collections' => $this->_pingState['collections']));
733: $query = 'UPDATE ' . $this->_syncUsersTable . ' SET device_ping = ? WHERE device_id = ? AND device_user = ?';
734: $this->_logger->debug(sprintf('Saving PING state: %s', $state));
735: try {
736: return $this->_db->update($query, array($state, $this->_devId, $this->_deviceInfo->user));
737: } catch (Horde_Db_Exception $e) {
738: throw new Horde_ActiveSync_Exception($e);
739: }
740: }
741:
742: 743: 744: 745: 746: 747:
748: public function getHeartbeatInterval()
749: {
750: if (empty($this->_pingState)) {
751: throw new Horde_ActiveSync_Exception('PING state not initialized');
752: }
753:
754: return (!$this->_pingState) ? 0 : $this->_pingState['lifetime'];
755: }
756:
757: 758: 759: 760: 761:
762: public function setHeartbeatInterval($lifetime)
763: {
764: $this->_pingState['lifetime'] = $lifetime;
765: }
766:
767: 768: 769: 770: 771: 772: 773:
774: public function getChanges($flags = 0)
775: {
776:
777: $cutoffdate = self::_getCutOffDate(!empty($this->_collection['filtertype'])
778: ? $this->_collection['filtertype']
779: : 0);
780:
781:
782: $this->_thisSyncTS = time();
783:
784: if (!empty($this->_collection['id'])) {
785: $folderId = $this->_collection['id'];
786: $this->_logger->debug('[' . $this->_devId . '] Initializing message diff engine for ' . $this->_collection['id']);
787:
788: if ($folderId != Horde_ActiveSync::FOLDER_TYPE_DUMMY) {
789:
790:
791: if (!empty($this->_changes)) {
792: $this->_logger->debug('[' . $this->_devId . '] Returning previously found changes.');
793: return $this->_changes;
794: }
795:
796:
797: $changes = $this->_backend->getServerChanges($folderId, (int)$this->_lastSyncTS, (int)$this->_thisSyncTS, $cutoffdate);
798: }
799:
800:
801:
802:
803:
804:
805: $this->_logger->debug('[' . $this->_devId . '] Found ' . count($changes) . ' message changes, checking for PIM initiated changes.');
806: if ($this->_havePIMChanges()) {
807: $this->_changes = array();
808: foreach ($changes as $change) {
809: $stat = $this->_backend->statMessage($folderId, $change['id']);
810: $ts = $this->_getPIMChangeTS($change['id']);
811: if ($ts && $ts >= $stat['mod']) {
812: $this->_logger->debug('[' . $this->_devId . '] Ignoring PIM initiated change for ' . $change['id'] . '(PIM TS: ' . $ts . ' Stat TS: ' . $stat['mod']);
813: } else {
814: $this->_changes[] = $change;
815: }
816: }
817: } else {
818:
819: $this->_logger->debug('[' . $this->_devId . '] No PIM changes present, returning all messages.');
820: $this->_changes = $changes;
821: }
822: } else {
823: $this->_logger->debug('[' . $this->_devId . '] Initializing folder diff engine');
824: $folderlist = $this->_backend->getFolderList();
825: if ($folderlist === false) {
826: return false;
827: }
828:
829: $this->_changes = $this->_getDiff((empty($this->_state) ? array() : $this->_state), $folderlist);
830: $this->_logger->debug('[' . $this->_devId . '] Found ' . count($this->_changes) . ' folder changes');
831: }
832:
833: return $this->_changes;
834: }
835:
836: 837: 838: 839: 840: 841:
842: public function setPolicyKey($devId, $key)
843: {
844: if (empty($this->_deviceInfo) || $devId != $this->_deviceInfo->id) {
845: $this->_logger->err('Device not loaded');
846: throw new Horde_ActiveSync_Exception('Device not loaded');
847: }
848:
849: $query = 'UPDATE ' . $this->_syncUsersTable . ' SET device_policykey = ? WHERE device_id = ? AND device_user = ?';
850: try {
851: $this->_db->update($query, array($key, $devId, $this->_backend->getUser()));
852: } catch (Horde_Db_Exception $e) {
853: throw new Horde_ActiveSync_Exception($e);
854: }
855: }
856:
857: 858: 859: 860: 861: 862: 863: 864:
865: public function resetAllPolicyKeys()
866: {
867: $query = 'UPDATE ' . $this->_syncUsersTable . ' SET device_policykey = 0';
868: try {
869: $this->_db->update($query);
870: } catch (Horde_Db_Exception $e) {
871: throw new Horde_ActiveSync_Exception($e);
872: }
873: }
874:
875: 876: 877: 878: 879: 880: 881: 882: 883:
884: public function setDeviceRWStatus($devId, $status)
885: {
886: $query = 'UPDATE ' . $this->_syncDeviceTable . ' SET device_rwstatus = ?'
887: . ' WHERE device_id = ?';
888: $values = array($status, $devId);
889: try {
890: $this->_db->update($query, $values);
891: } catch (Horde_Db_Exception $e) {
892: throw new Horde_ActiveSync_Exception($e);
893: }
894:
895: if ($status == Horde_ActiveSync::RWSTATUS_PENDING) {
896:
897:
898: $query = 'UPDATE ' . $this->_syncUsersTable
899: . ' SET device_policykey = 0 WHERE device_id = ?';
900: try {
901: $this->_db->update($query, array($devId));
902: } catch (Horde_Db_Exception $e) {
903: throw new Horde_ActiveSync_Exception($e);
904: }
905: }
906: }
907:
908: 909: 910: 911: 912: 913: 914: 915: 916: 917:
918: public function removeState($synckey = null, $devId = null, $user = null)
919: {
920: $state_query = 'DELETE FROM ' . $this->_syncStateTable . ' WHERE';
921: $map_query = 'DELETE FROM ' . $this->_syncMapTable . ' WHERE';
922: if ($devId && $user) {
923: $state_query .= ' sync_devid = ? AND sync_user = ?';
924: $map_query .= ' sync_devid = ? AND sync_user = ?';
925: $user_query = 'DELETE FROM ' . $this->_syncUsersTable . ' WHERE device_id = ? AND device_user = ?';
926: $values = array($devId, $user);
927: $this->_logger->debug('[' . $devId . '] Removing device state for user ' . $user . '.');
928: } elseif ($devId){
929: $state_query .= ' sync_devid = ?';
930: $map_query .= ' sync_devid = ?';
931: $user_query = 'DELETE FROM ' . $this->_syncUsersTable . ' WHERE device_id = ?';
932: $device_query = 'DELETE FROM ' . $this->_syncDeviceTable . ' WHERE device_id = ?';
933: $values = array($devId);
934: $this->_logger->debug('[' . $devId . '] Removing all device state for device ' . $devId . '.');
935: } else {
936: $state_query .= ' sync_key = ?';
937: $map_query .= ' sync_key = ?';
938: $values = array($synckey);
939: $this->_logger->debug('[' . $this->_devId . '] Removing device state for sync_key ' . $synckey . ' only.');
940: }
941:
942: try {
943: $this->_db->delete($state_query, $values);
944: $this->_db->delete($map_query, $values);
945: if (!empty($user_query)) {
946: $this->_db->delete($user_query, $values);
947: }
948: if (!empty($device_query)) {
949: $this->_db->delete($device_query, $values);
950: } elseif (!empty($user_query)) {
951: 952:
953: $sql = 'SELECT COUNT(*) FROM ' . $this->_syncUsersTable . ' WHERE device_id = ?';
954: if (!$this->_db->selectValue($sql, array($devId))) {
955: $query = 'DELETE FROM ' . $this->_syncDeviceTable . ' WHERE device_id = ?';
956: $this->_db->delete($query, array($devId));
957: }
958: }
959: } catch (Horde_Db_Exception $e) {
960: throw new Horde_ActiveSync_Exception($e);
961: }
962: }
963:
964: 965: 966: 967: 968: 969: 970: 971: 972: 973:
974: public function isDuplicatePIMAddition($id)
975: {
976: $sql = 'SELECT message_uid FROM ' . $this->_syncMapTable . ' WHERE sync_clientid = ? AND sync_user = ?';
977: try {
978: $uid = $this->_db->selectValue($sql, array($id, $this->_deviceInfo->user));
979:
980: return $uid;
981: } catch (Horde_Db_Exception $e) {
982: throw new Horde_ActiveSync_Exception($e);
983: }
984: }
985:
986: 987: 988: 989: 990: 991: 992:
993: protected function _getPIMChangeTS($uid)
994: {
995: $sql = 'SELECT sync_modtime FROM ' . $this->_syncMapTable . ' WHERE message_uid = ? AND sync_devid = ? AND sync_user = ?';
996: try {
997: return $this->_db->selectValue($sql, array($uid, $this->_devId, $this->_deviceInfo->user));
998: } catch (Horde_Db_Exception $e) {
999: throw new Horde_ActiveSync_Exception($e);
1000: }
1001: }
1002:
1003: 1004: 1005: 1006: 1007: 1008: 1009: 1010: 1011: 1012: 1013:
1014: protected function _havePIMChanges()
1015: {
1016: $sql = 'SELECT COUNT(*) FROM ' . $this->_syncMapTable . ' WHERE sync_devid = ? AND sync_user = ?';
1017: try {
1018: return (bool)$this->_db->selectValue($sql, array($this->_devId, $this->_deviceInfo->user));
1019: } catch (Horde_Db_Exception $e) {
1020: throw new Horde_ActiveSync_Exception($e);
1021: }
1022: }
1023:
1024: 1025: 1026: 1027: 1028: 1029: 1030: 1031:
1032: protected function _getLastSyncTS($syncKey = 0)
1033: {
1034: $sql = 'SELECT MAX(sync_time) FROM ' . $this->_syncStateTable . ' WHERE sync_folderid = ? AND sync_devid = ?';
1035: $values = array($this->_collection['id'], $this->_devId);
1036: if (!empty($syncKey)) {
1037: $sql .= ' AND sync_key = ?';
1038: array_push($values, $syncKey);
1039: }
1040: try {
1041: $this->_lastSyncTS = $this->_db->selectValue($sql, $values);
1042: } catch (Horde_Db_Exception $e) {
1043: throw new Horde_ActiveSync_Exception($e);
1044: }
1045:
1046: return !empty($this->_lastSyncTS) ? $this->_lastSyncTS : 0;
1047: }
1048:
1049: 1050: 1051: 1052: 1053: 1054: 1055: 1056:
1057: protected function _gc($syncKey)
1058: {
1059: if (!preg_match('/^s{0,1}\{([0-9A-Za-z-]+)\}([0-9]+)$/', $syncKey, $matches)) {
1060: return false;
1061: }
1062: $guid = $matches[1];
1063: $n = $matches[2];
1064:
1065:
1066:
1067:
1068: $sql = 'SELECT sync_key FROM ' . $this->_syncStateTable
1069: . ' WHERE sync_devid = ? AND sync_folderid = ?';
1070: $values = array(
1071: $this->_devId,
1072: !empty($this->_collection['id']) ?
1073: $this->_collection['id'] :
1074: 'foldersync');
1075:
1076: $results = $this->_db->selectAll($sql, $values);
1077: $remove = array();
1078: $guids = array($guid);
1079: foreach ($results as $oldkey) {
1080: if (preg_match('/^s{0,1}\{([0-9A-Za-z-]+)\}([0-9]+)$/', $oldkey['sync_key'], $matches)) {
1081: if ($matches[1] == $guid && $matches[2] < $n) {
1082: $remove[] = $oldkey['sync_key'];
1083: }
1084: } else {
1085:
1086: $remove[] = $oldkey['sync_key'];
1087: $guids[] = $matches[1];
1088: }
1089: }
1090: if (count($remove)) {
1091: $sql = 'DELETE FROM ' . $this->_syncStateTable . ' WHERE sync_key IN ('
1092: . str_repeat('?,', count($remove) - 1) . '?)';
1093: $this->_db->delete($sql, $remove);
1094: }
1095:
1096:
1097:
1098:
1099: $sql = 'SELECT sync_key FROM ' . $this->_syncMapTable
1100: . ' WHERE sync_devid = ? AND sync_user = ?';
1101: $maps = $this->_db->selectValues($sql, array($this->_devId, $this->_deviceInfo->user));
1102: foreach ($maps as $key) {
1103: if (preg_match('/^s{0,1}\{([0-9A-Za-z-]+)\}([0-9]+)$/', $key, $matches)) {
1104: if ($matches[1] == $guid && $matches[2] < $n) {
1105: $remove[] = $key;
1106: }
1107: }
1108: }
1109: if (count($remove)) {
1110: $sql = 'DELETE FROM ' . $this->_syncMapTable . ' WHERE sync_key IN ('
1111: . str_repeat('?,', count($remove) - 1) . '?)';
1112: $this->_db->delete($sql, $remove);
1113: }
1114:
1115: return true;
1116: }
1117:
1118: 1119: 1120: 1121: 1122: 1123: 1124: 1125:
1126: protected function _resetDeviceState($id)
1127: {
1128: $this->_logger->debug('[' . $this->_devId . '] Resetting device state.');
1129: $state_query = 'DELETE FROM ' . $this->_syncStateTable . ' WHERE sync_devid = ? AND sync_folderid = ?';
1130: $map_query = 'DELETE FROM ' . $this->_syncMapTable . ' WHERE sync_devid = ? AND sync_folderid = ?';
1131: $user = 'DELETE FROM ' . $this->_syncUsersTable . ' WHERE device_id = ? AND device_user = ?';
1132: try {
1133: $this->_db->delete($state_query, array($this->_devId, $id));
1134: $this->_db->delete($map_query, array($this->_devId, $id));
1135: $this->_db->delete($user, array($this->_devId, $this->_devInfo->user));
1136: } catch (Horde_Db_Exception $e) {
1137: throw new Horde_ActiveSync_Exception($e);
1138: }
1139: }
1140:
1141: }