From f108dea1cb23f3d4dfe80fe07966d1400ac168bb Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Tue, 8 Jul 2008 05:34:10 +0000 Subject: [PATCH] groupdav now uses infologs own vcalinfolog class and not longer icalsrv, also added by groupdav required support to keep the uid of the client and not use extra charset attributes which are depricated in recent iCal and some clients have problems with them --- infolog/inc/class.infolog_groupdav.inc.php | 16 +- infolog/inc/class.vcalinfolog.inc.php | 668 +++++++++++---------- 2 files changed, 349 insertions(+), 335 deletions(-) diff --git a/infolog/inc/class.infolog_groupdav.inc.php b/infolog/inc/class.infolog_groupdav.inc.php index 36eed70e4e..f6a11166c7 100644 --- a/infolog/inc/class.infolog_groupdav.inc.php +++ b/infolog/inc/class.infolog_groupdav.inc.php @@ -90,10 +90,9 @@ class infolog_groupdav extends groupdav_handler { return $task; } - include_once(EGW_INCLUDE_ROOT.'/icalsrv/inc/class.boinfolog_vtodos.inc.php'); - $handler =& new boinfolog_vtodos($this->bo); - $vtodo = $handler->export_vtodo($task,UMM_UID2UID); - $options['data'] = $handler->render_velt2vcal($vtodo); + include_once(EGW_INCLUDE_ROOT.'/infolog/inc/class.vcalinfolog.inc.php'); + $handler = new vcalinfolog(); + $options['data'] = $handler->exportVTODO($id,'2.0',false,false); // keep UID the client set and no extra charset attributes $options['mimetype'] = 'text/calendar; charset=utf-8'; header('Content-Encoding: identity'); header('ETag: '.$this->get_etag($task)); @@ -115,13 +114,12 @@ class infolog_groupdav extends groupdav_handler { return $ok; } - include_once(EGW_INCLUDE_ROOT.'/icalsrv/inc/class.boinfolog_vtodos.inc.php'); - $handler =& new boinfolog_vtodos($this->bo); - $vcalelm =& $handler->parse_vcal2velt($options['content']); - if (!($info_id = $handler->import_vtodo($vcalelm, $uid_mapping_import=UMM_UID2UID, $reimport_missing_events=false, $id)) > 0) + include_once(EGW_INCLUDE_ROOT.'/infolog/inc/class.vcalinfolog.inc.php'); + $handler = new vcalinfolog(); + if (!($info_id = $handler->importVCal($options['content'],is_numeric($id) ? $id : -1))) { if ($this->debug) error_log(__METHOD__."(,$id) import_vtodo($options[content]) returned false"); - return false; // something went wrong ... + return '403 Forbidden'; } header('ETag: '.$this->get_etag($info_id)); if (is_null($ok) || $id != $info_id) diff --git a/infolog/inc/class.vcalinfolog.inc.php b/infolog/inc/class.vcalinfolog.inc.php index df66f462a3..73ba0cb560 100644 --- a/infolog/inc/class.vcalinfolog.inc.php +++ b/infolog/inc/class.vcalinfolog.inc.php @@ -10,346 +10,362 @@ * @version $Id$ */ - require_once EGW_SERVER_ROOT.'/infolog/inc/class.boinfolog.inc.php'; - require_once EGW_SERVER_ROOT.'/phpgwapi/inc/horde/Horde/iCalendar.php'; +require_once EGW_SERVER_ROOT.'/infolog/inc/class.boinfolog.inc.php'; +require_once EGW_SERVER_ROOT.'/phpgwapi/inc/horde/Horde/iCalendar.php'; - class vcalinfolog extends boinfolog +class vcalinfolog extends boinfolog +{ + var $egw_priority2vcal_priority = array( + 0 => 3, + 1 => 2, + 2 => 1, + 3 => 1, + ); + + var $vcal_priority2egw_priority = array( + 1 => 2, + 2 => 1, + 3 => 0, + ); + + /** + * Exports one InfoLog tast to an iCalendar VTODO + * + * @param int $_taskID info_id + * @param string $version='2.0' could be '1.0' too + * @param boolean $force_own_uid=true ignore the stored and maybe from the client transfered uid and generate a new one + * RalfBecker: GroupDAV/CalDAV requires to switch that non RFC conform behavior off, dont know if SyncML still needs it + * @param boolean $extra_charset_attribute=true GroupDAV/CalDAV dont need the charset attribute and some clients have problems with it + * @return string/boolean string with vCal or false on error (eg. no permission to read the event) + */ + function exportVTODO($_taskID, $_version='2.0',$force_own_uid=true,$extra_charset_attribute=true) { - var $egw_priority2vcal_priority = array( - 0 => 3, - 1 => 2, - 2 => 1, - 3 => 1, - ); + $taskData = $this->read($_taskID); - var $vcal_priority2egw_priority = array( - 1 => 2, - 2 => 1, - 3 => 0, - ); + $taskData = $GLOBALS['egw']->translation->convert($taskData, $GLOBALS['egw']->translation->charset(), 'UTF-8'); - function exportVTODO($_taskID, $_version) + $taskGUID = $GLOBALS['egw']->common->generate_uid('infolog_task',$_taskID); + + $vcal = &new Horde_iCalendar; + $vcal->setAttribute('VERSION',$_version); + $vcal->setAttribute('METHOD','PUBLISH'); + + $vevent = Horde_iCalendar::newComponent('VTODO',$vcal); + + // set fields that may contain non-ascii chars and encode them if necessary + foreach(array( + 'SUMMARY' => $taskData['info_subject'], + 'DESCRIPTION' => $taskData['info_des'], + 'LOCATION' => $taskData['info_location'], + ) as $field => $value) { - $taskData = $this->read($_taskID); - - $taskData = $GLOBALS['egw']->translation->convert($taskData, $GLOBALS['egw']->translation->charset(), 'UTF-8'); - - //_debug_array($taskData); - - $taskGUID = $GLOBALS['egw']->common->generate_uid('infolog_task',$_taskID); - #print "
"; - #print $GLOBALS['egw']->contenthistory->getTSforAction($eventGUID,'add'); - #print "
"; - - $vcal = &new Horde_iCalendar; - $vcal->setAttribute('VERSION',$_version); - $vcal->setAttribute('METHOD','PUBLISH'); - - $vevent = Horde_iCalendar::newComponent('VTODO',$vcal); - + $vevent->setAttribute($field,$value); $options = array(); - - $vevent->setAttribute('SUMMARY',$taskData['info_subject']); - $vevent->setAttribute('DESCRIPTION',$taskData['info_des']); - $vevent->setAttribute('LOCATION',$taskData['info_location']); - if($taskData['info_startdate']) - $vevent->setAttribute('DTSTART',$taskData['info_startdate']); - if($taskData['info_enddate']) - $vevent->setAttribute('DUE',$taskData['info_enddate']); - if($taskData['info_datecompleted']) - $vevent->setAttribute('COMPLETED',$taskData['info_datecompleted']); - $vevent->setAttribute('DTSTAMP',time()); - $vevent->setAttribute('CREATED',$GLOBALS['egw']->contenthistory->getTSforAction($eventGUID,'add')); - $vevent->setAttribute('LAST-MODIFIED',$GLOBALS['egw']->contenthistory->getTSforAction($eventGUID,'modify')); - $vevent->setAttribute('UID',$taskGUID); - $vevent->setAttribute('CLASS',$taskData['info_access'] == 'public' ? 'PUBLIC' : 'PRIVATE'); - $vevent->setAttribute('STATUS',$this->status2vtodo($taskData['info_status'])); - // we try to preserv the original infolog status as X-INFOLOG-STATUS, so we can restore it, if the user does not modify STATUS - $vevent->setAttribute('X-INFOLOG-STATUS',$taskData['info_status']); - $vevent->setAttribute('PRIORITY',$this->egw_priority2vcal_priority[$taskData['info_priority']]); - - if (!empty($taskData['info_cat'])) + if(preg_match('/([\000-\012\015\016\020-\037\075])/',$value)) { - $cats = $this->get_categories(array($taskData['info_cat'])); - $vevent->setAttribute('CATEGORIES', $cats[0]); + $options['ENCODING'] = 'QUOTED-PRINTABLE'; } - - #$vevent->setAttribute('TRANSP','OPAQUE'); - # status - # ATTENDEE - - $options = array('CHARSET' => 'UTF-8','ENCODING' => 'QUOTED-PRINTABLE'); - $vevent->setParameter('SUMMARY', $options); - $vevent->setParameter('DESCRIPTION', $options); - - $vcal->addComponent($vevent); - - #print "
";
-			#print $vcal->exportvCalendar();
-			#print "
"; - - return $vcal->exportvCalendar(); - } - - function importVTODO(&$_vcalData, $_taskID=-1) - { - if(!$taskData = $this->vtodotoegw($_vcalData)) { - return false; - } - - if($_taskID > 0) { - $taskData['info_id'] = $_taskID; - } - - // we suppose that a not set status in a vtodo means that the task did not started yet - if(empty($taskData['info_status'])) { - $taskData['info_status'] = 'not-started'; - } - - #_debug_array($taskData);exit; - return $this->write($taskData); - } - - function searchVTODO($_vcalData) - { - if(!$egwData = $this->vtodotoegw($_vcalData)) { - return false; - } - - #unset($egwData['info_priority']); - - $filter = array('col_filter' => $egwData); - if($foundItems = $this->search($filter)) { - if(count($foundItems) > 0) { - $itemIDs = array_keys($foundItems); - return $itemIDs[0]; - } - } - - return false; - } - - function vtodotoegw($_vcalData) - { - $vcal = &new Horde_iCalendar; - if(!$vcal->parsevCalendar($_vcalData)) { - return FALSE; - } - - $components = $vcal->getComponents(); - - if(count($components) > 0) + if($extra_charset_attribute && preg_match('/([\177-\377])/',$value)) { - $component = $components[0]; - if(is_a($component, 'Horde_iCalendar_vtodo')) - { - if($_taskID>0) - $taskData['info_id'] = $_taskID; - - foreach($component->_attributes as $attributes) - { - #print $attributes['name'].' - '.$attributes['value'].'
'; - switch($attributes['name']) - { - case 'CLASS': - $taskData['info_access'] = strtolower($attributes['value']); - break; - case 'DESCRIPTION': - $taskData['info_des'] = $attributes['value']; - break; - case 'LOCATION': - $taskData['info_location'] = $attributes['value']; - break; - case 'DUE': - $taskData['info_enddate'] = $attributes['value']; - break; - case 'COMPLETED': - $taskData['info_datecompleted'] = $attributes['value']; - break; - case 'DTSTART': - $taskData['info_startdate'] = $attributes['value']; - break; - case 'PRIORITY': - if (1 <= $attributes['value'] && $attributes['value'] <= 3) - { - $taskData['info_priority'] = $this->vcal_priority2egw_priority[$attributes['value']]; - } - else - { - $taskData['info_priority'] = 1; // default = normal - } - break; - case 'STATUS': - // check if we (still) have X-INFOLOG-STATUS set AND it would give an unchanged status (no change by the user) - foreach($component->_attributes as $attr) - { - if ($attr['name'] == 'X-INFOLOG-STATUS') break; - } - $taskData['info_status'] = $this->vtodo2status($attributes['value'], - $attr['name'] == 'X-INFOLOG-STATUS' ? $attr['value'] : null); - break; - 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 - # do NOT convert here - #$taskData = $GLOBALS['egw']->translation->convert($taskData, 'UTF-8'); - - return $taskData; - } + $options['CHARSET'] = 'UTF-8'; } - return FALSE; + if ($options) $vevent->setParameter($field, $options); } + if($taskData['info_startdate']) + $vevent->setAttribute('DTSTART',$taskData['info_startdate']); + if($taskData['info_enddate']) + $vevent->setAttribute('DUE',$taskData['info_enddate']); + if($taskData['info_datecompleted']) + $vevent->setAttribute('COMPLETED',$taskData['info_datecompleted']); + $vevent->setAttribute('DTSTAMP',time()); + $vevent->setAttribute('CREATED',$GLOBALS['egw']->contenthistory->getTSforAction($eventGUID,'add')); + $vevent->setAttribute('LAST-MODIFIED',$GLOBALS['egw']->contenthistory->getTSforAction($eventGUID,'modify')); + $vevent->setAttribute('UID',$force_own_uid ? $taskGUID : $taskData['info_uid']); + $vevent->setAttribute('CLASS',$taskData['info_access'] == 'public' ? 'PUBLIC' : 'PRIVATE'); + $vevent->setAttribute('STATUS',$this->status2vtodo($taskData['info_status'])); + // we try to preserv the original infolog status as X-INFOLOG-STATUS, so we can restore it, if the user does not modify STATUS + $vevent->setAttribute('X-INFOLOG-STATUS',$taskData['info_status']); + $vevent->setAttribute('PRIORITY',$this->egw_priority2vcal_priority[$taskData['info_priority']]); - function exportVNOTE($_noteID, $_type) + if (!empty($taskData['info_cat'])) { - $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; + $cats = $this->get_categories(array($taskData['info_cat'])); + $vevent->setAttribute('CATEGORIES', $cats[0]); } - - function importVNOTE(&$_vcalData, $_type, $_noteID = -1) - { - if(!$note = $this->vnotetoegw($_vcalData, $_type)) - { - return false; - } - - if($_noteID > 0) - { - $note['info_id'] = $_noteID; - } + $vcal->addComponent($vevent); - 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; - } + return $vcal->exportvCalendar(); } + /** + * Import a VTODO component of an iCal + * + * @param string $_vcalData + * @param int $_taskID=-1 info_id, default -1 = new entry + * @return int|boolean integer info_id or false on error + */ + function importVTODO(&$_vcalData, $_taskID=-1) + { + if(!$taskData = $this->vtodotoegw($_vcalData,$_taskID)) + { + return false; + } + // we suppose that a not set status in a vtodo means that the task did not started yet + if(empty($taskData['info_status'])) + { + $taskData['info_status'] = 'not-started'; + } + return $this->write($taskData); + } + + function searchVTODO($_vcalData) + { + if(!$egwData = $this->vtodotoegw($_vcalData)) { + return false; + } + + #unset($egwData['info_priority']); + + $filter = array('col_filter' => $egwData); + if($foundItems = $this->search($filter)) { + if(count($foundItems) > 0) { + $itemIDs = array_keys($foundItems); + return $itemIDs[0]; + } + } + + return false; + } + + function vtodotoegw($_vcalData,$_taskID=-1) + { + $vcal = &new Horde_iCalendar; + if(!$vcal->parsevCalendar($_vcalData)) + { + return FALSE; + } + + $components = $vcal->getComponents(); + + if(count($components) > 0) + { + $component = $components[0]; + if(is_a($component, 'Horde_iCalendar_vtodo')) + { + $taskData = array(); + if($_taskID > 0) + { + $taskData['info_id'] = $_taskID; + } + foreach($component->_attributes as $attributes) + { + switch($attributes['name']) + { + case 'CLASS': + $taskData['info_access'] = strtolower($attributes['value']); + break; + case 'DESCRIPTION': + $taskData['info_des'] = $attributes['value']; + break; + case 'LOCATION': + $taskData['info_location'] = $attributes['value']; + break; + case 'DUE': + $taskData['info_enddate'] = $attributes['value']; + break; + case 'COMPLETED': + $taskData['info_datecompleted'] = $attributes['value']; + break; + case 'DTSTART': + $taskData['info_startdate'] = $attributes['value']; + break; + case 'PRIORITY': + if (1 <= $attributes['value'] && $attributes['value'] <= 3) + { + $taskData['info_priority'] = $this->vcal_priority2egw_priority[$attributes['value']]; + } + else + { + $taskData['info_priority'] = 1; // default = normal + } + break; + case 'STATUS': + // check if we (still) have X-INFOLOG-STATUS set AND it would give an unchanged status (no change by the user) + foreach($component->_attributes as $attr) + { + if ($attr['name'] == 'X-INFOLOG-STATUS') break; + } + $taskData['info_status'] = $this->vtodo2status($attributes['value'], + $attr['name'] == 'X-INFOLOG-STATUS' ? $attr['value'] : null); + break; + 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; + case 'UID': + $taskData['info_uid'] = $attributes['value']; + if ($_taskID <= 0 && !empty($attributes['value']) && ($uid_task = $this->read($attributes['value']))) + { + $taskData['info_id'] = $uid_task['id']; + unset($uid_task); + } + break; + } + } + # the horde ical class does already convert in parsevCalendar + # do NOT convert here + #$taskData = $GLOBALS['egw']->translation->convert($taskData, 'UTF-8'); + + return $taskData; + } + } + 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; + } +} +