1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18:
19: class Horde_SyncMl_Device_sync4j extends Horde_SyncMl_Device
20: {
21: 22: 23:
24: public function convertClient2Server($content, $contentType)
25: {
26: list($content, $contentType) =
27: parent::convertClient2Server($content, $contentType);
28:
29: switch ($contentType) {
30: case 'text/x-s4j-sifn' :
31: case 'text/x-sifn' :
32: $content = Horde_SyncMl_Device_sync4j::sif2vnote($content);
33: $contentType = 'text/x-vnote';
34: break;
35:
36: case 'text/x-s4j-sifc' :
37: case 'text/x-sifc' :
38: $content = Horde_SyncMl_Device_sync4j::sif2vcard($content);
39: $contentType = 'text/x-vcard';
40: break;
41:
42: case 'text/x-s4j-sife' :
43: case 'text/x-sife' :
44: $content = Horde_SyncMl_Device_sync4j::sif2vevent($content);
45: $contentType = 'text/calendar';
46: break;
47:
48: case 'text/x-s4j-sift' :
49: case 'text/x-sift' :
50: $content = Horde_SyncMl_Device_sync4j::sif2vtodo($content);
51: $contentType = 'text/calendar';
52: break;
53:
54: case 'text/calendar':
55: case 'text/x-vcalendar':
56: $si = $GLOBALS['backend']->state->sourceURI;
57: if (stristr($si, 'fol-') !== false) {
58:
59:
60:
61:
62: $content = preg_replace('/^STATUS:.*\r?\n/im', '', $content);
63: }
64: break;
65: }
66:
67: $GLOBALS['backend']->logFile(
68: Horde_SyncMl_Backend::LOGFILE_DATA,
69: "\nInput converted for server ($contentType):\n$content\n");
70:
71: return array($content, $contentType);
72: }
73:
74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89:
90: public function convertServer2Client($content, $contentType, $database)
91: {
92: $database = $GLOBALS['backend']->normalize($database);
93:
94: list($content, $contentType, $encodingType) =
95: parent::convertServer2Client($content, $contentType, $database);
96:
97: if ($this->requestedContentType == $contentType) {
98: if ($contentType == 'text/calendar' ||
99: $contentType == 'text/x-vcalendar') {
100: $si = $GLOBALS['backend']->state->sourceURI;
101: if (stristr($si, 'fol-') !== false) {
102:
103:
104:
105:
106: $content = preg_replace('/^STATUS:.*\r?\n/im', '', $content);
107: }
108: }
109: return array($content, $contentType, $encodingType);
110: }
111:
112: switch ($contentType) {
113: case 'text/calendar' :
114: case 'text/x-vcalendar' :
115: switch($database) {
116: case 'calendar':
117: $content = Horde_SyncMl_Device_sync4j::vevent2sif($content);
118: $content = base64_encode($content);
119: $contentType = 'text/x-s4j-sife';
120: break 2;
121: case 'tasks':
122: $content = Horde_SyncMl_Device_sync4j::vtodo2sif($content);
123: $content = base64_encode($content);
124: $contentType = 'text/x-s4j-sift';
125: break 2;
126: }
127: break;
128:
129: case 'text/x-vcard' :
130: $content = Horde_SyncMl_Device_sync4j::vcard2sif($content);
131: $content = base64_encode($content);
132: $contentType = 'text/x-s4j-sifc';
133: break;
134:
135: case 'text/x-vnote':
136: case 'text/plain':
137: $content = Horde_SyncMl_Device_sync4j::vnote2sif($content);
138: $content = base64_encode($content);
139: $contentType = 'text/x-s4j-sifn';
140: break;
141: }
142:
143: $l = "\nOutput converted for client ($contentType):\n" . base64_decode($content) . "\n";
144: $GLOBALS['backend']->logFile(Horde_SyncMl_Backend::LOGFILE_DATA, $l);
145:
146: return array($content, $contentType, 'b64');
147: }
148:
149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162:
163: public function sif2array($sif)
164: {
165: $r = array();
166: if (preg_match_all('/<([^>]*)>([^<]*)<\/\1>/si', $sif, $matches, PREG_SET_ORDER)) {
167: foreach ($matches as $match) {
168: if (isset($r[$match[1]])) {
169: if (!is_array($r[$match[1]])) {
170: $r[$match[1]] = array($r[$match[1]]);
171: }
172: $r[$match[1]][] = html_entity_decode($match[2]);
173: } else {
174: $r[$match[1]] = html_entity_decode($match[2]);
175: }
176: }
177: }
178: return $r;
179: }
180:
181: 182: 183: 184: 185: 186: 187: 188: 189:
190: public function array2sif($array, $pre = '', $post = '')
191: {
192: $xml = $pre;
193: foreach ($array as $key => $value) {
194: if (is_a($value, 'PEAR_Error')) {
195: continue;
196: }
197: if (is_array($value)) {
198: if (is_array($value[0])) {
199: $subxml = '';
200: foreach ($value as $val) {
201: $subkey = key($val);
202: $subxml .= '<' . $subkey . '>'
203: . htmlspecialchars($val[$subkey])
204: . '</' . $subkey . '>';
205: }
206: $xml .= '<' . $key . '>' . $subxml . '</' . $key . '>';
207: continue;
208: }
209: $value = $value[0];
210: }
211: $xml .= '<' . $key . '>' . htmlspecialchars($value) . '</' . $key . '>';
212: }
213: return $xml . $post;
214: }
215:
216: public function sif2vnote($sif)
217: {
218: $a = Horde_SyncMl_Device_sync4j::sif2array($sif);
219:
220: $iCal = new Horde_Icalendar();
221: $iCal->setAttribute('VERSION', '1.1');
222: $iCal->setAttribute('PRODID', '-//The Horde Project//SyncML//EN');
223: $iCal->setAttribute('METHOD', 'PUBLISH');
224:
225: $vnote = Horde_Icalendar::newComponent('vnote', $iCal);
226: $vnote->setAttribute('BODY', isset($a['Body']) ? $a['Body'] : '');
227: if (isset($a['Subject'])) {
228: $vnote->setAttribute('SUMMARY', $a['Subject']);
229: }
230: if (isset($a['Categories'])) {
231: $vnote->setAttribute('CATEGORIES', $a['Categories']);
232: }
233:
234: return $vnote->exportvCalendar();
235: }
236:
237: public function sif2vcard($sif)
238: {
239: $a = Horde_SyncMl_Device_sync4j::sif2array($sif);
240:
241: $iCal = new Horde_Icalendar();
242: $iCal->setAttribute('VERSION', '3.0');
243: $iCal->setAttribute('PRODID', '-//The Horde Project//SyncML//EN');
244: $iCal->setAttribute('METHOD', 'PUBLISH');
245:
246: $vcard = Horde_Icalendar::newComponent('vcard', $iCal);
247:
248: $map = array(
249: 'FileAs' => array('FN'),
250: 'NickName' => array('NICKNAME'),
251: 'HomeTelephoneNumber' => array('TEL', array('TYPE' => 'HOME')),
252: 'Home2TelephoneNumber' => array('TEL', array('TYPE' => 'HOME')),
253: 'HomeFaxNumber' => array('TEL', array('TYPE' => 'HOME')),
254: 'BusinessTelephoneNumber' => array('TEL', array('TYPE' => 'WORK')),
255: 'Business2TelephoneNumber' => array('TEL', array('TYPE' => 'WORK')),
256: 'BusinessFaxNumber' => array('TEL', array('TYPE' => 'FAX')),
257: 'PrimaryTelephoneNumber' => array('TEL', array('TYPE' => 'PREF')),
258: 'MobileTelephoneNumber' => array('TEL', array('TYPE' => 'CELL')),
259: 'CarTelephoneNumber' => array('TEL', array('TYPE' => 'CAR')),
260: 'PagerNumber' => array('TEL', array('TYPE' => 'PAGER')),
261: 'OtherTelephoneNumber' => array('TEL'),
262: 'OtherFaxNumber' => array('TEL'),
263: 'Email1Address' => array('EMAIL'),
264: 'Email2Address' => array('EMAIL', array('TYPE' => 'HOME')),
265: 'Email3Address' => array('EMAIL', array('TYPE' => 'WORK')),
266: 'HomeLabel' => array('LABEL', array('TYPE' => 'HOME')),
267: 'BusinessLabel' => array('LABEL', array('TYPE' => 'WORK')),
268: 'OtherLabel' => array('LABEL'),
269: 'Profession' => array('ROLE'),
270: 'JobTitle' => array('TITLE'),
271: 'Body' => array('NOTE'),
272: 'WebPage' => array('URL'),
273: 'Birthday' => array('BDAY'),
274: 'Categories' => array('CATEGORIES'),
275: 'Timezone' => array('TZ'),
276: 'Anniversary' => array('X-ANNIVERSARY'),
277: 'Spouse' => array('X-SPOUSE'),
278: 'Children' => array('X-CHILDREN'),
279: );
280: foreach ($map as $sif_value => $vcard_value) {
281: if (isset($a[$sif_value])) {
282: $vcard->setAttribute($vcard_value[0],
283: $a[$sif_value],
284: isset($vcard_value[1]) ? $vcard_value[1] : array());
285: }
286: }
287:
288: $map = array(
289: array(
290: 'N',
291: array(Horde_Icalendar_Vcard::N_FAMILY => 'LastName',
292: Horde_Icalendar_Vcard::N_GIVEN => 'FirstName',
293: Horde_Icalendar_Vcard::N_ADDL => 'MiddleName',
294: Horde_Icalendar_Vcard::N_PREFIX => 'Title',
295: Horde_Icalendar_Vcard::N_SUFFIX => 'Suffix'),
296: array(),
297: false),
298: array(
299: 'ADR',
300: array(Horde_Icalendar_Vcard::ADR_POB => 'HomeAddressPostOfficeBox',
301: Horde_Icalendar_Vcard::ADR_EXTEND => '',
302: Horde_Icalendar_Vcard::ADR_STREET => 'HomeAddressStreet',
303: Horde_Icalendar_Vcard::ADR_LOCALITY => 'HomeAddressCity',
304: Horde_Icalendar_Vcard::ADR_REGION => 'HomeAddressState',
305: Horde_Icalendar_Vcard::ADR_POSTCODE => 'HomeAddressPostalCode',
306: Horde_Icalendar_Vcard::ADR_COUNTRY => 'HomeAddressCountry'),
307: array('TYPE' => 'HOME'),
308: true),
309: array(
310: 'ADR',
311: array(Horde_Icalendar_Vcard::ADR_POB => 'BusinessAddressPostOfficeBox',
312: Horde_Icalendar_Vcard::ADR_EXTEND => '',
313: Horde_Icalendar_Vcard::ADR_STREET => 'BusinessAddressStreet',
314: Horde_Icalendar_Vcard::ADR_LOCALITY => 'BusinessAddressCity',
315: Horde_Icalendar_Vcard::ADR_REGION => 'BusinessAddressState',
316: Horde_Icalendar_Vcard::ADR_POSTCODE => 'BusinessAddressPostalCode',
317: Horde_Icalendar_Vcard::ADR_COUNTRY => 'BusinessAddressCountry'),
318: array('TYPE' => 'WORK'),
319: true),
320: array(
321: 'ADR',
322: array(Horde_Icalendar_Vcard::ADR_POB => 'OtherAddressPostOfficeBox',
323: Horde_Icalendar_Vcard::ADR_EXTEND => '',
324: Horde_Icalendar_Vcard::ADR_STREET => 'OtherAddressStreet',
325: Horde_Icalendar_Vcard::ADR_LOCALITY => 'OtherAddressCity',
326: Horde_Icalendar_Vcard::ADR_REGION => 'OtherAddressState',
327: Horde_Icalendar_Vcard::ADR_POSTCODE => 'OtherAddressPostalCode',
328: Horde_Icalendar_Vcard::ADR_COUNTRY => 'OtherAddressCountry'),
329: array(),
330: true),
331: );
332: foreach ($map as $struct) {
333: $values = array();
334: foreach ($struct[1] as $vcard_value => $sif_value) {
335: $values[$vcard_value] = isset($a[$sif_value]) ? $a[$sif_value] : '';
336: }
337: $check = array_flip($values);
338: if (count($check) > 1 || strlen(key($check))) {
339: $vcard->setAttribute($struct[0],
340: implode(';', $values),
341: $struct[2],
342: $struct[3],
343: $values);
344: }
345: }
346:
347: $org = array();
348: if (isset($a['CompanyName'])) {
349: $org[] = $a['CompanyName'];
350: if (isset($a['Department'])) {
351: $org[] = $a['Department'];
352: }
353: }
354: if (count($org)) {
355: $vcard->setAttribute('ORG', null, array(), true, $org);
356: }
357:
358: return $vcard->exportvCalendar();
359: }
360:
361: public function sif2vevent($sif)
362: {
363: $a = Horde_SyncMl_Device_sync4j::sif2array($sif);
364:
365: $iCal = new Horde_Icalendar();
366: $iCal->setAttribute('PRODID', '-//The Horde Project//SyncML//EN');
367: $iCal->setAttribute('METHOD', 'PUBLISH');
368:
369: $vEvent = Horde_Icalendar::newComponent('vevent', $iCal);
370: $vEvent->setAttribute('DTSTAMP', time());
371:
372: $map = array('Subject' => 'SUMMARY',
373: 'Body' => 'DESCRIPTION',
374: 'Categories' => 'CATEGORIES',
375: 'Location' => 'LOCATION');
376: foreach ($map as $source => $target) {
377: if (!empty($a[$source])) {
378: $vEvent->setAttribute($target, $a[$source]);
379: }
380: }
381:
382: if ($a['AllDayEvent'] == 1) {
383:
384:
385:
386: $startTime = $iCal->_parseDateTime($a['Start']);
387: $vEvent->setAttribute('DTSTART',
388: array('year' => date('Y', $startTime),
389: 'month' => date('m', $startTime),
390: 'mday' => date('d', $startTime)),
391: array('VALUE' => 'DATE'));
392: $t = $iCal->_parseDateTime($a['End']);
393: $d = new Horde_Date(array('year' => date('Y', $t),
394: 'month' => date('m', $t),
395: 'mday' => date('d', $t) + 1));
396: $vEvent->setAttribute('DTEND',$d, array('VALUE' => 'DATE'));
397: } else {
398: $startTime = $iCal->_parseDateTime($a['Start']);
399: $vEvent->setAttribute('DTSTART', $startTime);
400: $vEvent->setAttribute('DTEND',
401: $iCal->_parseDateTime($a['End']));
402: }
403:
404: if (isset($a['IsRecurring']) && $a['IsRecurring'] == 1) {
405: $interval = '';
406: switch ($a['RecurrenceType']) {
407: case 0:
408:
409: if (!empty($a['DayOfWeekMask'])) {
410: $freq = 'WEEKLY';
411: $interval = ';INTERVAL=1';
412: } else {
413: $freq = 'DAILY';
414: $interval = ';INTERVAL=' . $a['Interval'];
415: }
416: break;
417: case 1:
418:
419: $freq = 'WEEKLY';
420: $interval = ';INTERVAL=' . $a['Interval'];
421: break;
422: case 2:
423:
424: $freq = 'MONTHLY';
425: $interval = ';INTERVAL=' . $a['Interval'];
426: break;
427: case 3:
428:
429: $freq = 'MONTHLY';
430: $interval = ';INTERVAL=' . $a['Interval'];
431: break;
432: case 5:
433:
434: $freq = 'YEARLY';
435: $interval = ';INTERVAL=' . $a['Interval'];
436: break;
437: case 6:
438:
439: $freq = 'YEARLY';
440: $interval = ';INTERVAL=' . $a['Interval'];
441: break;
442: }
443: $rrule = 'FREQ=' . $freq;
444: if (isset($a['Occurrences'])) {
445: $rrule .= ';COUNT=' . $a['Occurrences'];
446: } elseif (!isset($a['NoEndDate']) || $a['NoEndDate'] != 1) {
447: $rrule .= ';UNTIL=' . $a['PatternEndDate'];
448: }
449: $rrule .= $interval;
450: if (!empty($a['DayOfMonth'])) {
451: $rrule .= ';BYMONTHDAY=' . $a['DayOfMonth'];
452: }
453: if (!empty($a['MonthOfYear'])) {
454: $rrule .= ';BYMONTH=' . $a['MonthOfYear'];
455: }
456: if (!empty($a['DayOfWeekMask'])) {
457: $rrule .= ';BYDAY=';
458: $icaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA');
459: for ($i = $flag = 0; $i <= 7 ; ++$i) {
460: if (pow(2, $i) & $a['DayOfWeekMask']) {
461: if ($flag) {
462: $rrule .= ',';
463: }
464: $rrule .= $icaldays[$i];
465: $flag = true;
466: }
467: }
468: }
469: $vEvent->setAttribute('RRULE', $rrule);
470: }
471:
472: if (isset($a['ExcludeDate'])) {
473: $dates = array();
474: if (!is_array($a['ExcludeDate'])) {
475: $dates[] = $a['AllDayEvent'] == 1
476: ? $iCal->_parseDate($a['ExcludeDate'])
477: : $iCal->_parseDateTime($a['ExcludeDate']);
478: } else {
479: foreach ($a['ExcludeDate'] as $date) {
480: $dates[] = $a['AllDayEvent'] == 1
481: ? $iCal->_parseDate($date)
482: : $iCal->_parseDateTime($date);
483: }
484: }
485: if ($a['AllDayEvent'] == 1) {
486: $vEvent->setAttribute('EXDATE', $dates, array('VALUE' => 'DATE'));
487: } else {
488: $vEvent->setAttribute('EXDATE', $dates);
489: }
490: }
491:
492: if (isset($a['ReminderSet']) && $a['ReminderSet'] == 1) {
493: $vEvent->setAttribute('AALARM', $startTime - $a['ReminderMinutesBeforeStart'] * 60);
494: }
495:
496: if (isset($a['BusyStatus'])) {
497: switch ($a['BusyStatus']) {
498: case 0:
499:
500: $vEvent->setAttribute('STATUS', 'FREE');
501: break;
502: case 1:
503:
504: $vEvent->setAttribute('STATUS', 'TENTATIVE');
505: break;
506: case 2:
507:
508: case 3:
509:
510: $vEvent->setAttribute('STATUS', 'CONFIRMED');
511: break;
512: }
513: }
514:
515: if (isset($a['Sensitivity'])) {
516: switch ($a['Sensitivity']) {
517: case 0:
518:
519: $vEvent->setAttribute('CLASS', 'PUBLIC');
520: break;
521: case 1:
522:
523: case 2:
524:
525: $vEvent->setAttribute('CLASS', 'PRIVATE');
526: break;
527: case 3:
528:
529: $vEvent->setAttribute('CLASS', 'CONFIDENTIAL');
530: break;
531: }
532: }
533:
534: return $vEvent->exportvCalendar();
535: }
536:
537: public function sif2vtodo($sif)
538: {
539: $a = Horde_SyncMl_Device_sync4j::sif2array($sif);
540:
541: $iCal = new Horde_Icalendar();
542: $iCal->setAttribute('PRODID', '-//The Horde Project//SyncML//EN');
543: $iCal->setAttribute('METHOD', 'PUBLISH');
544:
545: $vtodo = Horde_Icalendar::newComponent('vtodo', $iCal);
546:
547: $vtodo->setAttribute('SUMMARY', $a['Subject']);
548: $vtodo->setAttribute('DESCRIPTION', $a['Body']);
549: if ($a['Importance'] == 0) {
550: $vtodo->setAttribute('PRIORITY', 5);
551: } elseif ($a['Importance'] == 2) {
552: $vtodo->setAttribute('PRIORITY', 1);
553: } else {
554: $vtodo->setAttribute('PRIORITY', 3);
555: }
556: if (!empty($a['StartDate']) && $a['StartDate'] != '45001231T230000Z') {
557: $vtodo->setAttribute('DTSTART', $iCal->_parseDateTime($a['StartDate']));
558: }
559: $dueSet = false;
560: if (!empty($a['DueDate']) && $a['DueDate'] != '45001231T230000Z') {
561: $vtodo->setAttribute('DUE', $iCal->_parseDateTime($a['DueDate']));
562: $dueSet = true;
563: }
564: if (!empty($a['ReminderSet'])) {
565: if (!$dueSet) {
566: $vtodo->setAttribute('DUE', $iCal->_parseDateTime($a['ReminderTime']));
567: }
568: $vtodo->setAttribute('AALARM', $iCal->_parseDateTime($a['ReminderTime']));
569: }
570: if (!empty($a['Complete'])) {
571: $vtodo->setAttribute('STATUS', 'COMPLETED');
572: }
573: $vtodo->setAttribute('CATEGORIES', isset($a['Categories']) ? $a['Categories'] : '');
574: if (isset($a['Sensitivity'])) {
575: switch ($a['Sensitivity']) {
576: case 0:
577:
578: $vtodo->setAttribute('CLASS', 'PUBLIC');
579: break;
580: case 1:
581:
582: case 2:
583:
584: $vtodo->setAttribute('CLASS', 'PRIVATE');
585: break;
586: case 3:
587:
588: $vtodo->setAttribute('CLASS', 'CONFIDENTIAL');
589: break;
590: }
591: }
592:
593: return $vtodo->exportvCalendar();
594: }
595:
596: public function vnote2sif($vnote)
597: {
598: $iCal = new Horde_Icalendar();
599: if (!$iCal->parsevCalendar($vnote)) {
600:
601: $a = array('Body' => $vnote);
602: } else {
603: $components = $iCal->getComponents();
604: if (!is_array($components) || count($components) == 0) {
605: $a = array(
606: 'Body' => $GLOBALS['backend']->t("Error converting notes."));
607: } else {
608: $a = array(
609: 'Body' => $components[0]->getAttribute('BODY'),
610: 'Categories' => $components[0]->getAttribute('CATEGORIES'));
611: try {
612: $sum = $components[0]->getAttribute('SUMMARY');
613: $a['Subject'] = $sum;
614: } catch (Horde_Icalendar_Exception $e) {
615: }
616: }
617: }
618:
619: return Horde_SyncMl_Device_sync4j::array2sif($a, '<note>', '</note>');
620: }
621:
622: public function vcard2sif($vcard)
623: {
624: $iCal = new Horde_Icalendar();
625: if (!$iCal->parsevCalendar($vcard)) {
626:
627: die("There was an error importing the data.");
628: }
629:
630: $components = $iCal->getComponents();
631:
632: switch (count($components)) {
633: case 0:
634:
635: die("No data was found.");
636:
637: case 1:
638: $content = $components[0];
639: break;
640:
641: default:
642:
643: die("Multiple components found; only one is supported.");
644: }
645:
646:
647:
648:
649: $attr = $content->getAllAttributes();
650: foreach ($attr as $item) {
651: switch ($item['name']) {
652: case 'FN':
653: $hash['FileAs'] = $item['value'];
654: break;
655:
656: case 'N':
657: $name = $item['values'];
658: $hash['LastName'] = $name[Horde_Icalendar_Vcard::N_FAMILY];
659: $hash['FirstName'] = $name[Horde_Icalendar_Vcard::N_GIVEN];
660: $hash['MiddleName'] = $name[Horde_Icalendar_Vcard::N_ADDL];
661: $hash['Title'] = $name[Horde_Icalendar_Vcard::N_PREFIX];
662: $hash['Suffix'] = $name[Horde_Icalendar_Vcard::N_SUFFIX];
663: break;
664:
665: case 'NICKNAME':
666: $hash['NickName'] = $item['value'];
667: break;
668:
669:
670: case 'ADR':
671: if (isset($item['params']['TYPE'])) {
672: if (!is_array($item['params']['TYPE'])) {
673: $item['params']['TYPE'] = array($item['params']['TYPE']);
674: }
675: } else {
676: $item['params']['TYPE'] = array();
677: if (isset($item['params']['WORK'])) {
678: $item['params']['TYPE'][] = 'WORK';
679: }
680: if (isset($item['params']['HOME'])) {
681: $item['params']['TYPE'][] = 'HOME';
682: }
683: }
684:
685: $address = $item['values'];
686: foreach ($item['params']['TYPE'] as $adr) {
687: switch (Horde_String::upper($adr)) {
688: case 'HOME':
689: $prefix = 'HomeAddress';
690: break;
691:
692: case 'WORK':
693: $prefix = 'BusinessAddress';
694: break;
695:
696: default:
697: $prefix = 'HomeAddress';
698: }
699:
700: if ($prefix) {
701: $hash[$prefix . 'Street'] =
702: isset($address[Horde_Icalendar_Vcard::ADR_STREET])
703: ? $address[Horde_Icalendar_Vcard::ADR_STREET]
704: : null;
705: $hash[$prefix . 'City'] =
706: isset($address[Horde_Icalendar_Vcard::ADR_LOCALITY])
707: ? $address[Horde_Icalendar_Vcard::ADR_LOCALITY]
708: : null;
709: $hash[$prefix . 'State'] =
710: isset($address[Horde_Icalendar_Vcard::ADR_REGION])
711: ? $address[Horde_Icalendar_Vcard::ADR_REGION]
712: : null;
713: $hash[$prefix . 'PostalCode'] =
714: isset($address[Horde_Icalendar_Vcard::ADR_POSTCODE])
715: ? $address[Horde_Icalendar_Vcard::ADR_POSTCODE]
716: : null;
717: $hash[$prefix . 'Country'] =
718: isset($address[Horde_Icalendar_Vcard::ADR_COUNTRY])
719: ? $address[Horde_Icalendar_Vcard::ADR_COUNTRY]
720: : null;
721: $hash[$prefix . 'PostOfficeBox'] =
722: isset($address[Horde_Icalendar_Vcard::ADR_POB])
723: ? $address[Horde_Icalendar_Vcard::ADR_POB]
724: : null;
725: }
726: }
727: break;
728:
729: case 'TEL':
730: if (isset($item['params']['FAX'])) {
731: if (isset($item['params']['WORK'])) {
732: $hash['BusinessFaxNumber'] = $item['value'];
733: } elseif (isset($item['params']['HOME'])) {
734: $hash['HomeFaxNumber'] = $item['value'];
735: } else {
736: $hash['OtherFaxNumber'] = $item['value'];
737: }
738: } elseif (isset($item['params']['TYPE'])) {
739: if (!is_array($item['params']['TYPE'])) {
740: $item['params']['TYPE'] = array($item['params']['TYPE']);
741: }
742:
743: foreach ($item['params']['TYPE'] as $tel) {
744: if (Horde_String::upper($tel) == 'WORK') {
745: $hash['BusinessTelephoneNumber'] = $item['value'];
746: } elseif (Horde_String::upper($tel) == 'HOME') {
747: $hash['HomeTelephoneNumber'] = $item['value'];
748: } elseif (Horde_String::upper($tel) == 'CELL') {
749: $hash['MobileTelephoneNumber'] = $item['value'];
750: } elseif (Horde_String::upper($tel) == 'FAX') {
751: $hash['BusinessFaxNumber'] = $item['value'];
752: }
753: }
754: } else {
755: if (isset($item['params']['HOME'])) {
756: $hash['HomeTelephoneNumber'] = $item['value'];
757: } elseif (isset($item['params']['WORK'])) {
758: $hash['BusinessTelephoneNumber'] = $item['value'];
759: } elseif (isset($item['params']['CELL'])) {
760: $hash['MobileTelephoneNumber'] = $item['value'];
761: } elseif (!isset($hash['HomeTelephoneNumber'])) {
762: $hash['HomeTelephoneNumber'] = $item['value'];
763: }
764: }
765: break;
766:
767: case 'EMAIL':
768: $email_set = false;
769: if (isset($item['params']['HOME']) && (!isset($hash['Email2Address']) ||
770: isset($item['params']['PREF']))) {
771: $hash['Email2Address'] = Horde_Icalendar_Vcard::getBareEmail($item['value']);
772: $email_set = true;
773: } elseif (isset($item['params']['WORK']) && (!isset($hash['Email3Address']) ||
774: isset($item['params']['PREF']))) {
775: $hash['Email3Address'] = Horde_Icalendar_Vcard::getBareEmail($item['value']);
776: $email_set = true;
777: } elseif (isset($item['params']['TYPE'])) {
778: if (!is_array($item['params']['TYPE'])) {
779: $item['params']['TYPE'] = array($item['params']['TYPE']);
780: }
781: if (in_array('HOME', $item['params']['TYPE']) &&
782: (!isset($hash['Email2Address']) || in_array('PREF', $item['params']['TYPE']))) {
783: $hash['Email2Address'] = Horde_Icalendar_Vcard::getBareEmail($item['value']);
784: $email_set = true;
785: } elseif (in_array('WORK', $item['params']['TYPE']) &&
786: (!isset($hash['Email3Address']) || in_array('PREF', $item['params']['TYPE']))) {
787: $hash['Email3Address'] = Horde_Icalendar_Vcard::getBareEmail($item['value']);
788: $email_set = true;
789: }
790: }
791: if (!$email_set && (!isset($hash['Email1Address']) || isset($item['params']['PREF']))) {
792: $hash['Email1Address'] = Horde_Icalendar_Vcard::getBareEmail($item['value']);
793: }
794: break;
795:
796: case 'TITLE':
797: $hash['JobTitle'] = $item['value'];
798: break;
799:
800: case 'ORG':
801: $values = preg_split('/(?<!\\\\);/', trim($item['value']));
802: $hash['CompanyName'] = $values[0];
803: $hash['Department'] = isset($values[1]) ? $values[1] : '';
804: break;
805:
806: case 'NOTE':
807: $hash['Body'] = $item['value'];
808: break;
809:
810: case 'URL':
811: $hash['WebPage'] = $item['value'];
812: break;
813:
814: case 'BDAY':
815: if (is_array($item['value'])) {
816: $hash['Birthday'] = sprintf('%04d-%02d-%02d',
817: $item['value']['year'],
818: $item['value']['month'],
819: $item['value']['mday']);
820: }
821: break;
822:
823: case 'X-ANNIVERSARY':
824: if (is_array($item['value'])) {
825: $hash['Anniversary'] = sprintf('%04d-%02d-%02d',
826: $item['value']['year'],
827: $item['value']['month'],
828: $item['value']['mday']);
829: }
830: break;
831:
832: case 'X-SPOUSE':
833: $hash['Spouse'] = $item['value'];
834: break;
835:
836: case 'X-CHILDREN':
837: $hash['Children'] = $item['value'];
838: break;
839:
840: case 'CATEGORIES':
841: $hash['Categories'] = $item['value'];
842: break;
843: }
844: }
845:
846: return Horde_SyncMl_Device_sync4j::array2sif(
847: $hash,
848: '<?xml version="1.0"?><contact>',
849: '</contact>');
850: }
851:
852: public function vevent2sif($vcard)
853: {
854: 855:
856: if (preg_match('/(\r\n|\r|\n)DTSTART:.*T000000(\r\n|\r|\n)/', $vcard)) {
857: if (preg_match('/(\r\n|\r|\n)DTEND:(\d\d\d\d)(\d\d)(\d\d)T235959(\r\n|\r|\n)/', $vcard, $m)) {
858: $vcard = preg_replace('/(\r\n|\r|\n)DTSTART:(.*)T000000(\r\n|\r|\n)/',
859: "$1DTSTART;VALUE=DATE:$2$3", $vcard);
860: $vcard = preg_replace('/(\r\n|\r|\n)DTEND:(.*)T235959(\r\n|\r|\n)/',
861: "$1DTEND;VALUE=DATE:$2$3", $vcard);
862: }
863:
864: }
865: $iCal = new Horde_Icalendar();
866: if (!$iCal->parsevCalendar($vcard)) {
867:
868: die("There was an error importing the data.");
869: }
870:
871: $components = $iCal->getComponents();
872:
873: switch (count($components)) {
874: case 0:
875:
876: die("No data was found.");
877:
878: case 1:
879: $content = $components[0];
880: break;
881:
882: default:
883:
884: die("Multiple components found; only one is supported.");
885: }
886:
887: $hash = array('ReminderSet' => 0,
888: 'IsRecurring' => 0,
889: 'BusyStatus' => 2);
890: $alarm = $end = null;
891: $start = $content->getAttribute('DTSTART');
892: $start_params = $content->getAttribute('DTSTART', true);
893: if (!empty($start_params[0]['VALUE']) &&
894: $start_params[0]['VALUE'] == 'DATE') {
895: $hash['AllDayEvent'] = 1;
896: $hash['Start'] = sprintf('%04d-%02d-%02d',
897: $start['year'],
898: $start['month'],
899: $start['mday']);
900: $start = mktime(0, 0, 0,
901: $start['month'],
902: $start['mday'],
903: $start['year']);
904: } else {
905: $hash['AllDayEvent'] = 0;
906: $hash['Start'] = Horde_Icalendar::_exportDateTime($start);
907: }
908:
909: foreach ($content->getAllAttributes() as $item) {
910: $GLOBALS['backend']->logMessage(
911: sprintf('Sync4j for name %s, value %s',
912: $item['name'],
913: is_string($item['value'])
914: ? $item['value'] : var_export($item['value'], true)), 'DEBUG');
915:
916: switch (Horde_String::upper($item['name'])) {
917: case 'DTSTART':
918: break;
919:
920: case 'DTEND':
921: if (!empty($item['params']['VALUE']) &&
922: $item['params']['VALUE'] == 'DATE') {
923: $hash['AllDayEvent'] = 1;
924: $date = new Horde_Date($item['value']['year'],
925: $item['value']['month'],
926: $item['value']['mday']);
927: $date->mday--;
928: $hash['End'] = $date->format('Y-m-d');
929: $end = $date->datestamp();
930: } else {
931: $hash['AllDayEvent'] = 0;
932: $hash['End'] = Horde_Icalendar::_exportDateTime($item['value']);
933: $end = $item['value'];
934: }
935: break;
936:
937: case 'SUMMARY':
938: $hash['Subject'] = $item['value'];
939: break;
940:
941: case 'DESCRIPTION':
942: $hash['Body'] = $item['value'];
943: break;
944:
945: case 'LOCATION':
946: $hash['Location'] = $item['value'];
947: break;
948:
949: case 'CATEGORIES':
950: $hash['Categories'] = $item['value'];
951: break;
952:
953: case 'AALARM':
954: $hash['ReminderSet'] = 1;
955: $alarm = $item['value'];
956: break;
957:
958: case 'STATUS':
959: switch (Horde_String::upper($item['value'])) {
960: case 'FREE':
961: case 'CANCELLED':
962: $hash['BusyStatus'] = 0;
963: break;
964:
965: case 'TENTATIVE':
966: $hash['BusyStatus'] = 1;
967: break;
968:
969: case 'CONFIRMED':
970: $hash['BusyStatus'] = 2;
971: break;
972: }
973: break;
974:
975: case 'CLASS':
976: switch (Horde_String::upper($item['value'])) {
977: case 'PUBLIC':
978: $hash['Sensitivity'] = 0;
979: break;
980:
981: case 'PRIVATE':
982: $hash['Sensitivity'] = 2;
983: break;
984:
985: case 'CONFIDENTIAL':
986: $hash['Sensitivity'] = 3;
987: break;
988: }
989: break;
990:
991: case 'RRULE':
992:
993: $rdata = array();
994: $parts = explode(';', $item['value']);
995: foreach ($parts as $part) {
996: list($key, $value) = explode('=', $part, 2);
997: $rdata[Horde_String::upper($key)] = $value;
998: }
999:
1000: if (!isset($rdata['FREQ'])) {
1001: break;
1002: }
1003:
1004: $hash['IsRecurring'] = 1;
1005:
1006: if (isset($rdata['BYDAY'])) {
1007: $maskdays = array('SU' => Horde_Date::MASK_SUNDAY,
1008: 'MO' => Horde_Date::MASK_MONDAY,
1009: 'TU' => Horde_Date::MASK_TUESDAY,
1010: 'WE' => Horde_Date::MASK_WEDNESDAY,
1011: 'TH' => Horde_Date::MASK_THURSDAY,
1012: 'FR' => Horde_Date::MASK_FRIDAY,
1013: 'SA' => Horde_Date::MASK_SATURDAY);
1014: $days = explode(',', $rdata['BYDAY']);
1015: $mask = 0;
1016: foreach ($days as $day) {
1017: $instance = (int)$day;
1018: $mask |= $maskdays[str_replace($instance, '', $day)];
1019: }
1020: }
1021:
1022: $hash['Interval'] = isset($rdata['INTERVAL'])
1023: ? $rdata['INTERVAL']
1024: : 1;
1025:
1026: switch (Horde_String::upper($rdata['FREQ'])) {
1027: case 'DAILY':
1028: $hash['RecurrenceType'] = 0;
1029: break;
1030:
1031: case 'WEEKLY':
1032: $hash['RecurrenceType'] = 1;
1033: if (isset($rdata['BYDAY'])) {
1034: $hash['DayOfWeekMask'] = $mask;
1035: }
1036: break;
1037:
1038: case 'MONTHLY':
1039: if (isset($rdata['BYDAY'])) {
1040: $hash['RecurrenceType'] = 3;
1041: $hash['Instance'] = $instance;
1042: $hash['DayOfWeekMask'] = $mask;
1043: } else {
1044: $hash['RecurrenceType'] = 2;
1045: $hash['DayOfMonth'] = date('j', $start);
1046: }
1047: break;
1048:
1049: case 'YEARLY':
1050: if (isset($rdata['BYDAY'])) {
1051: $hash['RecurrenceType'] = 6;
1052: $hash['Instance'] = $instance;
1053: $hash['DayOfWeekMask'] = $mask;
1054: } else {
1055: $hash['RecurrenceType'] = 5;
1056: $hash['DayOfMonth'] = date('j', $start);
1057: }
1058: $hash['MonthOfYear'] = date('n', $start);
1059: unset($hash['Interval']);
1060: break;
1061: }
1062:
1063: if (isset($rdata['UNTIL'])) {
1064: $hash['NoEndDate'] = 0;
1065: $hash['PatternEndDate'] = $rdata['UNTIL'];
1066: } elseif (isset($rdata['COUNT'])) {
1067: $hash['NoEndDate'] = 0;
1068: $hash['Occurrences'] = $rdata['COUNT'];
1069: } else {
1070: $hash['NoEndDate'] = 1;
1071: }
1072: break;
1073:
1074: case 'EXDATE':
1075: if (empty($hash['Exceptions'])) {
1076: $hash['Exceptions'] = array();
1077: }
1078: foreach ($item['values'] as $date) {
1079: if ($hash['AllDayEvent'] == 1) {
1080: $d = new Horde_Date(array('year' => $date['year'],
1081: 'month' => $date['month'],
1082: 'mday' => $date['mday'] + 1));
1083: $hash['Exceptions'][] = array('ExcludeDate' => $d->format('Y-m-d'));
1084: } else {
1085: $hash['Exceptions'][] = array('ExcludeDate' => Horde_Icalendar::_exportDate($date));
1086: }
1087: }
1088: break;
1089: }
1090: }
1091:
1092: if (!empty($start)) {
1093: if ($hash['ReminderSet'] && !empty($alarm) && $start != $alarm) {
1094: $hash['ReminderMinutesBeforeStart'] = ($start - $alarm) / 60;
1095: } else {
1096:
1097: foreach ($content->getComponents() as $component) {
1098: if ($component->getType() != 'vAlarm') {
1099: continue;
1100: }
1101: try {
1102: $trigger = $component->getAttribute('TRIGGER');
1103: } catch (Horde_Icalendar_Exception $e) {
1104: continue;
1105: }
1106: if (is_array($trigger) || empty($trigger)) {
1107: continue;
1108: }
1109: $hash['ReminderSet'] = 1;
1110: $hash['ReminderMinutesBeforeStart'] = (-$trigger) / 60;
1111: }
1112: }
1113: }
1114:
1115: if (empty($hash['AllDayEvent']) && !empty($start) &&
1116: !empty($end) && $start != $end) {
1117: $hash['Duration'] = ($end - $start) / 60;
1118: $GLOBALS['backend']->logMessage(
1119: 'Duration set to ' . $hash['Duration'], 'DEBUG');
1120:
1121: }
1122:
1123: return Horde_SyncMl_Device_sync4j::array2sif(
1124: $hash,
1125: '<?xml version="1.0"?><appointment>',
1126: '</appointment>');
1127: }
1128:
1129: public function vtodo2sif($vcard)
1130: {
1131: $iCal = new Horde_Icalendar();
1132: if (!$iCal->parsevCalendar($vcard)) {
1133: return PEAR::raiseError('There was an error importing the data.');
1134: }
1135:
1136: $components = $iCal->getComponents();
1137:
1138: switch (count($components)) {
1139: case 0:
1140: return PEAR::raiseError('No data was found');
1141:
1142: case 1:
1143: $content = $components[0];
1144: break;
1145:
1146: default:
1147: return PEAR::raiseError('Multiple components found; only one is supported.');
1148: }
1149:
1150: $hash['Complete'] = 0;
1151: $due = false;
1152:
1153: $attr = $content->getAllAttributes();
1154: foreach ($attr as $item) {
1155: switch ($item['name']) {
1156: case 'SUMMARY':
1157: $hash['Subject'] = $item['value'];
1158: break;
1159:
1160: case 'DESCRIPTION':
1161: $hash['Body'] = $item['value'];
1162: break;
1163:
1164: case 'PRIORITY':
1165: if ($item['value'] == 1) {
1166: $hash['Importance'] = 2;
1167: } elseif ($item['value'] == 5) {
1168: $hash['Importance'] = 0;
1169: } else {
1170: $hash['Importance'] = 1;
1171: }
1172: break;
1173:
1174: case 'DTSTART':
1175: $hash['StartDate'] = Horde_Icalendar::_exportDateTime($item['value']);
1176: break;
1177:
1178: case 'DUE':
1179: $hash['DueDate'] = Horde_Icalendar::_exportDateTime($item['value']);
1180: $due = $item['value'];
1181: break;
1182:
1183: case 'AALARM':
1184: $hash['ReminderTime'] = $item['value'];
1185: $hash['ReminderSet'] = 1;
1186: break;
1187:
1188: case 'STATUS':
1189: $hash['Complete'] = $item['value'] == 'COMPLETED' ? 1 : 0;
1190: break;
1191:
1192: case 'CATEGORIES':
1193: $hash['Categories'] = $item['value'];
1194: break;
1195:
1196: case 'CLASS':
1197: switch (Horde_String::upper($item['value'])) {
1198: case 'PUBLIC':
1199: $hash['Sensitivity'] = 0;
1200: break;
1201:
1202: case 'PRIVATE':
1203: $hash['Sensitivity'] = 2;
1204: break;
1205:
1206: case 'CONFIDENTIAL':
1207: $hash['Sensitivity'] = 3;
1208: break;
1209: }
1210: break;
1211: }
1212: }
1213:
1214: if ($due && !isset($hash['ReminderSet'])) {
1215:
1216: foreach ($content->getComponents() as $component) {
1217: if ($component->getType() != 'vAlarm') {
1218: continue;
1219: }
1220: try {
1221: $trigger = $component->getAttribute('TRIGGER');
1222: } catch (Horde_Icalendar_Exception $e) {
1223: continue;
1224: }
1225: if (is_array($trigger) || empty($trigger)) {
1226: continue;
1227: }
1228: $hash['ReminderSet'] = 1;
1229: $hash['ReminderTime'] = Horde_Icalendar::_exportDateTime($due - $trigger);
1230: }
1231: }
1232:
1233: return Horde_SyncMl_Device_sync4j::array2sif(
1234: $hash,
1235: '<?xml version="1.0"?><task>',
1236: '</task>');
1237: }
1238:
1239: 1240: 1241: 1242: 1243: 1244: 1245:
1246: public function useCdataTag()
1247: {
1248: return false;
1249: }
1250: }
1251: