1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10:
11: class Horde_Core_ActiveSync_Driver extends Horde_ActiveSync_Driver_Base
12: {
13:
14: const APPOINTMENTS_FOLDER = 'Calendar';
15: const CONTACTS_FOLDER = 'Contacts';
16: const TASKS_FOLDER = 'Tasks';
17: const FOLDER_INBOX = 'Inbox';
18:
19: 20: 21: 22: 23:
24: private $_modCache;
25:
26: 27: 28: 29: 30:
31: private $_connector;
32:
33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44:
45: public function __construct($params = array())
46: {
47: parent::__construct($params);
48: if (empty($this->_params['connector']) || !($this->_params['connector'] instanceof Horde_Core_ActiveSync_Connector)) {
49: throw new InvalidArgumentException('Missing required connector object.');
50: }
51:
52: if (empty($this->_params['auth']) || !($this->_params['auth'] instanceof Horde_Auth_Base)) {
53: throw new InvalidArgumentException('Missing required Auth object');
54: }
55:
56: $this->_connector = $params['connector'];
57: $this->_auth = $params['auth'];
58: }
59:
60: 61: 62: 63: 64:
65: public function logon($username, $password, $domain = null)
66: {
67: $this->_logger->info('Horde_ActiveSync_Driver_Horde::logon attempt for: ' . $username);
68: parent::logon($username, $password, $domain);
69:
70: return $this->_auth->authenticate($username, array('password' => $password));
71: }
72:
73: 74: 75: 76: 77:
78: public function logOff()
79: {
80: $this->_connector->clearAuth();
81: $this->_logger->info('User ' . $this->_user . ' logged off');
82: return true;
83: }
84:
85: 86: 87: 88: 89: 90: 91: 92: 93:
94: public function setup($user)
95: {
96: parent::setup($user);
97: $this->_modCache = array();
98: return true;
99: }
100:
101: 102: 103: 104: 105:
106: public function getWasteBasket()
107: {
108: $this->_logger->debug('Horde::getWasteBasket()');
109:
110: return false;
111: }
112:
113: 114: 115: 116: 117:
118: public function getFolderList()
119: {
120: ob_start();
121:
122: $this->_logger->debug('Horde::getFolderList()');
123:
124: try {
125: $supported = $this->_connector->horde_listApis();
126: } catch (Exception $e) {
127: $this->_logger->err($e->getMessage());
128: $this->_endBuffer();
129: return array();
130: }
131: $folders = array();
132:
133: if (array_search('calendar', $supported)) {
134: $folders[] = $this->statFolder(self::APPOINTMENTS_FOLDER);
135: }
136:
137: if (array_search('contacts', $supported)) {
138: $folders[] = $this->statFolder(self::CONTACTS_FOLDER);
139: }
140:
141: if (array_search('tasks', $supported)) {
142: $folders[] = $this->statFolder(self::TASKS_FOLDER);
143: }
144:
145:
146:
147: $folders[] = $this->statFolder(self::FOLDER_INBOX);
148:
149: if ($errors = Horde::endBuffer()) {
150: $this->_logger->err('Unexpected output: ' . $errors);
151: }
152: $this->_endBuffer();
153:
154: return $folders;
155: }
156:
157: 158: 159: 160: 161: 162: 163:
164: public function getFolder($id)
165: {
166: $this->_logger->debug('Horde::getFolder(' . $id . ')');
167:
168: $folder = new Horde_ActiveSync_Message_Folder();
169: $folder->serverid = $id;
170: $folder->parentid = "0";
171: $folder->displayname = $id;
172:
173: switch ($id) {
174: case self::APPOINTMENTS_FOLDER:
175: $folder->type = Horde_ActiveSync::FOLDER_TYPE_APPOINTMENT;
176: break;
177: case self::CONTACTS_FOLDER:
178: $folder->type = Horde_ActiveSync::FOLDER_TYPE_CONTACT;
179: break;
180: case self::TASKS_FOLDER:
181: $folder->type = Horde_ActiveSync::FOLDER_TYPE_TASK;
182: break;
183: case self::FOLDER_INBOX:
184: $folder->type = Horde_ActiveSync::FOLDER_TYPE_INBOX;
185: break;
186: default:
187: return false;
188: }
189:
190: return $folder;
191: }
192:
193: 194: 195: 196: 197: 198: 199: 200:
201: public function statFolder($id)
202: {
203: $this->_logger->debug('Horde::statFolder(' . $id . ')');
204:
205: $folder = array();
206: $folder['id'] = $id;
207: $folder['mod'] = $id;
208: $folder['parent'] = 0;
209:
210: return $folder;
211: }
212:
213: 214: 215: 216: 217:
218: public function getMessageList($folderid, $cutoffdate)
219: {
220: $this->_logger->debug('Horde::getMessageList(' . $folderid . ', ' . $cutoffdate . ')');
221:
222: ob_start();
223: $messages = array();
224: switch ($folderid) {
225: case self::APPOINTMENTS_FOLDER:
226: $startstamp = (int)$cutoffdate;
227: $endstamp = time() + 32140800;
228:
229: try {
230: $events = $this->_connector->calendar_listUids($startstamp, $endstamp);
231: } catch (Horde_Exception $e) {
232: $this->_logger->err($e->getMessage());
233: $this->_endBuffer();
234: return array();
235: }
236: foreach ($events as $uid) {
237: $messages[] = $this->_smartStatMessage($folderid, $uid, false);
238: }
239: break;
240:
241: case self::CONTACTS_FOLDER:
242: try {
243: $contacts = $this->_connector->contacts_listUids();
244: } catch (Horde_Exception $e) {
245: $this->_logger->err($e->getMessage());
246: $this->_endBuffer();
247: return array();
248: }
249:
250: foreach ($contacts as $contact) {
251: $messages[] = $this->_smartStatMessage($folderid, $contact, false);
252: }
253: break;
254:
255: case self::TASKS_FOLDER:
256: try {
257: $tasks = $this->_connector->tasks_listUids();
258: } catch (Horde_Exception $e) {
259: $this->_logger->err($e->getMessage());
260: $this->_endBuffer();
261: return array();
262: }
263: foreach ($tasks as $task) {
264: $messages[] = $this->_smartStatMessage($folderid, $task, false);
265: }
266: break;
267:
268: default:
269: $this->_endBuffer();
270: return array();
271: }
272: $this->_endBuffer();
273:
274: return $messages;
275: }
276:
277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288:
289: public function getServerChanges($folderId, $from_ts, $to_ts, $cutoffdate)
290: {
291: $this->_logger->debug("Horde_ActiveSync_Driver_Horde::getServerChanges($folderId, $from_ts, $to_ts, $cutoffdate)");
292:
293: $changes = array(
294: 'add' => array(),
295: 'delete' => array(),
296: 'modify' => array()
297: );
298:
299: ob_start();
300: switch ($folderId) {
301: case self::APPOINTMENTS_FOLDER:
302: if ($from_ts == 0) {
303:
304: $startstamp = (int)$cutoffdate;
305: $endstamp = time() + 32140800;
306: try {
307: $changes['add'] = $this->_connector->calendar_listUids($startstamp, $endstamp);
308: } catch (Horde_Exception $e) {
309: $this->_logger->err($e->getMessage());
310: $this->_endBuffer();
311: return array();
312: }
313: } else {
314: try {
315: $changes = $this->_connector->getChanges('calendar', $from_ts, $to_ts);
316: } catch (Horde_Exception $e) {
317: $this->_logger->err($e->getMessage());
318: $this->_endBuffer();
319: return array();
320: }
321: }
322: break;
323:
324: case self::CONTACTS_FOLDER:
325:
326: if ($from_ts == 0) {
327: try {
328: $changes['add'] = $this->_connector->contacts_listUids();
329: } catch (Horde_Exception $e) {
330: $this->_logger->err($e->getMessage());
331: $this->_endBuffer();
332: return array();
333: }
334: $edits = $deletes = array();
335: } else {
336: try {
337: $changes = $this->_connector->getChanges('contacts', $from_ts, $to_ts);
338: } catch (Horde_Exception $e) {
339: $this->_logger->err($e->getMessage());
340: $this->_endBuffer();
341: return array();
342: }
343: }
344: break;
345:
346: case self::TASKS_FOLDER:
347:
348: if ($from_ts == 0) {
349: try {
350: $changes['add'] = $this->_connector->tasks_listUids();
351: } catch (Horde_Exception $e) {
352: $this->_logger->err($e->getMessage());
353: $this->_endBuffer();
354: return array();
355: }
356: } else {
357: try {
358: $changes = $this->_connector->getChanges('tasks', $from_ts, $to_ts);
359: } catch (Horde_Exception $e) {
360: $this->_logger->err($e->getMessage());
361: $this->_endBuffer();
362: return array();
363: }
364: }
365: break;
366: }
367:
368: $results = array();
369:
370:
371: foreach ($changes['add'] as $add) {
372: $results[] = array(
373: 'id' => $add,
374: 'type' => 'change',
375: 'flags' => Horde_ActiveSync::FLAG_NEWMESSAGE);
376: }
377:
378:
379: foreach ($changes['modify'] as $change) {
380: $results[] = array(
381: 'id' => $change,
382: 'type' => 'change');
383: }
384:
385:
386: foreach ($changes['delete'] as $deleted) {
387: $results[] = array(
388: 'id' => $deleted,
389: 'type' => 'delete');
390: }
391: $this->_endBuffer();
392:
393: return $results;
394: }
395:
396: 397: 398: 399: 400:
401: public function getMessage($folderid, $id, $truncsize, $mimesupport = 0)
402: {
403: $this->_logger->debug('Horde::getMessage(' . $folderid . ', ' . $id . ')');
404: ob_start();
405: $message = false;
406: switch ($folderid) {
407: case self::APPOINTMENTS_FOLDER:
408: try {
409: $message = $this->_connector->calendar_export($id);
410:
411: if (!$message->getUid()) {
412: $message->setUid($id);
413: }
414: } catch (Horde_Exception $e) {
415: $this->_logger->err($e->getMessage());
416: $this->_endBuffer();
417: return false;
418: }
419: break;
420:
421: case self::CONTACTS_FOLDER:
422: try {
423: $message = $this->_connector->contacts_export($id);
424: } catch (Horde_Exception $e) {
425: $this->_logger->err($e->getMessage());
426: $this->_endBuffer();
427: return false;
428: }
429: break;
430:
431: case self::TASKS_FOLDER:
432: try {
433: $message = $this->_connector->tasks_export($id);
434: } catch (Horde_Exception $e) {
435: $this->_logger->err($e->getMessage());
436: $this->_endBuffer();
437: return false;
438: }
439: break;
440: default:
441: $this->_endBuffer();
442: return false;
443: }
444: if (strlen($message->body) > $truncsize) {
445: $message->body = self::truncate($message->body, $truncsize);
446: $message->bodytruncated = 1;
447: } else {
448:
449: $message->bodytruncated = 0;
450: }
451:
452: $this->_endBuffer();
453: return $message;
454: }
455:
456: 457: 458: 459: 460:
461: public function statMessage($folderid, $id)
462: {
463: return $this->_smartStatMessage($folderid, $id, true);
464: }
465:
466: 467: 468: 469: 470:
471: public function deleteMessage($folderid, $id)
472: {
473: $this->_logger->debug('Horde::deleteMessage(' . $folderid . ', ' . $id . ')');
474: ob_start();
475: $status = false;
476: switch ($folderid) {
477: case self::APPOINTMENTS_FOLDER:
478: try {
479: $status = $this->_connector->calendar_delete($id);
480: } catch (Horde_Exception $e) {
481: $this->_logger->err($e->getMessage());
482: $this->_endBuffer();
483: return false;
484: }
485: break;
486:
487: case self::CONTACTS_FOLDER:
488: try {
489: $status = $this->_connector->contacts_delete($id);
490: } catch (Horde_Exception $e) {
491: $this->_logger->err($e->getMessage());
492: $this->_endBuffer();
493: return false;
494: }
495: break;
496:
497: case self::TASKS_FOLDER:
498: try {
499: $status = $this->_connector->tasks_delete($id);
500: } catch (Horde_Exception $e) {
501: $this->_logger->err($e->getMessage());
502: $this->_endBuffer();
503: return false;
504: }
505: break;
506: default:
507: $this->_endBuffer();
508: return false;
509: }
510:
511: $this->_endBuffer();
512: return $status;
513: }
514:
515: 516: 517: 518: 519: 520: 521: 522: 523: 524: 525: 526:
527: public function changeMessage($folderid, $id, $message, $device)
528: {
529: $this->_logger->debug('Horde::changeMessage(' . $folderid . ', ' . $id . ')');
530: ob_start();
531: $stat = false;
532: switch ($folderid) {
533: case self::APPOINTMENTS_FOLDER:
534: if (!$id) {
535: try {
536: $id = $this->_connector->calendar_import($message);
537: } catch (Horde_Exception $e) {
538: $this->_logger->err($e->getMessage());
539: $this->_endBuffer();
540: return false;
541: }
542:
543:
544: $stat = $this->_smartStatMessage($folderid, $id, false);
545: $stat['mod'] = time();
546: } else {
547:
548:
549: $message->setServerUID($id);
550: if (!empty($device->supported[self::APPOINTMENTS_FOLDER])) {
551: $message->setSupported($device->supported[self::APPOINTMENTS_FOLDER]);
552: }
553: try {
554: $this->_connector->calendar_replace($id, $message);
555: } catch (Horde_Exception $e) {
556: $this->_logger->err($e->getMessage());
557: $this->_endBuffer();
558: return false;
559: }
560: $stat = $this->_smartStatMessage($folderid, $id, false);
561: }
562: break;
563:
564: case self::CONTACTS_FOLDER:
565: if (!$id) {
566: try {
567: $id = $this->_connector->contacts_import($message);
568: } catch (Horde_Exception $e) {
569: $this->_logger->err($e->getMessage());
570: $this->_endBuffer();
571: return false;
572: }
573: $stat = $this->_smartStatMessage($folderid, $id, false);
574: $stat['mod'] = time();
575: } else {
576: if (!empty($device->supported[self::CONTACTS_FOLDER])) {
577: $message->setSupported($device->supported[self::CONTACTS_FOLDER]);
578: }
579: try {
580: $this->_connector->contacts_replace($id, $message);
581: } catch (Horde_Exception $e) {
582: $this->_logger->err($e->getMessage());
583: $this->_endBuffer();
584: return false;
585: }
586: $stat = $this->_smartStatMessage($folderid, $id, false);
587: }
588: break;
589:
590: case self::TASKS_FOLDER:
591: if (!$id) {
592: try {
593: $id = $this->_connector->tasks_import($message);
594: } catch (Horde_Exception $e) {
595: $this->_logger->err($e->getMessage());
596: $this->_endBuffer();
597: return false;
598: }
599: $stat = $this->_smartStatMessage($folderid, $id, false);
600: $stat['mod'] = time();
601: } else {
602: if (!empty($device->supported[self::TASKS_FOLDER])) {
603: $message->setSupported($device->supported[self::TASKS_FOLDER]);
604: }
605: try {
606: $this->_connector->tasks_replace($id, $message);
607: } catch (Horde_Exception $e) {
608: $this->_logger->err($e->getMessage());
609: $this->_endBuffer();
610: return false;
611: }
612: $stat = $this->_smartStatMessage($folderid, $id, false);
613: }
614: break;
615:
616: default:
617: $this->_endBuffer();
618: return false;
619: }
620:
621: $this->_endBuffer();
622: return $stat;
623: }
624:
625: 626: 627: 628: 629: 630: 631: 632: 633: 634: 635:
636: public function getSearchResults($query, $range)
637: {
638: $return = array('rows' => array(),
639: 'range' => $range);
640:
641: ob_start();
642: try {
643: $results = $this->_connector->contacts_search($query);
644: } catch (Horde_ActiveSync_Exception $e) {
645: $this->_logger->err($e->getMessage());
646: $this->_endBuffer();
647: return $return;
648: }
649:
650:
651: $count = count($results);
652: if (!$count) {
653: return $return;
654: }
655: $this->_logger->info('Horde::getSearchResults found ' . $count . ' matches.');
656:
657: preg_match('/(.*)\-(.*)/', $range, $matches);
658: $return_count = $matches[2] - $matches[1];
659: $rows = array_slice($results, $matches[1], $return_count + 1, true);
660: $rows = array_pop($rows);
661: foreach ($rows as $row) {
662: $return['rows'][] = array(
663: Horde_ActiveSync::GAL_ALIAS => !empty($row['alias']) ? $row['alias'] : '',
664: Horde_ActiveSync::GAL_DISPLAYNAME => $row['name'],
665: Horde_ActiveSync::GAL_EMAILADDRESS => !empty($row['email']) ? $row['email'] : '',
666: Horde_ActiveSync::GAL_FIRSTNAME => $row['firstname'],
667: Horde_ActiveSync::GAL_LASTNAME => $row['lastname'],
668: Horde_ActiveSync::GAL_COMPANY => !empty($row['company']) ? $row['company'] : '',
669: Horde_ActiveSync::GAL_HOMEPHONE => !empty($row['homePhone']) ? $row['homePhone'] : '',
670: Horde_ActiveSync::GAL_PHONE => !empty($row['workPhone']) ? $row['workPhone'] : '',
671: Horde_ActiveSync::GAL_MOBILEPHONE => !empty($row['cellPhone']) ? $row['cellPhone'] : '',
672: Horde_ActiveSync::GAL_TITLE => !empty($row['title']) ? $row['title'] : '',
673: );
674: }
675:
676: $this->_endBuffer();
677: return $return;
678: }
679:
680: 681: 682: 683: 684: 685: 686: 687: 688: 689: 690:
691: public function sendMail($rfc822, $forward = false, $reply = false, $parent = false)
692: {
693: $headers = Horde_Mime_Headers::parseHeaders($rfc822);
694: $message = Horde_Mime_Part::parseMessage($rfc822);
695:
696:
697:
698: $ident = $GLOBALS['injector']
699: ->getInstance('Horde_Core_Factory_Identity')
700: ->create($this->_user);
701: $name = $ident->getValue('fullname');
702: $from_addr = $ident->getValue('from_addr');
703:
704: $mail = new Horde_Mime_Mail();
705: $mail->addHeaders($headers->toArray());
706: $mail->addHeader('From', $name . '<' . $from_addr . '>');
707:
708: $body_id = $message->findBody();
709: if ($body_id) {
710: $part = $message->getPart($body_id);
711: $body = $part->getContents();
712: $mail->setBody($body);
713: } else {
714: $mail->setBody('No body?');
715: }
716:
717: foreach ($message->contentTypeMap() as $id => $type) {
718: $mail->addPart($type, $message->getPart($id)->toString());
719: }
720:
721: $mail->send($GLOBALS['injector']->getInstance('Horde_Mail'));
722:
723: return true;
724: }
725:
726: 727: 728: 729: 730: 731: 732: 733:
734: private function _smartStatMessage($folderid, $id, $hint)
735: {
736: ob_start();
737: $this->_logger->debug('ActiveSync_Driver_Horde::_smartStatMessage:' . $folderid . ':' . $id);
738: $statKey = $folderid . $id;
739: $mod = false;
740:
741: if ($hint && isset($this->_modCache[$statKey])) {
742: $mod = $this->_modCache[$statKey];
743: } else {
744: try {
745: switch ($folderid) {
746: case self::APPOINTMENTS_FOLDER:
747: $mod = $this->_connector->calendar_getActionTimestamp($id, 'modify');
748: break;
749:
750: case self::CONTACTS_FOLDER:
751: $mod = $this->_connector->contacts_getActionTimestamp($id, 'modify');
752: break;
753:
754: case self::TASKS_FOLDER:
755: $mod = $this->_connector->tasks_getActionTimestamp($id, 'modify');
756: break;
757:
758: default:
759: $this->_endBuffer();
760: return false;
761: }
762: } catch (Horde_Exception $e) {
763: $this->_logger->err($e->getMessage());
764: $this->_endBuffer();
765: return array('id' => '', 'mod' => 0, 'flags' => 1);
766: }
767: $this->_modCache[$statKey] = $mod;
768: }
769:
770: $message = array();
771: $message['id'] = $id;
772: $message['mod'] = $mod;
773: $message['flags'] = 1;
774:
775: $this->_endBuffer();
776: return $message;
777: }
778:
779: private function _endBuffer()
780: {
781: if ($output = ob_get_clean()) {
782: $this->_logger->err('Unexpected output: ' . $output);
783: }
784: }
785:
786: }
787: