diff --git a/addressbook/inc/class.bocontacts.inc.php b/addressbook/inc/class.bocontacts.inc.php index 6b1eb6d167..02456b77ef 100755 --- a/addressbook/inc/class.bocontacts.inc.php +++ b/addressbook/inc/class.bocontacts.inc.php @@ -1108,4 +1108,86 @@ class bocontacts extends socontacts //echo "
bocontacts::addr_format_by_country('$country'='$code') = '$adr_format'
\n"; return $adr_format; } + + var $app_cat; + var $glob_cat; + + function find_or_add_categories($catname_list) + { + if (!is_object($this->glob_cat)) + { + if (!is_object($GLOBALS['egw']->categories)) + { + $GLOBALS['egw']->categories =& CreateObject('phpgwapi.categories',$this->owner,'phpgw'); + } + $this->glob_cat =& $GLOBALS['egw']->categories; + } + + if (!is_object($this->app_cat)) + { + $this->app_cat =& CreateObject('phpgwapi.categories',$this->owner,'addressbook'); + } + + $cat_id_list = array(); + foreach($catname_list as $cat_name) + { + $cat_name = trim($cat_name); + if (!($cat_id = $this->glob_cat->name2id($cat_name)) + && !($cat_id = $this->app_cat->name2id($cat_name))) + { + $cat_id = $this->app_cat->add( array('name' => $cat_name,'descr' => $cat_name )); + } + + $cat_id_list[] = $cat_id; + } + + if (count($cat_id_list) > 1) + { + sort($cat_id_list, SORT_NUMERIC); + } + return $cat_id_list; + } + + function get_categories($cat_id_list) + { + if (!is_object($this->glob_cat)) + { + if (!is_object($GLOBALS['egw']->categories)) + { + $GLOBALS['egw']->categories =& CreateObject('phpgwapi.categories',$this->owner,'phpgw'); + } + $this->glob_cat =& $GLOBALS['egw']->categories; + } + + if (!is_object($this->app_cat)) + { + $this->app_cat =& CreateObject('phpgwapi.categories',$this->owner,'addressbook'); + } + + $cat_list = array(); + foreach(explode(',',$cat_id_list) as $cat_id) + { + if ( ($cat_data = $this->glob_cat->return_single($cat_id)) + || ($cat_data = $this->app_cat->return_single($cat_id)) ) + { + $cat_list[] = $cat_data[0]['name']; + } + } + + return $cat_list; + } + + function fixup_contact(&$contact) + { + if (!isset($contact['n_fn']) || empty($contact['n_fn'])) + { + $contact['n_fn'] = $this->fullname($contact); + } + + if (!isset($contact['n_fileas']) || empty($contact['n_fileas'])) + { + $contact['n_fileas'] = $this->fileas($contact); + } + } + } diff --git a/addressbook/inc/class.sifaddressbook.inc.php b/addressbook/inc/class.sifaddressbook.inc.php index eafbd161d1..39ce1b6f3f 100644 --- a/addressbook/inc/class.sifaddressbook.inc.php +++ b/addressbook/inc/class.sifaddressbook.inc.php @@ -135,23 +135,9 @@ class sifaddressbook extends bocontacts $value = $GLOBALS['egw']->translation->convert($value, 'utf-8'); switch($key) { case 'cat_id': - if(!empty($value)) { - $isAdmin = $GLOBALS['egw']->acl->check('run',1,'admin'); - $egwCategories =& CreateObject('phpgwapi.categories', $GLOBALS['egw_info']['user']['account_id'], 'addressbook'); - $categories = explode(';',$value); - // add missing categories as personal categories as needed - foreach($categories as $categorieName) { - $cat_id = false; - $categorieName = trim($categorieName); - if(!($cat_id = $egwCategories->name2id($categorieName))) { - $cat_id = $egwCategories->add(array('name' => $categorieName, 'descr' => lang('added by synchronisation'))); - error_log("added $cat_id => $categorieName"); - } - if($cat_id) { - if(!empty($finalContact[$key])) $finalContact[$key] .= ','; - $finalContact[$key] .= $cat_id; - } - } + if(!empty($value)) + { + $finalContact[$key] = implode(",", $this->find_or_add_categories(explode(';', $value))); } break; @@ -164,6 +150,8 @@ class sifaddressbook extends bocontacts break; } } + + $this->fixup_contact($finalContact); return $finalContact; } @@ -231,6 +219,9 @@ class sifaddressbook extends bocontacts #error_log(print_r($entry,true)); $sysCharSet = $GLOBALS['egw']->translation->charset(); + // fillup some defaults such as n_fn and n_fileas is needed + $this->fixup_contact($entry); + foreach($this->sifMapping as $sifField => $egwField) { if(empty($egwField)) continue; @@ -245,16 +236,8 @@ class sifaddressbook extends bocontacts // TODO handle multiple categories case 'Categories': if(!empty($value)) { - $egwCategories =& CreateObject('phpgwapi.categories',$GLOBALS['egw_info']['user']['account_id'],'addressbook'); - $categories = explode(',',$value); - $value = ''; - foreach($categories as $cat_id) { - if(($catData = $egwCategories->return_single($cat_id))) - { - if(!empty($value)) $value .= '; '; - $value .= $catData[0]['name']; - } - } + $value = implode("; ", $this->get_categories($value)); + $value = $GLOBALS['egw']->translation->convert($value, $sysCharSet, 'utf-8'); } $sifContact .= "<$sifField>$value$sifField>"; break; @@ -270,7 +253,7 @@ class sifaddressbook extends bocontacts break; default: - $sifContact .= "<$sifField>$value$sifField>"; + $sifContact .= "<$sifField>".trim($value)."$sifField>"; break; } } diff --git a/addressbook/inc/class.vcaladdressbook.inc.php b/addressbook/inc/class.vcaladdressbook.inc.php index 3bcb60f71b..f9cdb77d52 100644 --- a/addressbook/inc/class.vcaladdressbook.inc.php +++ b/addressbook/inc/class.vcaladdressbook.inc.php @@ -57,52 +57,83 @@ class vcaladdressbook extends bocontacts if(!($entry = $this->read($_id))) { return false; } - - foreach($this->supportedFields as $vcardField => $databaseFields) { - $options = array(); - $value = ''; - foreach($databaseFields as $databaseField) { - $tempVal = ';'; - if(!empty($databaseField)) { - $tempVal = trim($entry[$databaseField]).';'; - } - $value .= $tempVal; - } - // remove the last ; - $value = substr($value, 0, -1); - switch($vcardField) { - // TODO handle multiple categories - case 'CATEGORIES': - $catData = ExecMethod('phpgwapi.categories.return_single',$value); - $value = $catData[0]['name']; - break; - case 'CLASS': - $value = $value ? 'PRIVATE' : 'PUBLIC'; - break; - case 'BDAY': - if(!empty($value)) { - $value = str_replace('-','',$value).'T000000Z'; - } - break; + $this->fixup_contact($entry); + + foreach($this->supportedFields as $vcardField => $databaseFields) + { + $values = array(); + $options = array(); + $hasdata = 0; + foreach($databaseFields as $databaseField) + { + $value = ""; + + if (!empty($databaseField)) + { + $value = trim($entry[$databaseField]); + } + + switch($databaseField) + { + case 'private': + $value = $value ? 'PRIVATE' : 'PUBLIC'; + $hasdata++; + break; + + case 'bday': + if (!empty($value)) + { + $value = str_replace('-','',$value).'T000000Z'; + $hasdata++; + } + break; + + case 'jpegphoto': + if(!empty($value)) + { + error_log("PHOTO='".$value."'"); + $hasdata++; + } + break; + + case 'cat_id': + if (!empty($value)) + { + $value = implode(",", $this->get_categories($value)); + } + // fall-through to the normal processing of string values + default: + if(!empty($value)) + { + $value = $GLOBALS['egw']->translation->convert(trim($value), $sysCharSet, 'utf-8'); + $options['CHARSET'] = 'UTF-8'; + + if(preg_match('/([\000-\012\015\016\020-\037\075])/',$value)) + { + $options['ENCODING'] = 'QUOTED-PRINTABLE'; + } + + $hasdata++; + } + break; + } + + if (empty($value)) + { + $value = ""; + } + + $values[] = $value; } - - if ($databaseField != 'jpegphoto') { - $value = $GLOBALS['egw']->translation->convert($value, $sysCharSet, 'utf-8'); + + if ($hasdata <= 0) + { + // don't add the entry if there is no data for this field + continue; } - - // don't add the entry if it contains only ';' - // exeptions for mendatory fields - if( ( strlen(str_replace(';','',$value)) != 0 ) || in_array($vcardField,array('FN','ORG','N')) ) { - $vCard->setAttribute($vcardField, $value); - } - if(preg_match('/([\000-\012\015\016\020-\037\075])/',$value)) { - $options['ENCODING'] = 'QUOTED-PRINTABLE'; - } - if(preg_match('/([\177-\377])/',$value)) { - $options['CHARSET'] = 'UTF-8'; - } - + + $vCard->setAttribute($vcardField, implode(';', $values)); $vCard->setParameter($vcardField, $options); } @@ -177,6 +208,7 @@ class vcaladdressbook extends bocontacts 'CLASS' => array('private'), 'EMAIL' => array('email'), 'N' => array('n_family','n_given','','',''), + 'FN' => array('n_fn'), 'NOTE' => array('note'), 'ORG' => array('org_name',''), 'TEL;CELL' => array('tel_cell'), @@ -196,6 +228,7 @@ class vcaladdressbook extends bocontacts 'EMAIL;INTERNET;WORK' => array('email'), 'EMAIL;INTERNET;HOME' => array('email_home'), 'N' => array('n_family','n_given','n_middle','n_prefix','n_suffix'), + 'FN' => array('n_fn'), 'NOTE' => array('note'), 'ORG' => array('org_name','org_unit'), 'TEL;CELL;WORK' => array('tel_cell'), @@ -216,6 +249,7 @@ class vcaladdressbook extends bocontacts 'CLASS' => array('private'), 'EMAIL' => array('email'), 'N' => array('n_family','n_given','','',''), + 'FN' => array('n_fn'), 'NOTE' => array('note'), 'ORG' => array('org_name',''), 'TEL;CELL;WORK' => array('tel_cell'), @@ -235,6 +269,7 @@ class vcaladdressbook extends bocontacts 'EMAIL;INTERNET;WORK' => array('email'), 'EMAIL;INTERNET;HOME' => array('email_home'), 'N' => array('n_family','n_given','','',''), + 'FN' => array('n_fn'), 'NOTE' => array('note'), 'ORG' => array('org_name','org_unit'), 'TEL;CELL;WORK' => array('tel_cell'), @@ -255,6 +290,7 @@ class vcaladdressbook extends bocontacts 'EMAIL;INTERNET;WORK' => array('email'), 'EMAIL;INTERNET;HOME' => array('email_home'), 'N' => array('n_family','n_given','','',''), + 'FN' => array('n_fn'), 'NOTE' => array('note'), 'ORG' => array('org_name',''), 'TEL;CELL;WORK' => array('tel_cell'), @@ -278,6 +314,7 @@ class vcaladdressbook extends bocontacts 'EMAIL;INTERNET;WORK' => array('email'), 'EMAIL;INTERNET;HOME' => array('email_home'), 'N' => array('n_family','n_given','','n_prefix','n_suffix'), + 'FN' => array('n_fn'), 'NOTE' => array('note'), 'ORG' => array('org_name',''), 'TEL;CELL;WORK' => array('tel_cell'), @@ -293,32 +330,38 @@ class vcaladdressbook extends bocontacts ); $defaultFields[6] = array( - 'ADR;WORK' => array('','','adr_one_street','adr_one_locality','adr_one_region', - 'adr_one_postalcode','adr_one_countryname'), - 'ADR;HOME' => array('','','adr_two_street','adr_two_locality','adr_two_region', - 'adr_two_postalcode','adr_two_countryname'), - 'EMAIL' => array('email'), - 'EMAIL;HOME' => array('email_home'), - 'N' => array('n_family','n_given','','',''), - 'NOTE' => array('note'), - 'ORG' => array('org_name','org_unit'), - 'TEL;CELL' => array('tel_cell'), - 'TEL;HOME;FAX' => array('tel_fax'), + 'ADR;WORK' => array('','','adr_one_street','adr_one_locality','adr_one_region', + 'adr_one_postalcode','adr_one_countryname'), + 'ADR;HOME' => array('','','adr_two_street','adr_two_locality','adr_two_region', + 'adr_two_postalcode','adr_two_countryname'), + 'EMAIL' => array('email'), + 'EMAIL;HOME' => array('email_home'), + 'N' => array('n_family','n_given','','',''), + 'FN' => array('n_fn'), + 'NOTE' => array('note'), + 'ORG' => array('org_name','org_unit'), + 'TEL;CELL' => array('tel_cell'), + 'TEL;HOME;FAX' => array('tel_fax'), 'TEL;HOME;VOICE' => array('tel_home'), - 'TEL;PAGER' => array('tel_pager'), + 'TEL;PAGER' => array('tel_pager'), 'TEL;WORK;VOICE' => array('tel_work'), - 'TITLE' => array('title'), - 'URL;WORK' => array('url'), - 'URL' => array('url_home'), + 'TITLE' => array('title'), + 'URL;WORK' => array('url'), + 'URL' => array('url_home'), ); + //error_log("Client: $_productManufacturer $_productName"); switch(strtolower($_productManufacturer)) { case 'funambol': - switch(strtolower($_productName)) + switch (strtolower($_productName)) { - case 'fmz-thunderbird-plugin': + case 'thunderbird': + $this->supportedFields = $defaultFields[6]; + break; + default: + error_log("Funambol product '$_productName', assuming same as thunderbird"); $this->supportedFields = $defaultFields[6]; break; } @@ -328,7 +371,10 @@ class vcaladdressbook extends bocontacts switch(strtolower($_productName)) { case 'syncje outlook edition': + $this->supportedFields = $defaultFields[1]; + break; default: + error_log("Nethaus product '$_productName', assuming same as 'syncje outlook'"); $this->supportedFields = $defaultFields[1]; break; } @@ -341,7 +387,10 @@ class vcaladdressbook extends bocontacts $this->supportedFields = $defaultFields[5]; break; case '6600': + $this->supportedFields = $defaultFields[4]; + break; default: + error_log("Unknown Nokia phone '$_productName', assuming same as '6600'"); $this->supportedFields = $defaultFields[4]; break; } @@ -363,17 +412,25 @@ class vcaladdressbook extends bocontacts switch(strtolower($_productName)) { case 'sx1': + $this->supportedFields = $defaultFields[3]; + break; default: + error_log("Unknown Siemens phone '$_productName', assuming same as 'sx1'"); $this->supportedFields = $defaultFields[3]; break; } break; case 'sonyericsson': + case 'sony ericsson': switch(strtolower($_productName)) { case 'd750i': + $this->supportedFields = $defaultFields[2]; + break; + case 'p910i': default: + error_log("unknown Sony Ericsson phone '$_productName', assuming same as 'd750i'"); $this->supportedFields = $defaultFields[2]; break; } @@ -387,6 +444,7 @@ class vcaladdressbook extends bocontacts #$this->supportedFields['PHOTO'] = array('jpegphoto'); break; default: + error_log("Synthesis connector '$_productName', using default fields"); $this->supportedFields = $defaultFields[0]; break; } @@ -398,7 +456,7 @@ class vcaladdressbook extends bocontacts // the fallback for SyncML default: - error_log("Client not found: $_productManufacturer $_productName"); + error_log("Client not found: '$_productManufacturer' '$_productName'"); $this->supportedFields = $defaultFields[0]; break; } @@ -500,30 +558,6 @@ class vcaladdressbook extends bocontacts } break; - case 'CATEGORIES': - #cat_id = 7,8 - $vcardData['category'] = array(); - if ($attributes['value']) - { - if (!is_object($this->cat)) - { - if (!is_object($GLOBALS['egw']->categories)) - { - $GLOBALS['egw']->categories =& CreateObject('phpgwapi.categories',$this->owner,'addressbook'); - } - $this->cat =& $GLOBALS['egw']->categories; - } - foreach(explode(',',$attributes['value']) as $cat_name) - { - if (!($cat_id = $this->cat->name2id($cat_name))) - { - $cat_id = $this->cat->add( array('name' => $cat_name,'descr' => $cat_name )); - } - $vcardData['category'][] = $cat_id; - } - } - break; - case 'VERSION': break; @@ -546,38 +580,30 @@ class vcaladdressbook extends bocontacts { if(!empty($fieldName)) { + $value = trim($vcardValues[$vcardKey]['values'][$fieldKey]); switch($fieldName) { case 'bday': - if(!empty($vcardValues[$vcardKey]['values'][$fieldKey])) { - $contact[$fieldName] = date('Y-m-d', $vcardValues[$vcardKey]['values'][$fieldKey]); + if(!empty($value)) { + $contact[$fieldName] = date('Y-m-d', $value); } break; case 'private': - (int)$contact[$fieldName] = $vcardValues[$vcardKey]['values'][$fieldKey] == 'PRIVATE'; + (int)$contact[$fieldName] = $value == 'PRIVATE'; break; case 'cat_id': - if (!is_object($this->cat)) { - if (!is_object($GLOBALS['egw']->categories)) { - $GLOBALS['egw']->categories =& CreateObject('phpgwapi.categories',$GLOBALS['egw_info']['user']['account_id'],'addressbook'); - } - $this->cat =& $GLOBALS['egw']->categories; - } - foreach(explode(',',$vcardValues[$vcardKey]['values'][$fieldKey]) as $cat_name) { - if (!($cat_id = $this->cat->name2id($cat_name))) { - $cat_id = $this->cat->add( array('name' => $cat_name, 'descr' => $cat_name )); - } - $contact[$fieldName] = $cat_id; - } + $contact[$fieldName] = implode(",",$this->find_or_add_categories(explode(',',$value))); break; + case 'note': // note may contain ','s but maybe this needs to be fixed in vcard parser... - $contact[$fieldName] = trim($vcardValues[$vcardKey]['value']); - break; + //$contact[$fieldName] = trim($vcardValues[$vcardKey]['value']); + //break; + default: - $contact[$fieldName] = trim($vcardValues[$vcardKey]['values'][$fieldKey]); + $contact[$fieldName] = $value; break; } } @@ -585,7 +611,7 @@ class vcaladdressbook extends bocontacts } } - $contact['n_fn'] = trim($contact['n_given'].' '.$contact['n_family']); + $this->fixup_contact($contact); return $contact; } diff --git a/calendar/inc/class.bocalupdate.inc.php b/calendar/inc/class.bocalupdate.inc.php index a93f7e589b..da486d9747 100644 --- a/calendar/inc/class.bocalupdate.inc.php +++ b/calendar/inc/class.bocalupdate.inc.php @@ -121,6 +121,21 @@ class bocalupdate extends bocal !$event['id'] && !$this->check_perms(EGW_ACL_EDIT,0,$event['owner'])) && !$this->check_perms(EGW_ACL_ADD,0,$event['owner'])) { + // Just update the status, if the user is in the event already + // is user is in both original and updated event + $egw_event = $this->read($event['id']); + + if ( isset($egw_event['participants'][$this->user]) + && isset($event['participants'][$this->user])) + { + // Update their status in the event and say we're done. + // Admittedly, this is false, it's dropping any changes on the floor, + // But this will work better than dropping -everything- silently on + // the floor + $this->set_status($event['id'],'u',$this->user,$event['participants'][$this->user],0); + unset($egw_event); + return $event['id']; + } return false; } // check for conflicts only happens !$ignore_conflicts AND if start + end date are given @@ -1017,4 +1032,65 @@ class bocalupdate extends bocal } return $this->so->delete_alarm($id); } + + var $app_cat; + var $glob_cat; + + function find_or_add_categories($catname_list) + { + if (!is_object($this->glob_cat)) + { + $this->glob_cat =& CreateObject('phpgwapi.categories',$GLOBALS['egw_info']['user']['account_id'],'phpgw'); + } + + if (!is_object($this->app_cat)) + { + $this->app_cat =& CreateObject('phpgwapi.categories',$GLOBALS['egw_info']['user']['account_id'],'calendar'); + } + + $cat_id_list = array(); + foreach($catname_list as $cat_name) + { + $cat_name = trim($cat_name); + if (!($cat_id = $this->glob_cat->name2id($cat_name)) + && !($cat_id = $this->app_cat->name2id($cat_name))) + { + $cat_id = $this->app_cat->add( array('name' => $cat_name,'descr' => $cat_name )); + } + + $cat_id_list[] = $cat_id; + } + + if (count($cat_id_list) > 1) + { + sort($cat_id_list, SORT_NUMERIC); + } + return $cat_id_list; + } + + function get_categories($cat_id_list) + { + if (!is_object($this->glob_cat)) + { + $this->glob_cat =& CreateObject('phpgwapi.categories',$GLOBALS['egw_info']['user']['account_id'],'phpgw'); + } + + if (!is_object($this->app_cat)) + { + $this->app_cat =& CreateObject('phpgwapi.categories',$GLOBALS['egw_info']['user']['account_id'],'calendar'); + } + + $cat_list = array(); + foreach(explode(',',$cat_id_list) as $cat_id) + { + if ( ($cat_data = $this->glob_cat->return_single($cat_id)) + || ($cat_data = $this->app_cat->return_single($cat_id)) ) + { + $cat_list[] = $cat_data[0]['name']; + } + } + + return $cat_list; + } + } diff --git a/calendar/inc/class.boical.inc.php b/calendar/inc/class.boical.inc.php index 066ffe7a9c..b00002c0bf 100644 --- a/calendar/inc/class.boical.inc.php +++ b/calendar/inc/class.boical.inc.php @@ -117,24 +117,38 @@ function &exportVCal($events,$version='1.0', $method='PUBLISH') { $egwSupportedFields = array( - 'CLASS' => array('dbName' => 'public'), - 'SUMMARY' => array('dbName' => 'title'), + 'CLASS' => array('dbName' => 'public'), + 'SUMMARY' => array('dbName' => 'title'), 'DESCRIPTION' => array('dbName' => 'description'), - 'LOCATION' => array('dbName' => 'location'), - 'DTSTART' => array('dbName' => 'start'), - 'DTEND' => array('dbName' => 'end'), - 'ORGANIZER' => array('dbName' => 'owner'), - 'ATTENDEE' => array('dbName' => 'participants'), - 'RRULE' => array('dbName' => 'recur_type'), - 'EXDATE' => array('dbName' => 'recur_exception'), - 'PRIORITY' => array('dbName' => 'priority'), - 'TRANSP' => array('dbName' => 'non_blocking'), + 'LOCATION' => array('dbName' => 'location'), + 'DTSTART' => array('dbName' => 'start'), + 'DTEND' => array('dbName' => 'end'), + 'ORGANIZER' => array('dbName' => 'owner'), + 'ATTENDEE' => array('dbName' => 'participants'), + 'RRULE' => array('dbName' => 'recur_type'), + 'EXDATE' => array('dbName' => 'recur_exception'), + 'PRIORITY' => array('dbName' => 'priority'), + 'TRANSP' => array('dbName' => 'non_blocking'), 'CATEGORIES' => array('dbName' => 'category'), ); if(!is_array($this->supportedFields)) { $this->setSupportedFields(); } + + if($this->productManufacturer == '' ) + { // syncevolution is broken + $version = "2.0"; + } + + $palm_enddate_workaround=False; + if($this->productManufacturer == 'Synthesis AG' + && strpos($this->productName, "PalmOS") ) + { + // This workaround adds 1 day to the recur_enddate if it exists, to fix a palm bug + $palm_enddate_workaround=True; + } + $vcal = &new Horde_iCalendar; $vcal->setAttribute('PRODID','-//eGroupWare//NONSGML eGroupWare Calendar '.$GLOBALS['egw_info']['apps']['calendar']['version'].'//'. strtoupper($GLOBALS['egw_info']['user']['preferences']['common']['lang'])); @@ -186,7 +200,22 @@ // PARTSTAT={NEEDS-ACTION|ACCEPTED|DECLINED|TENTATIVE|DELEGATED|COMPLETED|IN-PROGRESS} everything from delegated is NOT used by eGW atm. $status = $this->status_egw2ical[$status]; // CUTYPE={INDIVIDUAL|GROUP|RESOURCE|ROOM|UNKNOWN} - $cutype = $GLOBALS['egw']->accounts->get_type($uid) == 'g' ? 'GROUP' : 'INDIVIDUAL'; + switch (is_nummeric($uid) ? $GLOBALS['egw']->accounts->get_type($uid) : $uid{0}) + { + case 'g': + $cutype = 'GROUP'; + break; + case 'r': + $cutype = 'RESOURCE'; + break; + case 'u': + $cutype = 'INDIVIDUAL'; + break; + default: + $cutype = 'UNKNOWN'; + $cutype = 'INDIVIDUAL'; + break; + }; $parameters['ATTENDEE'][] = array( 'CN' => $cn, 'ROLE' => $role, @@ -242,7 +271,20 @@ $rrule['FREQ'] = $rrule['FREQ'].' '.$rrule['BYDAY']; break; } - $rrule['UNTIL'] = ($event['recur_enddate']) ? date('Ymd',$event['recur_enddate']).'T'.date('His',$event['start']) : '#0'; + + if ($event['recur_enddate']) + { + $recur_enddate = (int)$event['recur_enddate']; + if ($palm_enddate_workaround) + { + $recur_enddate += 86400; + } + $rrule['UNTIL'] = date('Ymd',$recur_enddate); + } + else + { + $rrule['UNTIL'] = '#0'; + } $attributes['RRULE'] = $rrule['FREQ'].' '.$rrule['UNTIL']; } else { @@ -289,7 +331,7 @@ { $days[] = date('Ymd',$day); } - $attributes['EXDATE'] = implode(';',$days); + $attributes['EXDATE'] = implode(',',$days); $parameters['EXDATE']['VALUE'] = 'DATE'; } break; @@ -309,9 +351,10 @@ case 'CATEGORIES': if ($event['category']) { - $attributes['CATEGORIES'] = implode(',',$this->categories($event['category'],$nul)); + $attributes['CATEGORIES'] = implode(',',$this->get_categories($event['category'])); } break; + default: if ($event[$egwFieldInfo['dbName']]) // dont write empty fields { @@ -394,6 +437,13 @@ } //echo "supportedFields="; _debug_array($this->supportedFields); + $syncevo_enddate_fix = False; + if( $this->productManufacturer == '' && $this->productName == '' ) + { + // syncevolution needs an adjusted recur_enddate + $syncevo_enddate_fix = True; + } + $Ok = false; // returning false, if file contains no components foreach($vcal->getComponents() as $component) { @@ -403,7 +453,10 @@ #$event = array('participants' => array()); $event = array(); $alarms = array(); - $vcardData = array('recur_type' => 0); + $vcardData = array( + 'recur_type' => MCAL_RECUR_NONE, + 'recur_exception' => array(), + ); // lets see what we can get from the vcard foreach($component->_attributes as $attributes) @@ -429,6 +482,11 @@ $alarms[$alarmTime] = array( 'time' => $alarmTime ); + } elseif (preg_match('/(........T......)$/',$attributes['value'],$matches)) { + $alarmTime = $vcal->_parseDateTime($attributes['value']); + $alarms[$alarmTime] = array( + 'time' => $alarmTime + ); } break; case 'CLASS': @@ -440,7 +498,7 @@ case 'DTEND': $dtend_ts = is_numeric($attributes['value']) ? $attributes['value'] : $this->date2ts($attributes['value']); if(date('H:i:s',$dtend_ts) == '00:00:00') { - $dtend_ts--; + $dtend_ts -= 60; } $vcardData['end'] = $dtend_ts; break; @@ -458,9 +516,14 @@ { $vcardData['recur_enddate'] = $vcal->_parseDateTime($matches[1]); } + elseif (preg_match('/COUNT=([0-9]+)/',$recurence,$matches)) + { + $vcardData['recur_count'] = (int)$matches[1]; + } if (preg_match('/INTERVAL=([0-9]+)/',$recurence,$matches)) { - $vcardData['recur_interval'] = (int) $matches[1]; + // 1 is invalid,, egw uses 0 for interval + $vcardData['recur_interval'] = (int) $matches[1] != 0 ? (int) $matches[1] : 0; } $vcardData['recur_data'] = 0; switch($type) @@ -492,6 +555,14 @@ } $vcardData['recur_type'] = MCAL_RECUR_WEEKLY; } + + if (!empty($vcardData['recur_count'])) + { + $vcardData['recur_enddate'] = mktime(0,0,0, + date('m',$vcardData['start']), + date('d',$vcardData['start']) + ($vcardData['recur_interval']*($vcardData['recur_count']-1)*7), + date('Y',$vcardData['start'])); + } break; case 'D': // 1.0 @@ -518,6 +589,14 @@ // fall-through case 'DAILY': // 2.0 $vcardData['recur_type'] = MCAL_RECUR_DAILY; + + if (!empty($vcardData['recur_count'])) + { + $vcardData['recur_enddate'] = mktime(0,0,0, + date('m',$vcardData['start']), + date('d',$vcardData['start']) + ($vcardData['recur_interval']*($vcardData['recur_count']-1)), + date('Y',$vcardData['start'])); + } break; case 'M': @@ -551,6 +630,14 @@ case 'MONTHLY': $vcardData['recur_type'] = strpos($recurence,'BYDAY') !== false ? MCAL_RECUR_MONTHLY_WDAY : MCAL_RECUR_MONTHLY_MDAY; + + if (!empty($vcardData['recur_count'])) + { + $vcardData['recur_enddate'] = mktime(0,0,0, + date('m',$vcardData['start']) + ($vcardData['recur_interval']*($vcardData['recur_count']-1)), + date('d',$vcardData['start']), + date('Y',$vcardData['start'])); + } break; case 'Y': // 1.0 @@ -577,11 +664,24 @@ // fall-through case 'YEARLY': // 2.0 $vcardData['recur_type'] = MCAL_RECUR_YEARLY; + + if (!empty($vcardData['recur_count'])) + { + $vcardData['recur_enddate'] = mktime(0,0,0, + date('m',$vcardData['start']), + date('d',$vcardData['start']), + date('Y',$vcardData['start']) + ($vcardData['recur_interval']*($vcardData['recur_count']-1))); + } break; } + if( $syncevo_enddate_fix && $vcardData['recur_enddate'] ) + { + // Does syncevolution need to adjust recur_enddate + $vcardData['recur_enddate'] = (int)$vcardData['recur_enddate'] + 86400; + } break; case 'EXDATE': - $vcardData['recur_exception'] = $attributes['value']; + $vcardData['recur_exception'] = array_merge($vcardData['recur_exception'],$attributes['value']); break; case 'SUMMARY': $vcardData['title'] = $attributes['value']; @@ -605,26 +705,14 @@ $vcardData['priority'] = (int) $this->priority_ical2egw[$attributes['value']]; break; case 'CATEGORIES': - $vcardData['category'] = array(); if ($attributes['value']) { - if (!is_object($this->cat)) - { - if (!is_object($GLOBALS['egw']->categories)) - { - $GLOBALS['egw']->categories =& CreateObject('phpgwapi.categories',$this->owner,'calendar'); - } - $this->cat =& $GLOBALS['egw']->categories; - } - foreach(explode(',',$attributes['value']) as $cat_name) - { - if (!($cat_id = $this->cat->name2id($cat_name))) - { - $cat_id = $this->cat->add( array('name' => $cat_name,'descr' => $cat_name )); - } - $vcardData['category'][] = $cat_id; - } + $vcardData['category'] = $this->find_or_add_categorie(explode(',',$attributes['value'])); } + else + { + $vcardData['category'] = array(); + } break; case 'ATTENDEE': if (preg_match('/MAILTO:([@.a-z0-9_-]+)/i',$attributes['value'],$matches) && @@ -734,6 +822,38 @@ $event['participants'] = array($GLOBALS['egw_info']['user']['account_id'] => 'A'); } + // If this is an updated meeting, and the client doesn't support + // participants, add them back + if( $cal_id >0 && !isset($this->supportedFields['participants'])) + { + $egw_event = $this->read($cal_id); + if ($egw_event) + { + $event['participants'] = $egw_event['participants']; + $event['participant_types'] = $egw_event['participant_types']; + } + } + + // Check for resources, and don't remove them + if( $cal_id > 0 ) + { + // for each existing participant: + $egw_event = $this->read($cal_id); + if ( $egw_event ) + { + foreach( $egw_event['participants'] as $uid => $status ) + { + // Is it a resource? + if ( preg_match("/^r(.*)/", $uid, $matches) ) + { + // Add it back in + $event['participants'][$uid] = 'A'; + $event['participant_types']['r'][$matches[1]] = 'A'; + } + } + } + } + #error_log('ALARMS'); #error_log(print_r($event, true)); @@ -773,28 +893,55 @@ $this->productManufacturer = $_productManufacturer; $this->productName = $_productName; - $defaultFields[0] = array('public' => 'public', 'description' => 'description', 'end' => 'end', - 'start' => 'start', 'location' => 'location', 'recur_type' => 'recur_type', - 'recur_interval' => 'recur_interval', 'recur_data' => 'recur_data', 'recur_enddate' => 'recur_enddate', - 'title' => 'title', 'priority' => 'priority', 'alarms' => 'alarms', + $defaultFields['minimal'] = array( + 'public' => 'public', + 'description' => 'description', + 'end' => 'end', + 'start' => 'start', + 'location' => 'location', + 'recur_type' => 'recur_type', + 'recur_interval' => 'recur_interval', + 'recur_data' => 'recur_data', + 'recur_enddate' => 'recur_enddate', + 'title' => 'title', + 'alarms' => 'alarms', + ); + $defaultFields['basic'] = $defaultFields['minimal'] + array( + 'recur_exception' => 'recur_exception', + 'priority' => 'priority', + ); + + $defaultFields['nexthaus'] = $defaultFields['basic'] + array( + 'participants' => 'participants', ); - $defaultFields[1] = array('public' => 'public', 'description' => 'description', 'end' => 'end', - 'start' => 'start', 'location' => 'location', 'recur_type' => 'recur_type', - 'recur_interval' => 'recur_interval', 'recur_data' => 'recur_data', 'recur_enddate' => 'recur_enddate', - 'title' => 'title', 'alarms' => 'alarms', - + $defaultFields['synthesis'] = $defaultFields['basic'] + array( + 'non_blocking' => 'non_blocking', + 'category' => 'category', ); + $defaultFields['evolution'] = $defaultFields['basic'] + array( + 'participants' => 'participants', + 'owner' => 'owner', + 'category' => 'category', + ); + + $defaultFields['full'] = $defaultFields['basic'] + array( + 'participants' => 'participants', + 'owner' => 'owner', + 'category' => 'category', + 'non_blocking' => 'non_blocking', + ); + + switch(strtolower($_productManufacturer)) { case 'nexthaus corporation': switch(strtolower($_productName)) { default: - $this->supportedFields = $defaultFields[0] + array('participants' => 'participants'); - #$this->supportedFields = $defaultFields; + $this->supportedFields = $defaultFields['nexthaus']; break; } break; @@ -805,7 +952,7 @@ switch(strtolower($_productName)) { default: - $this->supportedFields = $defaultFields[0]; + $this->supportedFields = $defaultFields['basic']; break; } break; @@ -814,18 +961,26 @@ switch(strtolower($_productName)) { case 'e61': + $this->supportedFields = $defaultFields['minimal']; + break; default: - $this->supportedFields = $defaultFields[1]; + error_log("Unknown Nokia phone '$_productName', assuming E61"); + $this->supportedFields = $defaultFields['minimal']; break; } break; case 'sonyericsson': + case 'sony ericsson': switch(strtolower($_productName)) { case 'd750i': + case 'p910i': + $this->supportedFields = $defaultFields['basic']; + break; default: - $this->supportedFields = $defaultFields[0]; + error_log("Unknown Sony Ericsson phone '$_productName' assuming d750i"); + $this->supportedFields = $defaultFields['basic']; break; } break; @@ -834,41 +989,35 @@ switch(strtolower($_productName)) { default: - $this->supportedFields = $defaultFields[0] + array( - 'recur_exception' => 'recur_exception', - 'non_blocking' => 'non_blocking', - ); + $this->supportedFields = $defaultFields['synthesis']; break; } break; //Syncevolution compatibility case 'patrick ohly': - $this->supportedFields = $defaultFields[1] + array( - 'participants' => 'participants', - 'owner' => 'owner', - 'category' => 'category', - ); + $this->supportedFields = $defaultFields['evolution']; + break; + + case '': // seems syncevolution 0.5 doesn't send a manufacturer + error_log("No vendor name, assuming syncevolution 0.5"); + $this->supportedFields = $defaultFields['evolution']; break; case 'file': // used outside of SyncML, eg. by the calendar itself ==> all possible fields - $this->supportedFields = $defaultFields[0] + array( - 'participants' => 'participants', - 'owner' => 'owner', - 'non_blocking' => 'non_blocking', - 'category' => 'category', - ); + $this->supportedFields = $defaultFields['full']; break; // the fallback for SyncML default: - error_log("Client not found: $_productManufacturer $_productName"); - $this->supportedFields = $defaultFields[0]; + error_log("Unknown calendar SyncML client: manufacturer='$_productManufacturer' product='$_productName'"); + $this->supportedFields = $defaultFields['full']; break; } } - function icaltoegw($_vcalData) { + function icaltoegw($_vcalData) + { // our (patched) horde classes, do NOT unfold folded lines, which causes a lot trouble in the import $_vcalData = preg_replace("/[\r\n]+ /",'',$_vcalData); @@ -1043,26 +1192,14 @@ } break; case 'CATEGORIES': - $vcardData['category'] = array(); if ($attributes['value']) { - if (!is_object($this->cat)) - { - if (!is_object($GLOBALS['egw']->categories)) - { - $GLOBALS['egw']->categories =& CreateObject('phpgwapi.categories',$this->owner,'calendar'); - } - $this->cat =& $GLOBALS['egw']->categories; - } - foreach(explode(',',$attributes['value']) as $cat_name) - { - if (!($cat_id = $this->cat->name2id($cat_name))) - { - $cat_id = $this->cat->add( array('name' => $cat_name,'descr' => $cat_name )); - } - $vcardData['category'][] = $cat_id; - } - } + $vcardData['category'] = $this->find_or_add_categories(explode(',',$attributes['value'])); + } + else + { + $vcardData['category'] = array(); + } break; case 'ATTENDEE': if (preg_match('/MAILTO:([@.a-z0-9_-]+)/i',$attributes['value'],$matches) && @@ -1114,6 +1251,10 @@ { switch($fieldName) { + case 'recur_interval': + case 'recur_enddate': + case 'recur_data': + case 'recur_exception': case 'alarms': // not handled here break; @@ -1129,17 +1270,12 @@ } } } - unset($supportedFields['recur_type']); - unset($supportedFields['recur_interval']); - unset($supportedFields['recur_enddate']); - unset($supportedFields['recur_data']); break; default: if (isset($vcardData[$fieldName])) { $event[$fieldName] = $vcardData[$fieldName]; } - unset($supportedFields[$fieldName]); break; } } @@ -1212,20 +1348,24 @@ { $vfreebusy->setAttribute($attr, $value, $parameters[$name]); } - foreach(parent::search(array( - 'start' => $this->now_su, - 'end' => $end, - 'users' => $user, - 'date_format' => 'server', - 'show_rejected' => false, - )) as $event) + $fbdata = parent::search(array( + 'start' => $this->now_su, + 'end' => $end, + 'users' => $user, + 'date_format' => 'server', + 'show_rejected' => false, + )); + if (is_array($fbdata)) { - if ($event['non_blocking']) continue; + foreach ($fbdata as $event) + { + if ($event['non_blocking']) continue; - $vfreebusy->setAttribute('FREEBUSY',array(array( - 'start' => $event['start'], - 'end' => $event['end'], - ))); + $vfreebusy->setAttribute('FREEBUSY',array(array( + 'start' => $event['start'], + 'end' => $event['end'], + ))); + } } $vcal->addComponent($vfreebusy); diff --git a/calendar/inc/class.sifcalendar.inc.php b/calendar/inc/class.sifcalendar.inc.php index 539ea3ae7c..0a5a8b3617 100644 --- a/calendar/inc/class.sifcalendar.inc.php +++ b/calendar/inc/class.sifcalendar.inc.php @@ -71,7 +71,7 @@ } function endElement($_parser, $_tag) { - #error_log($_tag .' => '. $this->sifData); + //error_log('endElem: ' . $_tag .' => '. trim($this->sifData)); if(!empty($this->sifMapping[$_tag])) { $this->event[$this->sifMapping[$_tag]] = trim($this->sifData); } @@ -128,19 +128,7 @@ case 'category': if(!empty($value)) { - $egwCategories =& CreateObject('phpgwapi.categories', $GLOBALS['egw_info']['user']['account_id'], 'calendar'); - $categories = explode(';',$value); - foreach($categories as $categorieName) { - $cat_id = false; - $categorieName = trim($categorieName); - if(!($cat_id = $egwCategories->name2id($categorieName))) { - $cat_id = $egwCategories->add(array('name' => $categorieName, 'descr' => lang('added by synchronisation'))); - } - if($cat_id) { - if(!empty($finalEvent[$key])) $finalEvent[$key] .= ','; - $finalEvent[$key] .= $cat_id; - } - } + $finalEvent[$key] = implode(',',$this->find_or_add_categories(explode(';', $value))); } break; @@ -148,6 +136,7 @@ case 'start': if($this->event['alldayevent'] < 1) { $finalEvent[$key] = $vcal->_parseDateTime($value); + error_log("event ".$key." val=".$value.", parsed=".$finalEvent[$key]); } break; @@ -348,15 +337,8 @@ { case 'Categories': if(!empty($value)) { - $egwCategories =& CreateObject('phpgwapi.categories',$GLOBALS['egw_info']['user']['account_id'],'calendar'); - $categories = explode(',',$value); - $value = ''; - foreach($categories as $cat_id) { - if($catData = $egwCategories->return_single($cat_id)) { - if(!empty($value)) $value .= '; '; - $value .= $GLOBALS['egw']->translation->convert($catData[0]['name'], $sysCharSet, 'utf-8'); - } - } + $value = implode('; ', $this->get_categories(explode(',',$value))); + $value = $GLOBALS['egw']->translation->convert($value, $sysCharSet, 'utf-8'); } $sifEvent .= "<$sifField>$value$sifField>"; break; diff --git a/calendar/inc/class.socal.inc.php b/calendar/inc/class.socal.inc.php index e954892480..924a8070bf 100644 --- a/calendar/inc/class.socal.inc.php +++ b/calendar/inc/class.socal.inc.php @@ -563,7 +563,7 @@ ORDER BY cal_user_type, cal_usre_id $exceptions = $event['recur_exception'] ? explode(',',$event['recur_exception']) : array(); $set_recurrences = $event['recur_type'] != $old_recur['recur_type'] || $event['recur_data'] != $old_recur['recur_data'] || $event['recur_interval'] != $old_recur['recur_interval'] || $event['recur_enddate'] != $old_recur['recur_enddate'] || - count(array_diff($old_exceptions,$exceptions)); // exception deleted + count(array_diff($old_exceptions,$exceptions)) || count(array_diff($exceptions, $old_exceptions)); // exception deleted or added } if($event['recur_type'] != MCAL_RECUR_NONE) { diff --git a/infolog/inc/class.boinfolog.inc.php b/infolog/inc/class.boinfolog.inc.php index 5854eba533..845c68c2c1 100644 --- a/infolog/inc/class.boinfolog.inc.php +++ b/infolog/inc/class.boinfolog.inc.php @@ -1270,7 +1270,75 @@ class boinfolog } return $icons; } - + + var $app_cat; + var $glob_cat; + + function find_or_add_categories($catname_list) + { + if (!is_object($this->glob_cat)) + { + if (!is_object($GLOBALS['egw']->categories)) + { + $GLOBALS['egw']->categories =& CreateObject('phpgwapi.categories',$GLOBALS['egw_info']['user']['account_id'],'phpgw'); + } + $this->glob_cat =& $GLOBALS['egw']->categories; + } + + if (!is_object($this->app_cat)) + { + $this->app_cat =& CreateObject('phpgwapi.categories',$GLOBALS['egw_info']['user']['account_id'],'infolog'); + } + + $cat_id_list = array(); + foreach($catname_list as $cat_name) + { + $cat_name = trim($cat_name); + if (!($cat_id = $this->glob_cat->name2id($cat_name)) + && !($cat_id = $this->app_cat->name2id($cat_name))) + { + $cat_id = $this->app_cat->add( array('name' => $cat_name,'descr' => $cat_name )); + } + + $cat_id_list[] = $cat_id; + } + + if (count($cat_id_list) > 1) + { + sort($cat_id_list, SORT_NUMERIC); + } + return $cat_id_list; + } + + function get_categories($cat_id_list) + { + if (!is_object($this->glob_cat)) + { + if (!is_object($GLOBALS['egw']->categories)) + { + $GLOBALS['egw']->categories =& CreateObject('phpgwapi.categories',$GLOBALS['egw_info']['user']['account_id'],'phpgw'); + } + $this->glob_cat =& $GLOBALS['egw']->categories; + } + + if (!is_object($this->app_cat)) + { + $this->app_cat =& CreateObject('phpgwapi.categories',$GLOBALS['egw_info']['user']['account_id'],'infolog'); + } + + $cat_list = array(); + foreach(explode(',',$cat_id_list) as $cat_id) + { + if ( ($cat_data = $this->glob_cat->return_single($cat_id)) + || ($cat_data = $this->app_cat->return_single($cat_id)) ) + { + $cat_list[] = $cat_data[0]['name']; + } + } + + return $cat_list; + } + /** * Send all async infolog notification * @@ -1352,3 +1420,4 @@ class boinfolog $GLOBALS['egw_info']['user']['preferences'] = $save_prefs; } } + diff --git a/infolog/inc/class.sifinfolog.inc.php b/infolog/inc/class.sifinfolog.inc.php index 5b18401afb..185c36ff8a 100644 --- a/infolog/inc/class.sifinfolog.inc.php +++ b/infolog/inc/class.sifinfolog.inc.php @@ -21,12 +21,24 @@ // array containing the current mappings(task or note) var $_currentSIFMapping; + var $_sifNoteMapping = array( + 'Body' => 'info_des', + 'Categories' => 'info_cat', + 'Color' => '', + 'Date' => 'info_startdate', + 'Height' => '', + 'Left' => '', + 'Subject' => 'info_subject', + 'Top' => '', + 'Width' => '', + ); + // mappings for SIFTask to InfologTask var $_sifTaskMapping = array( 'ActualWork' => '', 'BillingInformation' => '', 'Body' => 'info_des', - 'Categories' => '', + 'Categories' => 'info_cat', 'Companies' => '', 'Complete' => '', 'DateCompleted' => 'info_datecompleted', @@ -61,8 +73,9 @@ } function endElement($_parser, $_tag) { + error_log("infolog: tag=$_tag data=".trim($this->sifData)); if(!empty($this->_currentSIFMapping[$_tag])) { - $this->_extractedSIFData[$this->_currentSIFMapping[$_tag]] = $this->sifData; + $this->_extractedSIFData[$this->_currentSIFMapping[$_tag]] = trim($this->sifData); } unset($this->sifData); } @@ -81,7 +94,18 @@ #fwrite($handle, $sifData); #fclose($handle); - $this->_currentSIFMapping = $this->_sifTaskMapping; + switch ($_sifType) + { + case 'note': + $this->_currentSIFMapping = $this->_sifNoteMapping; + break; + + case 'task': + default: + $this->_currentSIFMapping = $this->_sifTaskMapping; + break; + } + $this->xml_parser = xml_parser_create('UTF-8'); xml_set_object($this->xml_parser, $this); xml_parser_set_option($this->xml_parser, XML_OPTION_CASE_FOLDING, false); @@ -108,6 +132,8 @@ foreach($this->_extractedSIFData as $key => $value) { $value = $GLOBALS['egw']->translation->convert($value, 'utf-8', $sysCharSet); + error_log("infolog key=$key => value=$value"); + switch($key) { case 'info_access': $taskData[$key] = ((int)$value > 0) ? 'private' : 'public'; @@ -127,6 +153,13 @@ break; + case 'info_cat': + if (!empty($value)) { + $categories = $this->find_or_add_categories(explode(';', $value)); + $taskData['info_cat'] = $categories[0]; + } + break; + case 'info_priority': $taskData[$key] = (int)$value; break; @@ -156,11 +189,52 @@ $taskData[$key] = $value; break; } + error_log("infolog task key=$key => value=".$taskData[$key]); } return $taskData; break; + case 'note': + $noteData = array(); + $noteData['info_type'] = 'note'; + $vcal = &new Horde_iCalendar; + + foreach($this->_extractedSIFData as $key => $value) + { + $value = $GLOBALS['egw']->translation->convert($value, 'utf-8', $sysCharSet); + + error_log("infolog client key=$key => value=".$value); + switch ($key) + { + case 'info_startdate': + if(!empty($value)) { + $noteData[$key] = $vcal->_parseDateTime($value); + // somehow the client always deliver a timestamp about 3538 seconds, when no startdate set. + if($noteData[$key] < 10000) + $noteData[$key] = ''; + } else { + $noteData[$key] = ''; + } + break; + + case 'info_cat': + if (!empty($value)) { + $categories = $this->find_or_add_categories(explode(';', $value)); + $taskData['info_cat'] = $categories[0]; + } + break; + + default: + $noteData[$key] = $value; + break; + } + error_log("infolog note key=$key => value=".$noteData[$key]); + } + return $noteData; + break; + + default: return false; } @@ -251,6 +325,15 @@ $sifTask .= "<$sifField>$value$sifField>"; break; + case 'Categories': + if (!empty($value)) + { + $value = implode('; ', $this->get_categories(array($value))); + $value = $GLOBALS['egw']->translation->convert($value, $sysCharSet, 'utf-8'); + } + $sifTask .= "<$sifField>$value$sifField>"; + break; + default: $sifTask .= "<$sifField>$value$sifField>"; break; @@ -296,6 +379,52 @@ } break; + case 'note': + if($taskData = $this->read($_id)) { + $sysCharSet = $GLOBALS['egw']->translation->charset(); + $vcal = &new Horde_iCalendar; + + $sifNote = '