From 7b6a1013fcc3043e11a6fa55cf2025b4bb9d3bbb Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Sat, 29 Sep 2007 10:29:48 +0000 Subject: [PATCH] SyncML patches from patrick.bihan-faou-AT-mindstep.com (without logout+mbstring stuff), small modification to use the already exiting methodes to generate full name and fileas) The code is commited to trunk only at the moment to allow testing of it. If everything goes well, we intend to commit it to 1.4 branch too. Here's the original description of the patch by Patrick: - handles the default config for current versions of funambol (i.e. the scard/stask/snote/scal locations) - tries to be a bit smarter on how the data content should be encoded based on what the client specified (sif+base64/vcard, / fragmented or not, etc.) - workaround a bug in some versions of funambol, where funambol does not specify the proper sif type for the type of requested data - imported patch #117 from egw's tracker - make sure that the logs generated by the horde code go to stderr so they can be view in the webserver's logs - as much as possible reduce code duplication. For example, the categories are handled in the parent classes for both the SIF avn VCAL formats for each type of data (addressbook,infolog,calendar). - make sure the code can handle more than one categories in each direction - treat the 'sony ericsson' vendor string just like 'sonyericsson', the newer phones apparently have a space in the vendor string... (this touches some files in the icalsrv as well) - handle notes: these should now work with everything (funambol or other) - remove more code duplication: the syncml "api" for the various data types (calendar, contacts, infolog) is now common for both the vcard and sif data formats (cf the files that need to be removed) - handle the "privat" filter in infolog like the "private" filter (some part of the code use the name without the trailing e) - imported patch # 267 from egw's tracker --- addressbook/inc/class.bocontacts.inc.php | 82 +++++ addressbook/inc/class.sifaddressbook.inc.php | 39 +- addressbook/inc/class.vcaladdressbook.inc.php | 234 ++++++------ calendar/inc/class.bocalupdate.inc.php | 76 ++++ calendar/inc/class.boical.inc.php | 346 ++++++++++++------ calendar/inc/class.sifcalendar.inc.php | 28 +- calendar/inc/class.socal.inc.php | 2 +- infolog/inc/class.boinfolog.inc.php | 71 +++- infolog/inc/class.sifinfolog.inc.php | 135 ++++++- infolog/inc/class.soinfolog.inc.php | 4 +- infolog/inc/class.vcalinfolog.inc.php | 162 +++++++- .../inc/horde/Horde/SyncML/Command/Put.php | 43 +++ .../horde/Horde/SyncML/Command/Results.php | 19 +- phpgwapi/inc/horde/Horde/SyncML/State.php | 178 +++++++-- phpgwapi/inc/horde/Horde/SyncML/Sync.php | 7 +- .../SyncML/Sync/RefreshFromServerSync.php | 21 +- .../inc/horde/Horde/SyncML/Sync/SlowSync.php | 30 +- .../horde/Horde/SyncML/Sync/TwoWaySync.php | 45 ++- phpgwapi/inc/horde/Horde/iCalendar.php | 20 +- phpgwapi/inc/horde/config/conf.php | 4 +- phpgwapi/inc/horde/config/registry.php | 62 ++-- 21 files changed, 1216 insertions(+), 392 deletions(-) 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',