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"; break; @@ -270,7 +253,7 @@ class sifaddressbook extends bocontacts break; default: - $sifContact .= "<$sifField>$value"; + $sifContact .= "<$sifField>".trim($value).""; 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"; 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"; 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"; + break; + default: $sifTask .= "<$sifField>$value"; break; @@ -296,6 +379,52 @@ } break; + case 'note': + if($taskData = $this->read($_id)) { + $sysCharSet = $GLOBALS['egw']->translation->charset(); + $vcal = &new Horde_iCalendar; + + $sifNote = ''; + + foreach($this->_sifNoteMapping as $sifField => $egwField) + { + if(empty($egwField)) continue; + + $value = $GLOBALS['egw']->translation->convert($taskData[$egwField], $sysCharSet, 'utf-8'); + + switch($sifField) { + case 'Date': + if(!empty($value)) { + $value = $vcal->_exportDateTime($value); + } + $sifNote .= "<$sifField>$value"; + break; + + case 'Body': + $value = $GLOBALS['egw']->translation->convert($taskData['info_subject'], $sysCharSet, 'utf-8') . "\n" . $value; + $sifNote .= "<$sifField>$value"; + break; + + case 'Categories': + if (!empty($value)) + { + $value = implode('; ', $this->get_categories(array($value))); + $value = $GLOBALS['egw']->translation->convert($value, $sysCharSet, 'utf-8'); + } + $sifNote .= "<$sifField>$value"; + break; + + + default: + $sifNote .= "<$sifField>$value"; + break; + } + } + + return base64_encode($sifNote); + } + break; + default; return false; } diff --git a/infolog/inc/class.soinfolog.inc.php b/infolog/inc/class.soinfolog.inc.php index 2533271ccc..bc6acdb287 100644 --- a/infolog/inc/class.soinfolog.inc.php +++ b/infolog/inc/class.soinfolog.inc.php @@ -186,7 +186,7 @@ class soinfolog // DB-Layer */ function aclFilter($filter = False) { - preg_match('/(my|responsible|delegated|own|privat|all|none|user)([0-9]*)/',$filter_was=$filter,$vars); + preg_match('/(my|responsible|delegated|own|privat|private|all|none|user)([0-9]*)/',$filter_was=$filter,$vars); $filter = $vars[1]; $f_user = intval($vars[2]); @@ -230,7 +230,7 @@ class soinfolog // DB-Layer $filtermethod .= " OR (".$this->responsible_filter($this->user)." AND info_access='public')"; // private: own entries plus the one user is responsible for - if ($filter == 'private' || $filter == 'own') + if ($filter == 'private' || $filter == 'privat' || $filter == 'own') { $filtermethod .= " OR (".$this->responsible_filter($this->user). ($filter == 'own' && count($public_user_list) ? // offer's should show up in own, eg. startpage, but need read-access diff --git a/infolog/inc/class.vcalinfolog.inc.php b/infolog/inc/class.vcalinfolog.inc.php index cf5f61cd7e..22d6f341cf 100644 --- a/infolog/inc/class.vcalinfolog.inc.php +++ b/infolog/inc/class.vcalinfolog.inc.php @@ -86,6 +86,12 @@ $this->status2vtodo[$taskData['info_status']] : 'NEEDS-ACTION'); $vevent->setAttribute('PRIORITY',$this->egw_priority2vcal_priority[$taskData['info_priority']]); + if (!empty($taskData['info_cat'])) + { + $cats = $this->get_categories(array($taskData['info_cat'])); + $vevent->setAttribute('CATEGORIES', $cats[0]); + } + #$vevent->setAttribute('TRANSP','OPAQUE'); # status # ATTENDEE @@ -122,7 +128,8 @@ return $this->write($taskData); } - function searchVTODO($_vcalData) { + function searchVTODO($_vcalData) + { if(!$egwData = $this->vtodotoegw($_vcalData)) { return false; } @@ -140,7 +147,8 @@ return false; } - function vtodotoegw($_vcalData) { + function vtodotoegw($_vcalData) + { $vcal = &new Horde_iCalendar; if(!$vcal->parsevCalendar($_vcalData)) { return FALSE; @@ -196,6 +204,13 @@ case 'SUMMARY': $taskData['info_subject'] = $attributes['value']; break; + + case 'CATEGORIES': + { + $cats = $this->find_or_add_categories(explode(',', $attributes['value'])); + $taskData['info_cat'] = $cats[0]; + } + break; } } # the horde ical class does already convert in parsevCalendar @@ -207,4 +222,147 @@ } return FALSE; } + + function exportVNOTE($_noteID, $_type) + { + $note = $this->read($_noteID); + $note = $GLOBALS['egw']->translation->convert($note, $GLOBALS['egw']->translation->charset(), 'UTF-8'); + + switch($_type) + { + case 'text/plain': + $txt = $note['info_subject']."\n\n".$note['info_des']; + return $txt; + break; + + case 'text/x-vnote': + $noteGUID = $GLOBALS['egw']->common->generate_uid('infolog_note',$_noteID); + $vnote = &new Horde_iCalendar_vnote(); + $vNote->setAttribute('VERSION', '1.1'); + $vnote->setAttribute('SUMMARY',$note['info_subject']); + $vnote->setAttribute('BODY',$note['info_des']); + if($note['info_startdate']) + $vnote->setAttribute('DCREATED',$note['info_startdate']); + $vnote->setAttribute('DCREATED',$GLOBALS['egw']->contenthistory->getTSforAction($eventGUID,'add')); + $vnote->setAttribute('LAST-MODIFIED',$GLOBALS['egw']->contenthistory->getTSforAction($eventGUID,'modify')); + if (!empty($note['info_cat'])) + { + $cats = $this->get_categories(array($note['info_cat'])); + $vnote->setAttribute('CATEGORIES', $cats[0]); + } + + #$vnote->setAttribute('UID',$noteGUID); + #$vnote->setAttribute('CLASS',$taskData['info_access'] == 'public' ? 'PUBLIC' : 'PRIVATE'); + + #$options = array('CHARSET' => 'UTF-8','ENCODING' => 'QUOTED-PRINTABLE'); + #$vnote->setParameter('SUMMARY', $options); + #$vnote->setParameter('DESCRIPTION', $options); + + return $vnote->exportvCalendar(); + break; + } + return false; + } + + function importVNOTE(&$_vcalData, $_type, $_noteID = -1) + { + if(!$note = $this->vnotetoegw($_vcalData, $_type)) + { + return false; + } + + if($_noteID > 0) + { + $note['info_id'] = $_noteID; + } + + if(empty($note['info_status'])) { + $note['info_status'] = 'done'; + } + + #_debug_array($taskData);exit; + return $this->write($note); + } + + function searchVNOTE($_vcalData, $_type) + { + if(!$note = $this->vnotetoegw($_vcalData)) { + return false; + } + + $filter = array('col_filter' => $egwData); + if($foundItems = $this->search($filter)) { + if(count($foundItems) > 0) { + $itemIDs = array_keys($foundItems); + return $itemIDs[0]; + } + } + + return false; + } + + function vnotetoegw($_data, $_type) + { + switch($_type) + { + case 'text/plain': + $note = array(); + $note['info_type'] = 'note'; + $botranslation =& CreateObject('phpgwapi.translation'); + $txt = $botranslation->convert($_data, 'utf-8'); + $txt = str_replace("\r\n", "\n", $txt); + + if (preg_match("/^(^\n)\n\n(.*)$/", $txt, $match)) + { + $note['info_subject'] = $match[0]; + $note['info_des'] = $match[1]; + } + else + { + $note['info_des'] = $txt; + } + + return $note; + break; + + case 'text/x-vnote': + $vnote = &new Horde_iCalendar; + if (!$vcal->parsevCalendar($_data)) + { + return FALSE; + } + $components = $vnote->getComponent(); + if(count($components) > 0) + { + $component = $components[0]; + if(is_a($component, 'Horde_iCalendar_vnote')) + { + $note = array(); + $note['info_type'] = 'note'; + + foreach($component->_attributes as $attribute) + { + switch ($attribute['name']) + { + case 'BODY': + $note['info_des'] = $attribute['value']; + break; + case 'SUMMARY': + $note['info_subject'] = $attribute['value']; + break; + case 'CATEGORIES': + { + $cats = $this->find_or_add_categories(explode(',', $attribute['value'])); + $note['info_cat'] = $cats[0]; + } + break; + } + } + } + return $note; + } + } + return FALSE; + } } + diff --git a/phpgwapi/inc/horde/Horde/SyncML/Command/Put.php b/phpgwapi/inc/horde/Horde/SyncML/Command/Put.php index 5d0c5d48ba..c87b8a83b3 100644 --- a/phpgwapi/inc/horde/Horde/SyncML/Command/Put.php +++ b/phpgwapi/inc/horde/Horde/SyncML/Command/Put.php @@ -134,6 +134,49 @@ class Horde_SyncML_Command_Put extends Horde_SyncML_Command { switch($element) { case 'CTType': $this->_contentType = trim($this->_chars); + if (substr($this->_contentType, 0, 14) == "text/x-s4j-sif") + { + // workaround a little bug in sync4j for mobile v3.1.3 (and possibly others) + // where the content-type is set to just one value regardless of + // the source... this further leads to a failure to send updates + // by the server since it does not know how to convert say tasks to text/x-s4j-sifc + // (it should be text/x-s4j-sift). + switch ($this->_sourceReference) + { + case 'contact': + if ($this->_contentType != "text/x-s4j-sifc") + { + error_log("forcing 'contact' content type to 'text/x-s4j-sifc' instead of '".$this->_contentType."'"); + $this->_contentType = "text/x-s4j-sifc"; + } + break; + case 'calendar': + case 'appointment': + if ($this->_contentType != "text/x-s4j-sife") + { + error_log("forcing 'calendar' content type to 'text/x-s4j-sife' instead of '".$this->_contentType."'"); + $this->_contentType = "text/x-s4j-sife"; + } + break; + case 'task': + if ($this->_contentType != "text/x-s4j-sift") + { + error_log("forcing 'task' content type to 'text/x-s4j-sift' instead of '".$this->_contentType."'"); + $this->_contentType = "text/x-s4j-sift"; + } + break; + case 'note': + if ($this->_contentType != "text/x-s4j-sifn") + { + error_log("forcing 'note' content type to 'text/x-s4j-sifn' instead of '".$this->_contentType."'"); + $this->_contentType = "text/x-s4j-sifn"; + } + break; + default: + #error_log("Leaving ContentType='".$this->_contentType."' as is for source '".$this->_sourceReference."'"); + break; + } + } break; case 'SyncType': diff --git a/phpgwapi/inc/horde/Horde/SyncML/Command/Results.php b/phpgwapi/inc/horde/Horde/SyncML/Command/Results.php index 42278fc562..40f1c68952 100644 --- a/phpgwapi/inc/horde/Horde/SyncML/Command/Results.php +++ b/phpgwapi/inc/horde/Horde/SyncML/Command/Results.php @@ -40,17 +40,18 @@ class Horde_SyncML_Command_Results extends Horde_SyncML_Command { break; case 'DevID': - switch(trim($this->_chars)) { + $devid = trim($this->_chars); + $this->_deviceInfo['deviceID'] = $devid; + switch ($devid) + { case 'fmz-thunderbird-plugin': - $this->_deviceInfo['deviceID'] = trim($this->_chars); - $this->_deviceInfo['manufacturer'] = 'funambol'; - $this->_deviceInfo['model'] = trim($this->_chars); + if (empty($this->_devinceInfo['manufacturer'])) + $this->_deviceInfo['manufacturer'] = 'funambol'; + if (empty($this->_devinceInfo['model'])) + $this->_deviceInfo['model'] = 'thunderbird'; + if (empty($this->_devinceInfo['softwareVersion'])) + $this->_deviceInfo['softwareVersion'] = '0.3'; break; - - default: - $this->_deviceInfo['deviceID'] = trim($this->_chars); - break; - } break; diff --git a/phpgwapi/inc/horde/Horde/SyncML/State.php b/phpgwapi/inc/horde/Horde/SyncML/State.php index 50d3c957be..e6afd5001c 100644 --- a/phpgwapi/inc/horde/Horde/SyncML/State.php +++ b/phpgwapi/inc/horde/Horde/SyncML/State.php @@ -655,28 +655,82 @@ class Horde_SyncML_State { /** * This function should use DevINF information. */ - function getPreferedContentType($type) { - switch($type) { + function adjustContentType($type, $target = null) + { + $ctype; + if (is_array($type)) + { + $ctype = $type['ContentType']; + $res = $type; + } + else + { + $ctype = $type; + $res = array(); + $res['ContentType'] = $ctype; + } + + switch($ctype) + { + case 'text/x-vcard': + case 'text/x-vcalendar': + case 'text/x-vnote': + case 'text/calendar': + case 'text/plain': + $res['mayFragment'] = 1; + break; + + case 'text/x-s4j-sifc': + case 'text/x-s4j-sife': + case 'text/x-s4j-sift': + case 'text/x-s4j-sifn': + $res['ContentFormat'] = 'b64'; + $res['mayFragment'] = 0; + break; + } + + if (!isset($res['mayFragment'])) + { + $res['mayFragment'] = 0; + } + + if ($target != null) + { + switch($target) + { + case 'calendar': + case 'tasks': + case 'notes': + case 'contacts': + $res['mayFragment'] = 1; + break; + + case 'sifcalendar': + case 'siftasks': + case 'sifnotes': + case 'sifcontacts': + case 'scard': + case 'scalendar': + case 'stask': + case 'snote': + default: + $res['mayFragment'] = 0; + break; + } + } + + return $res; + } + + function getPreferedContentType($type) + { + $_type = str_replace('./','',$type); + switch($_type) + { case 'contacts': - case './contacts': return 'text/x-vcard'; break; - - case 'sifcalendar': - case './sifcalendar': - return 'text/x-s4j-sife'; - break; - - case 'sifcontacts': - case './sifcontacts': - return 'text/x-s4j-sifc'; - break; - - case 'siftasks': - case './siftasks': - return 'text/x-s4j-sift'; - break; - + case 'notes': return 'text/x-vnote'; break; @@ -684,14 +738,84 @@ class Horde_SyncML_State { case 'tasks': return 'text/x-vcalendar'; break; - + case 'calendar': - case './calendar': return 'text/x-vcalendar'; break; + + case 'sifcalendar': + case 'scal': + return 'text/x-s4j-sife'; + break; + + case 'sifcontacts': + case 'scard': + return 'text/x-s4j-sifc'; + break; + + case 'siftasks': + case 'stask': + return 'text/x-s4j-sift'; + break; + + case 'sifnotes': + case 'snote': + return 'text/x-s4j-sifn'; + break; } } + function getHordeType($type) + { + $_type = str_replace('./','',$type); + switch($_type) + { + case 'contacts': + return 'contacts'; + break; + + case 'notes': + return 'notes'; + break; + + case 'tasks': + return 'tasks'; + break; + + case 'calendar': + return 'calendar'; + break; + + # funambol related types + + case 'sifcalendar': + case 'scal': + return 'sifcalendar'; + break; + + case 'sifcontacts': + case 'scard': + return 'sifcontacts'; + break; + + case 'siftasks': + case 'stask': + return 'siftasks'; + break; + + case 'sifnotes': + case 'snote': + return 'sifnotes'; + break; + + default: + Horde::logMessage("unknown hordeType for type=$type ($_type)", __FILE__, __LINE__, PEAR_LOG_INFO); + return $_type; + break; + } + } + + /** /** * Returns the preferred contenttype of the client for the given * sync data type (database). @@ -699,15 +823,21 @@ class Horde_SyncML_State { * This is passed as an option to the Horde API export functions. */ - function getPreferedContentTypeClient($_sourceLocURI) { + function getPreferedContentTypeClient($_sourceLocURI, $_targetLocURI = null) { $deviceInfo = $this->getClientDeviceInfo(); - if(isset($deviceInfo['dataStore'][$_sourceLocURI]['rxPreference']['contentType'])) { - return array('ContentType' => $deviceInfo['dataStore'][$_sourceLocURI]['rxPreference']['contentType']); + if(isset($deviceInfo['dataStore'][$_sourceLocURI]['rxPreference']['contentType'])) + { + return $this->adjustContentType($deviceInfo['dataStore'][$_sourceLocURI]['rxPreference']['contentType'], $_targetLocURI); } Horde::logMessage('SyncML: sourceLocURI ' . $_sourceLocURI .' not found', __FILE__, __LINE__, PEAR_LOG_DEBUG); + if ($_targetLocURI != null) + { + return $this->adjustContentType($this->getPreferedContentType($_targetLocURI), $_targetLocURI); + } + return PEAR::raiseError(_('sourceLocURI not found')); } diff --git a/phpgwapi/inc/horde/Horde/SyncML/Sync.php b/phpgwapi/inc/horde/Horde/SyncML/Sync.php index bb443a3138..154da61570 100644 --- a/phpgwapi/inc/horde/Horde/SyncML/Sync.php +++ b/phpgwapi/inc/horde/Horde/SyncML/Sync.php @@ -141,13 +141,14 @@ class Horde_SyncML_Sync { } $hordeType = $type = $this->_targetLocURI; - // remove the './' from the beginning - $hordeType = str_replace('./','',$hordeType); + $hordeType = $state->getHordeType($hordeType); if(!$contentType = $command->getContentType()) { $contentType = $state->getPreferedContentType($type); } - if ($this->_targetLocURI == 'calendar' && strpos($command->getContent(), 'BEGIN:VTODO') !== false) { + if (($contentType == 'text/x-vcalendar' || $contentType == 'text/calendar') + && strpos($command->getContent(), 'BEGIN:VTODO') !== false) + { $hordeType = 'tasks'; } diff --git a/phpgwapi/inc/horde/Horde/SyncML/Sync/RefreshFromServerSync.php b/phpgwapi/inc/horde/Horde/SyncML/Sync/RefreshFromServerSync.php index ef96857385..35c071f4e7 100644 --- a/phpgwapi/inc/horde/Horde/SyncML/Sync/RefreshFromServerSync.php +++ b/phpgwapi/inc/horde/Horde/SyncML/Sync/RefreshFromServerSync.php @@ -36,28 +36,27 @@ class Horde_SyncML_Sync_RefreshFromServerSync extends Horde_SyncML_Sync_TwoWaySy continue; } - $contentType = $state->getPreferedContentTypeClient($this->_sourceLocURI); - if(is_a($contentType, 'PEAR_Error')) { - // Client did not sent devinfo - $contentType = array('ContentType' => $state->getPreferedContentType($this->_targetLocURI)); - } - + $contentType = $state->getPreferedContentTypeClient($this->_sourceLocURI, $this->_targetLocURI); $cmd = &new Horde_SyncML_Command_Sync_ContentSyncElement(); $c = $registry->call($hordeType . '/export', array('guid' => $guid, 'contentType' => $contentType)); Horde::logMessage("SyncML: slowsync add $guid to client ". print_r($c, true), __FILE__, __LINE__, PEAR_LOG_DEBUG); if (!is_a($c, 'PEAR_Error')) { $cmd->setContent($c); - if($hordeType == 'sifcalendar' || $hordeType == 'sifcontacts' || $hordeType == 'siftasks') { - $cmd->setContentFormat('b64'); + $cmd->setContentType($contentType['ContentType']); + if (isset($contentType['ContentFormat'])) + { + $cmd->setContentFormat($contentType['ContentFormat']); } - $cmd->setContentType($contentType['ContentType']); $cmd->setSourceURI($guid); $currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Add'); $state->log('Server-Add'); // return if we have to much data - if(++$counter >= MAX_ENTRIES && $hordeType != 'sifcalendar' && $hordeType != 'sifcontacts' && $hordeType != 'siftasks') { + if(++$counter >= MAX_ENTRIES + && isset($contentType['mayFragment']) + && $contentType['mayFragment']) + { $state->setSyncStatus(SERVER_SYNC_DATA_PENDING); return $currentCmdID; } @@ -77,7 +76,7 @@ class Horde_SyncML_Sync_RefreshFromServerSync extends Horde_SyncML_Sync_TwoWaySy $state = &$_SESSION['SyncML.state']; $syncType = $this->_targetLocURI; - $hordeType = str_replace('./','',$syncType); + $hordeType = $state->getHordeType($syncType); Horde::logMessage("SyncML: reading added items from database for $hordeType", __FILE__, __LINE__, PEAR_LOG_DEBUG); $state->setAddedItems($hordeType, $registry->call($hordeType. '/list', array())); diff --git a/phpgwapi/inc/horde/Horde/SyncML/Sync/SlowSync.php b/phpgwapi/inc/horde/Horde/SyncML/Sync/SlowSync.php index ce09fcaea5..39f587559c 100644 --- a/phpgwapi/inc/horde/Horde/SyncML/Sync/SlowSync.php +++ b/phpgwapi/inc/horde/Horde/SyncML/Sync/SlowSync.php @@ -38,28 +38,27 @@ class Horde_SyncML_Sync_SlowSync extends Horde_SyncML_Sync_TwoWaySync { continue; } - $contentType = $state->getPreferedContentTypeClient($this->_sourceLocURI); - if(is_a($contentType, 'PEAR_Error')) { - // Client did not sent devinfo - $contentType = array('ContentType' => $state->getPreferedContentType($this->_targetLocURI)); - } - + $contentType = $state->getPreferedContentTypeClient($this->_sourceLocURI, $this->_targetLocURI); $cmd = &new Horde_SyncML_Command_Sync_ContentSyncElement(); $c = $registry->call($hordeType . '/export', array('guid' => $guid, 'contentType' => $contentType)); #Horde::logMessage("SyncML: slowsync add guid $guid to client ". print_r($c, true), __FILE__, __LINE__, PEAR_LOG_DEBUG); if (!is_a($c, 'PEAR_Error')) { $cmd->setContent($c); - if($hordeType == 'sifcalendar' || $hordeType == 'sifcontacts' || $hordeType == 'siftasks') { - $cmd->setContentFormat('b64'); + $cmd->setContentType($contentType['ContentType']); + if (isset($contentType['ContentFormat'])) + { + $cmd->setContentFormat($contentType['ContentFormat']); } - $cmd->setContentType($contentType['ContentType']); $cmd->setSourceURI($guid); $currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Add'); $state->log('Server-Add'); // return if we have to much data - if(++$counter >= MAX_ENTRIES && $hordeType != 'sifcalendar' && $hordeType != 'sifcontacts' && $hordeType != 'siftasks') { + if(++$counter >= MAX_ENTRIES + && isset($contentType['mayFragment']) + && $contentType['mayFragment']) + { $state->setSyncStatus(SERVER_SYNC_DATA_PENDING); return $currentCmdID; } @@ -129,9 +128,8 @@ class Horde_SyncML_Sync_SlowSync extends Horde_SyncML_Sync_TwoWaySync { return; } - $hordeType = $type = $this->_targetLocURI; - // remove the './' from the beginning - $hordeType = str_replace('./','',$hordeType); + $type = $this->_targetLocURI; + $hordeType = $state->getHordeType($type); $syncElementItems = $command->getSyncElementItems(); @@ -140,7 +138,9 @@ class Horde_SyncML_Sync_SlowSync extends Horde_SyncML_Sync_TwoWaySync { $contentType = $state->getPreferedContentType($type); } - if ($this->_targetLocURI == 'calendar' && strpos($syncItem->getContent(), 'BEGIN:VTODO') !== false) { + if (($contentType == 'text/x-vcalendar' || $contentType == 'text/calendar') + && strpos($syncItem->getContent(), 'BEGIN:VTODO') !== false) + { $hordeType = 'tasks'; } @@ -179,7 +179,7 @@ class Horde_SyncML_Sync_SlowSync extends Horde_SyncML_Sync_TwoWaySync { $state = &$_SESSION['SyncML.state']; $syncType = $this->_targetLocURI; - $hordeType = str_replace('./','',$syncType); + $hordeType = $state->getHordeType($syncType); Horde::logMessage("SyncML: reading added items from database for $hordeType", __FILE__, __LINE__, PEAR_LOG_DEBUG); $state->setAddedItems($hordeType, $registry->call($hordeType. '/list', array())); diff --git a/phpgwapi/inc/horde/Horde/SyncML/Sync/TwoWaySync.php b/phpgwapi/inc/horde/Horde/SyncML/Sync/TwoWaySync.php index fac31906c5..6ba885949f 100644 --- a/phpgwapi/inc/horde/Horde/SyncML/Sync/TwoWaySync.php +++ b/phpgwapi/inc/horde/Horde/SyncML/Sync/TwoWaySync.php @@ -27,7 +27,7 @@ class Horde_SyncML_Sync_TwoWaySync extends Horde_SyncML_Sync { $syncType = $this->_targetLocURI; - $hordeType = str_replace('./','',$syncType); + $hordeType = $state->getHordeType($syncType); $refts = $state->getServerAnchorLast($syncType); $currentCmdID = $this->handleSync($currentCmdID, @@ -79,11 +79,7 @@ class Horde_SyncML_Sync_TwoWaySync extends Horde_SyncML_Sync { } // Create a replace request for client. - $contentType = $state->getPreferedContentTypeClient($this->_sourceLocURI); - if(is_a($contentType, 'PEAR_Error')) { - // Client did not sent devinfo - $contentType = array('ContentType' => $state->getPreferedContentType($this->_targetLocURI)); - } + $contentType = $state->getPreferedContentTypeClient($this->_sourceLocURI, $this->_targetLocURI); $c = $registry->call($hordeType. '/export', array('guid' => $guid, 'contentType' => $contentType)); if (!is_a($c, 'PEAR_Error')) { @@ -95,14 +91,19 @@ class Horde_SyncML_Sync_TwoWaySync extends Horde_SyncML_Sync { $cmd->setSourceURI($guid); $cmd->setTargetURI($locid); $cmd->setContentType($contentType['ContentType']); - if($hordeType == 'sifcalendar' || $hordeType == 'sifcontacts' || $hordeType == 'siftasks') { - $cmd->setContentFormat('b64'); + if (isset($contentType['ContentFormat'])) + { + $cmd->setContentFormat($contentType['ContentFormat']); } + $currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Replace'); $state->log('Server-Replace'); // return if we have to much data - if(++$counter >= MAX_ENTRIES && $hordeType != 'sifcalendar' && $hordeType != 'sifcontacts' && $hordeType != 'siftasks') { + if (++$counter >= MAX_ENTRIES + && isset($contentType['mayFragment']) + && $contentType['mayFragment']) + { $state->setSyncStatus(SERVER_SYNC_DATA_PENDING); return $currentCmdID; } @@ -139,8 +140,12 @@ class Horde_SyncML_Sync_TwoWaySync extends Horde_SyncML_Sync { $state->log('Server-Delete'); $state->removeUID($syncType, $locid); + $contentType = $state->getPreferedContentTypeClient($this->_sourceLocURI, $this->_targetLocURI); // return if we have to much data - if(++$counter >= MAX_ENTRIES && $hordeType != 'sifcalender' && $hordeType != 'sifcontacts' &&$hordeType != 'siftasks') { + if(++$counter >= MAX_ENTRIES + && isset($contentType['mayFragment']) + && $contentType['mayFragment']) + { $state->setSyncStatus(SERVER_SYNC_DATA_PENDING); return $currentCmdID; } @@ -180,11 +185,7 @@ class Horde_SyncML_Sync_TwoWaySync extends Horde_SyncML_Sync { Horde::logMessage("SyncML: add: $guid", __FILE__, __LINE__, PEAR_LOG_DEBUG); // Create an Add request for client. - $contentType = $state->getPreferedContentTypeClient($this->_sourceLocURI); - if(is_a($contentType, 'PEAR_Error')) { - // Client did not sent devinfo - $contentType = array('ContentType' => $state->getPreferedContentType($this->_targetLocURI)); - } + $contentType = $state->getPreferedContentTypeClient($this->_sourceLocURI, $this->_targetLocURI); $cmd = &new Horde_SyncML_Command_Sync_ContentSyncElement(); $c = $registry->call($hordeType . '/export', @@ -197,16 +198,20 @@ class Horde_SyncML_Sync_TwoWaySync extends Horde_SyncML_Sync { if (!is_a($c, 'PEAR_Error')) { // Item in history but not in database. Strange, but can happen. $cmd->setContent($c); - if($hordeType == 'sifcalendar' || $hordeType == 'sifcontacts' || $hordeType == 'siftasks') { - $cmd->setContentFormat('b64'); - } $cmd->setContentType($contentType['ContentType']); + if (isset($contentType['ContentFormat'])) + { + $cmd->setContentFormat($contentType['ContentFormat']); + } $cmd->setSourceURI($guid); $currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Add'); $state->log('Server-Add'); // return if we have to much data - if(++$counter >= MAX_ENTRIES && $hordeType != 'sifcalendar' && $hordeType != 'sifcontacts' &&$hordeType != 'siftasks') { + if(++$counter >= MAX_ENTRIES + && isset($contentType['mayFragment']) + && $contentType['mayFragment']) + { $state->setSyncStatus(SERVER_SYNC_DATA_PENDING); return $currentCmdID; } @@ -225,7 +230,7 @@ class Horde_SyncML_Sync_TwoWaySync extends Horde_SyncML_Sync { $state = &$_SESSION['SyncML.state']; $syncType = $this->_targetLocURI; - $hordeType = str_replace('./','',$syncType); + $hordeType = $state->getHordeType($syncType); $refts = $state->getServerAnchorLast($syncType); Horde::logMessage("SyncML: reading changed items from database for $hordeType", __FILE__, __LINE__, PEAR_LOG_DEBUG); diff --git a/phpgwapi/inc/horde/Horde/iCalendar.php b/phpgwapi/inc/horde/Horde/iCalendar.php index 3d562a1136..6ef80a52f4 100644 --- a/phpgwapi/inc/horde/Horde/iCalendar.php +++ b/phpgwapi/inc/horde/Horde/iCalendar.php @@ -510,18 +510,11 @@ class Horde_iCalendar { case 'CREATED': case 'LAST-MODIFIED': case 'BDAY': - $this->setAttribute($tag, $this->_parseDateTime($value), $params); - break; - case 'DTEND': case 'DTSTART': case 'DUE': case 'RECURRENCE-ID': - if (isset($params['VALUE']) && $params['VALUE'] == 'DATE') { - $this->setAttribute($tag, $this->_parseDate($value), $params); - } else { - $this->setAttribute($tag, $this->_parseDateTime($value), $params); - } + $this->setAttribute($tag, $this->_parseDateTime($value), $params); break; case 'RDATE': @@ -902,7 +895,7 @@ class Horde_iCalendar { if (!$date = $this->_parseDate($text)) { return $date; } - return @gmmktime(0, 0, 0, $date['month'], $date['mday'], $date['year']); + return @mktime(0, 0, 0, $date['month'], $date['mday'], $date['year']); } if (!$date = $this->_parseDate($dateParts[0])) { @@ -912,6 +905,8 @@ class Horde_iCalendar { return $time; } + error_log("parseDateTime: ".$text." => ".print_r($time, true)); + if ($time['zone'] == 'UTC') { return @gmmktime($time['hour'], $time['minute'], $time['second'], $date['month'], $date['mday'], $date['year']); @@ -1016,7 +1011,12 @@ class Horde_iCalendar { */ function _parseDate($text) { - if (strlen($text) != 8) { + if (strlen($text) == 10) + { + $text = str_replace('-','',$text); + } + if (strlen($text) != 8) + { return false; } diff --git a/phpgwapi/inc/horde/config/conf.php b/phpgwapi/inc/horde/config/conf.php index 188b15ce28..1e981e0ee9 100644 --- a/phpgwapi/inc/horde/config/conf.php +++ b/phpgwapi/inc/horde/config/conf.php @@ -18,12 +18,12 @@ $conf['auth']['checkip'] = true; $conf['auth']['params']['username'] = 'Administrator'; $conf['auth']['params']['requestuser'] = false; $conf['auth']['driver'] = 'auto'; -$conf['log']['priority'] = PEAR_LOG_INFO; +$conf['log']['priority'] = PEAR_LOG_DEBUG; $conf['log']['ident'] = 'EGWSYNC'; $conf['log']['params'] = array(); $conf['log']['name'] = '/tmp/egroupware_syncml.log'; $conf['log']['params']['append'] = true; -$conf['log']['type'] = 'file'; +$conf['log']['type'] = 'error_log'; $conf['log']['enabled'] = true; $conf['log_accesskeys'] = false; $conf['prefs']['driver'] = 'none'; diff --git a/phpgwapi/inc/horde/config/registry.php b/phpgwapi/inc/horde/config/registry.php index 830b9c6127..aaef720670 100644 --- a/phpgwapi/inc/horde/config/registry.php +++ b/phpgwapi/inc/horde/config/registry.php @@ -77,7 +77,7 @@ $this->applications['egwnotessync'] = array( 'icon' => $this->applications['horde']['webroot'] . '/mnemo/graphics/mnemo.gif', 'name' => _("Notes"), 'status' => 'active', - 'provides' => 'notes', + 'provides' => array('notes', 'sifnotes', 'snote'), 'menu_parent' => 'organizing' ); @@ -87,19 +87,19 @@ $this->applications['egwcontactssync'] = array( 'icon' => $this->applications['horde']['webroot'] . '/mnemo/graphics/mnemo.gif', 'name' => _("Contacts"), 'status' => 'active', - 'provides' => 'contacts', + 'provides' => array('contacts', 'sifcontacts', 'scard'), 'menu_parent' => 'organizing' ); -$this->applications['egwsifcontactssync'] = array( - 'fileroot' => EGW_SERVER_ROOT.'/syncml/sifcontacts', - 'webroot' => $this->applications['horde']['webroot'] . '/mnemo', - 'icon' => $this->applications['horde']['webroot'] . '/mnemo/graphics/mnemo.gif', - 'name' => _("SIF Contacts"), - 'status' => 'active', - 'provides' => 'sifcontacts', - 'menu_parent' => 'organizing' -); +#$this->applications['egwsifcontactssync'] = array( +# 'fileroot' => EGW_SERVER_ROOT.'/syncml/sifcontacts', +# 'webroot' => $this->applications['horde']['webroot'] . '/mnemo', +# 'icon' => $this->applications['horde']['webroot'] . '/mnemo/graphics/mnemo.gif', +# 'name' => _("SIF Contacts"), +# 'status' => 'active', +# 'provides' => 'sifcontacts', +# 'menu_parent' => 'organizing' +#); $this->applications['egwcalendarsync'] = array( 'fileroot' => EGW_SERVER_ROOT.'/syncml/calendar', @@ -107,19 +107,19 @@ $this->applications['egwcalendarsync'] = array( 'icon' => $this->applications['horde']['webroot'] . '/mnemo/graphics/mnemo.gif', 'name' => _("Calendar"), 'status' => 'active', - 'provides' => 'calendar', + 'provides' => array('calendar', 'sifcalendar', 'scal'), 'menu_parent' => 'organizing' ); -$this->applications['egwsifcalendarsync'] = array( - 'fileroot' => EGW_SERVER_ROOT.'/syncml/sifcalendar', - 'webroot' => $this->applications['horde']['webroot'] . '/mnemo', - 'icon' => $this->applications['horde']['webroot'] . '/mnemo/graphics/mnemo.gif', - 'name' => _("Calendar"), - 'status' => 'active', - 'provides' => 'sifcalendar', - 'menu_parent' => 'organizing' -); +#$this->applications['egwsifcalendarsync'] = array( +# 'fileroot' => EGW_SERVER_ROOT.'/syncml/sifcalendar', +# 'webroot' => $this->applications['horde']['webroot'] . '/mnemo', +# 'icon' => $this->applications['horde']['webroot'] . '/mnemo/graphics/mnemo.gif', +# 'name' => _("Calendar"), +# 'status' => 'active', +# 'provides' => 'sifcalendar', +# 'menu_parent' => 'organizing' +#); $this->applications['egwtaskssync'] = array( 'fileroot' => EGW_SERVER_ROOT.'/syncml/tasks', @@ -127,19 +127,19 @@ $this->applications['egwtaskssync'] = array( 'icon' => $this->applications['horde']['webroot'] . '/mnemo/graphics/mnemo.gif', 'name' => _("Tasks"), 'status' => 'active', - 'provides' => 'tasks', + 'provides' => array('tasks', 'siftasks', 'stask'), 'menu_parent' => 'organizing' ); -$this->applications['egwsiftaskssync'] = array( - 'fileroot' => EGW_SERVER_ROOT.'/syncml/siftasks', - 'webroot' => $this->applications['horde']['webroot'] . '/mnemo', - 'icon' => $this->applications['horde']['webroot'] . '/mnemo/graphics/mnemo.gif', - 'name' => _("SIFTasks"), - 'status' => 'active', - 'provides' => 'siftasks', - 'menu_parent' => 'organizing' -); +#$this->applications['egwsiftaskssync'] = array( +# 'fileroot' => EGW_SERVER_ROOT.'/syncml/siftasks', +# 'webroot' => $this->applications['horde']['webroot'] . '/mnemo', +# 'icon' => $this->applications['horde']['webroot'] . '/mnemo/graphics/mnemo.gif', +# 'name' => _("SIFTasks"), +# 'status' => 'active', +# 'provides' => array('siftasks', 'stask'), +# 'menu_parent' => 'organizing' +#); $this->applications['egwcaltaskssync'] = array( 'fileroot' => EGW_SERVER_ROOT.'/syncml/caltasks',