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:  * Sync4j (www.sync4j.org)
   4:  *
   5:  * The Sync4J outlook converter uses its native SIF format for data
   6:  * exchange. Conversion to text/vcalendar etc. is done by SifConverter.php The
   7:  * connector seems not support DevInf information, so Horde_SyncMl_Device can
   8:  * only detect it by the decice ID: so in the connector configuration the
   9:  * device ID must be set to 'sc-pim-<type>' which should be the default anyhow.
  10:  *
  11:  * Copyright 2005-2012 Horde LLC (http://www.horde.org/)
  12:  *
  13:  * See the enclosed file COPYING for license information (LGPL). If you
  14:  * did not receive this file, see http://www.horde.org/licenses/lgpl21.
  15:  *
  16:  * @author  Karsten Fourmont <karsten@horde.org>
  17:  * @package SyncMl
  18:  */
  19: class Horde_SyncMl_Device_sync4j extends Horde_SyncMl_Device
  20: {
  21:     /**
  22:      * Convert the content.
  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:                 // The Funambol Outlook connector uses invalid STATUS
  59:                 // values. Actually it maps MeetingStatus values of the
  60:                 // Outlook event to the STATUS property, which is
  61:                 // completely useless. So drop the STATUS altogether.
  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:      * Converts the content from the backend to a format suitable for the
  76:      * client device.
  77:      *
  78:      * Strips the uid (primary key) information as client and server might use
  79:      * different ones.
  80:      *
  81:      * @param string $content      The content to convert
  82:      * @param string $contentType  The content type of content as returned
  83:      *                             from the backend
  84:      * @param string $database     The server database URI.
  85:      *
  86:      * @return array  Three-element array with the converted content, the
  87:      *                (possibly changed) new content type, and encoding type
  88:      *                (like b64 as used by Funambol).
  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:                     // The Funambol Outlook connector uses invalid STATUS
 103:                     // values. Actually it maps MeetingStatus values of the
 104:                     // Outlook event to the STATUS property, which is
 105:                     // completely useless. So drop the STATUS altogether.
 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:      * Decodes a sif xml string to an associative array.
 151:      *
 152:      * Quick hack to convert from text/vcard and text/vcalendar to
 153:      * Sync4J's proprietery sif datatypes and vice versa.  For details
 154:      * about the sif format see the appendix of the developer guide on
 155:      * www.sync4j.org.
 156:      *
 157:      * @access private
 158:      *
 159:      * @param string $sif  A sif string like <k1>v1&gt;</k1><k2>v2</k2>
 160:      *
 161:      * @return array  Assoc array in utf8 like array ('k1' => 'v1>', 'k2' => 'v2');
 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:      * Converts a hash to a SIF XML structure.
 183:      *
 184:      * @param array $array  A hash.
 185:      * @param string $pre   A prefix string for the XML result.
 186:      * @param string $post  A suffix string for the XML result.
 187:      *
 188:      * @return string  The resulting XML string.
 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:             // Not exactly correct, we ignore the start and end time of
 384:             // all-day events and simple assume that the client had set them
 385:             // correctly to 0:00.
 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:                 /* olDaily */
 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:                 /* olWeekly */
 419:                 $freq = 'WEEKLY';
 420:                 $interval = ';INTERVAL=' . $a['Interval'];
 421:                 break;
 422:             case 2:
 423:                 /* olMonthly */
 424:                 $freq = 'MONTHLY';
 425:                 $interval = ';INTERVAL=' . $a['Interval'];
 426:                 break;
 427:             case 3:
 428:                 /* olMonthNth */
 429:                 $freq = 'MONTHLY';
 430:                 $interval = ';INTERVAL=' . $a['Interval'];
 431:                 break;
 432:             case 5:
 433:                 /* olYearly */
 434:                 $freq = 'YEARLY';
 435:                 $interval = ';INTERVAL=' . $a['Interval'];
 436:                 break;
 437:             case 6:
 438:                 /* olYearNth */
 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:                 /* olFree - FREE is not a iCalendar standard value. */
 500:                 $vEvent->setAttribute('STATUS', 'FREE');
 501:                 break;
 502:             case 1:
 503:                 /* olTentative */
 504:                 $vEvent->setAttribute('STATUS', 'TENTATIVE');
 505:                 break;
 506:             case 2:
 507:                 /* olBusy */
 508:             case 3:
 509:                 /* olOutOfOffice */
 510:                 $vEvent->setAttribute('STATUS', 'CONFIRMED');
 511:                 break;
 512:             }
 513:         }
 514: 
 515:         if (isset($a['Sensitivity'])) {
 516:             switch ($a['Sensitivity']) {
 517:             case 0:
 518:                 /* olNormal - FREE is not a iCalendar standard value. */
 519:                 $vEvent->setAttribute('CLASS', 'PUBLIC');
 520:                 break;
 521:             case 1:
 522:                 /* olPersonal */
 523:             case 2:
 524:                 /* olPrivate */
 525:                 $vEvent->setAttribute('CLASS', 'PRIVATE');
 526:                 break;
 527:             case 3:
 528:                 /* olConfidential */
 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:                 /* olNormal */
 578:                 $vtodo->setAttribute('CLASS', 'PUBLIC');
 579:                 break;
 580:             case 1:
 581:                 /* olPersonal */
 582:             case 2:
 583:                 /* olPrivate */
 584:                 $vtodo->setAttribute('CLASS', 'PRIVATE');
 585:                 break;
 586:             case 3:
 587:                 /* olConfidential */
 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:             // handle plain text:
 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:             // @TODO: NEVER use die() in a library.
 627:             die("There was an error importing the data.");
 628:         }
 629: 
 630:         $components = $iCal->getComponents();
 631: 
 632:         switch (count($components)) {
 633:         case 0:
 634:             // @TODO: NEVER use die() in a library.
 635:             die("No data was found.");
 636: 
 637:         case 1:
 638:             $content = $components[0];
 639:             break;
 640: 
 641:         default:
 642:             // @TODO: NEVER use die() in a library.
 643:             die("Multiple components found; only one is supported.");
 644:         }
 645: 
 646:         // from here on, the code is taken from
 647:         // Turba_Driver::toHash, v 1.65 2005/03/12
 648:         // and modified for the Sync4J attribute names.
 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:             // For vCard 3.0.
 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:                     // For vCard 3.0.
 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:         /* Some special handling for all-day vEvents that are not passed
 855:          * as TYPE=DATE (TYPE=DATE does not exist for vCalendar 1.0) */
 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:             // @TODO: else: handle case with DTEND= T240000
 864:         }
 865:         $iCal = new Horde_Icalendar();
 866:         if (!$iCal->parsevCalendar($vcard)) {
 867:             // @TODO: NEVER use die() in a library.
 868:             die("There was an error importing the data.");
 869:         }
 870: 
 871:         $components = $iCal->getComponents();
 872: 
 873:         switch (count($components)) {
 874:         case 0:
 875:             // @TODO: NEVER use die() in a library.
 876:             die("No data was found.");
 877: 
 878:         case 1:
 879:             $content = $components[0];
 880:             break;
 881: 
 882:         default:
 883:             // @TODO: NEVER use die() in a library.
 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:                 // Parse the recurrence rule into keys and values.
 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:                 // Parse VALARM components.
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:             // Parse VALARM components.
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:      * Sync4j as of Funambol Outlook connector 3.0.15 can't deal
1241:      * with <![CDATA[ so omit it.
1242:      * The Funambol Sync4j client chokes on the cdata
1243:      * so for this device it has to be set to false. Syn4j uses base64
1244:      * encoding and so the problems with escaping does not occur.
1245:      */
1246:     public function useCdataTag()
1247:     {
1248:         return false;
1249:     }
1250: }
1251: 
API documentation generated by ApiGen