Apply InfoLog SyncML and GroupDAV changes

This commit is contained in:
Jörg Lehrke 2010-03-07 15:47:00 +00:00
parent 3d482fbcef
commit 3bd935a541
7 changed files with 1322 additions and 651 deletions

View File

@ -227,8 +227,9 @@ class boinfolog extends infolog_bo
/**
* Convert an InfoLog entry into its xmlrpc representation, eg. convert timestamps to datetime.iso8601
*
* @param array $data infolog entry
* @param array xmlrpc infolog entry
* @param array $data infolog entry in db format
*
* @return array xmlrpc infolog entry
*/
function data2xmlrpc($data)
{
@ -272,8 +273,9 @@ class boinfolog extends infolog_bo
/**
* Convert an InfoLog xmlrpc representation into the internal one, eg. convert datetime.iso8601 to timestamps
*
* @param array $data infolog entry
* @param array xmlrpc infolog entry
* @param array $data infolog entry in xmlrpc representation
*
* @return array infolog entry in db format
*/
function xmlrpc2data($data)
{

View File

@ -30,16 +30,25 @@ class infolog_bo
var $vfs_basedir='/infolog';
var $link_pathes = array();
var $send_file_ips = array();
var $tz_offset = 0;
/**
* offset in secconds between user and server-time,
* it need to be add to a server-time to get the user-time or substracted from a user-time to get the server-time
* Set Logging
*
* @var boolean
*/
var $log = false;
/**
* Cached timezone data
*
* @var array id => data
*/
protected static $tz_cache = array();
/**
* current time as timestamp in user-time and server-time
*
* @var int
*/
var $tz_offset_s = 0;
var $user_time_now;
var $now;
/**
* name of timestamps in an InfoLog entry
*
@ -233,9 +242,8 @@ class infolog_bo
$this->user = $GLOBALS['egw_info']['user']['account_id'];
$this->tz_offset = $GLOBALS['egw_info']['user']['preferences']['common']['tz_offset'];
$this->tz_offset_s = 60*60*$this->tz_offset;
$this->user_time_now = time() + $this->tz_offset_s;
$this->now = time();
$this->user_time_now = egw_time::server2user($this->now,'ts');
$this->grants = $GLOBALS['egw']->acl->get_grants('infolog',$this->group_owners ? $this->group_owners : true);
$this->so = new infolog_so($this->grants);
@ -275,7 +283,7 @@ class infolog_bo
/**
* check's if user has the requiered rights on entry $info_id
*
* @param int/array $info data or info_id of infolog entry to check
* @param int|array $info data or info_id of infolog entry to check
* @param int $required_rights EGW_ACL_{READ|EDIT|ADD|DELETE}
* @return boolean
*/
@ -405,15 +413,111 @@ class infolog_bo
return substr($des,0,60).' ...';
}
/**
* Convert the timestamps from given timezone to another and keep dates.
* The timestamps are mostly expected to be in server-time
* and $fromTZId is only used to qualify dates.
*
* @param array $values to modify
* @param string $fromTZId=null
* @param string $toTZId=false
* TZID timezone name e.g. 'UTC'
* or NULL for timestamps in user-time
* or false for timestamps in server-time
*/
function time2time(&$values, $fromTZId=false, $toTZId=null)
{
if ($fromTZId === $toTZId) return;
$tz = egw_time::$server_timezone;
if ($fromTZId)
{
if (!isset(self::$tz_cache[$fromTZId]))
{
self::$tz_cache[$fromTZId] = calendar_timezones::DateTimeZone($fromTZId);
}
$fromTZ = self::$tz_cache[$fromTZId];
}
elseif (is_null($fromTZId))
{
$tz = egw_time::$user_timezone;
$fromTZ = egw_time::$user_timezone;
}
else
{
$fromTZ = egw_time::$server_timezone;
}
if ($toTZId)
{
if (!isset(self::$tz_cache[$toTZId]))
{
self::$tz_cache[$toTZId] = calendar_timezones::DateTimeZone($toTZId);
}
$toTZ = self::$tz_cache[$toTZId];
}
elseif (is_null($toTZId))
{
$toTZ = egw_time::$user_timezone;
}
else
{
$toTZ = egw_time::$server_timezone;
}
foreach($this->timestamps as $key)
{
if ($values[$key])
{
$time = new egw_time($values[$key], $tz);
$time->setTimezone($fromTZ);
if ($key == 'info_enddate')
{
// Set due date to 00:00
$time->setTime(0, 0, 0);
}
if ($time->format('Hi') == '0000')
{
// we keep dates the same in new timezone
$arr = egw_time::to($time,'array');
$time = new egw_time($arr, $toTZ);
}
else
{
$time->setTimezone($toTZ);
}
$values[$key] = egw_time::to($time,'ts');
}
}
}
/**
* convert a date from server to user-time
*
* @param int $ts timestamp in server-time
* @param string $date_format='ts' date-formats: 'ts'=timestamp, 'server'=timestamp in server-time, 'array'=array or string with date-format
* @return mixed depending of $date_format
*/
function date2usertime($ts,$date_format='ts')
{
if (empty($ts) || $date_format == 'server') return $ts;
return egw_time::server2user($ts,$date_format);
}
/**
* Read an infolog entry specified by $info_id
*
* @param int/array $info_id integer id or array with key 'info_id' of the entry to read
* @param int|array $info_id integer id or array with key 'info_id' of the entry to read
* @param boolean $run_link_id2from=true should link_id2from run, default yes,
* need to be set to false if called from link-title to prevent an infinit recursion
* @return array/boolean infolog entry, null if not found or false if no permission to read it
* @param string $date_format='ts' date-formats: 'ts'=timestamp, 'server'=timestamp in server-time,
* 'array'=array or string with date-format
*
* @return array|boolean infolog entry, null if not found or false if no permission to read it
*/
function &read($info_id,$run_link_id2from=true)
function &read($info_id,$run_link_id2from=true,$date_format='ts')
{
if (is_array($info_id))
{
@ -437,13 +541,21 @@ class infolog_bo
}
if ($run_link_id2from) $this->link_id2from($data);
// convert system- to user-time
foreach($this->timestamps as $time)
// convert server- to user-time
if ($date_format == 'ts')
{
if ($data[$time]) $data[$time] += $this->tz_offset_s;
$this->time2time($data);
// pre-cache title and file access
self::set_link_cache($data);
}
else
{
$time = new egw_time($data['info_enddate'], egw_time::$server_timezone);
// Set due date to 00:00
$time->setTime(0, 0, 0);
$data['info_enddate'] = egw_time::to($time,'ts');
}
// pre-cache title and file access
self::set_link_cache($data);
return $data;
}
@ -451,9 +563,9 @@ class infolog_bo
/**
* Delete an infolog entry, evtl. incl. it's children / subs
*
* @param int/array $info_id int id
* @param int|array $info_id int id
* @param boolean $delete_children should the children be deleted
* @param int/boolean $new_parent parent to use for not deleted children if > 0
* @param int|boolean $new_parent parent to use for not deleted children if > 0
* @return boolean True if delete was successful, False otherwise ($info_id does not exist or no rights)
*/
function delete($info_id,$delete_children=False,$new_parent=False)
@ -488,7 +600,7 @@ class infolog_bo
}
}
}
if (!($info = $this->read($info_id))) return false; // should not happen
if (!($info = $this->read($info_id, true, 'server'))) return false; // should not happen
$deleted = $info;
$deleted['info_status'] = 'deleted';
@ -532,9 +644,11 @@ class infolog_bo
* @param array &$values values to write
* @param boolean $check_defaults=true check and set certain defaults
* @param boolean $touch_modified=true touch the modification data and sets the modiefier's user-id
* @param boolean $user2server=true conversion between user- and server-time necessary
*
* @return int/boolean info_id on a successfull write or false
*/
function write(&$values, $check_defaults=True, $touch_modified=True)
function write(&$values, $check_defaults=true, $touch_modified=true, $user2server=true)
{
//echo "boinfolog::write()values="; _debug_array($values);
if ($status_only = $values['info_id'] && !$this->check_access($values['info_id'],EGW_ACL_EDIT))
@ -572,13 +686,13 @@ class infolog_bo
'info_id' => $values['info_id'],
'info_datemodified' => $values['info_datemodified'],
);
foreach($this->responsible_edit as $name)
foreach ($this->responsible_edit as $name)
{
if (isset($backup_values[$name])) $values[$name] = $backup_values[$name];
}
if ($set_completed)
{
$values['info_datecompleted'] = $this->user_time_now;
$values['info_datecompleted'] = $user2server ? $this->user_time_now : $this->now;
$values['info_percent'] = 100;
$forcestatus = true;
$status = 'done';
@ -605,7 +719,7 @@ class infolog_bo
if (!$values['info_datecompleted'] &&
(in_array($values['info_status'],array('done','billed')) || (int)$values['info_percent'] == 100))
{
$values['info_datecompleted'] = $this->user_time_now; // set date completed to today if status == done
$values['info_datecompleted'] = $user2server ? $this->user_time_now : $this->now; // set date completed to today if status == done
}
if (in_array($values['info_status'],array('done','billed')))
{
@ -651,44 +765,64 @@ class infolog_bo
{
$values['info_owner'] = $this->so->user;
}
if ($info_from_set = ($values['info_link_id'] && isset($values['info_from']) && empty($values['info_from'])))
{
$values['info_from'] = $this->link_id2from($values);
}
if ($status_only && !$undelete) $values = array_merge($backup_values,$values);
$to_write = $values;
if ($user2server)
{
// convert user- to server-time
$this->time2time($to_write, null, false);
$time = new egw_time($values['info_enddate'], egw_time::$user_timezone);
// Set due date to 00:00
$time->setTime(0, 0, 0);
$values['info_enddate'] = egw_time::to($time,'ts');
}
else
{
$time = new egw_time($values['info_enddate'], egw_time::$server_timezone);
// Set due date to 00:00
$time->setTime(0, 0, 0);
$to_write['info_enddate'] = egw_time::to($time,'ts');
// convert server- to user-time
$this->time2time($values);
}
if ($touch_modified || !$values['info_datemodified'])
{
// Should only an entry be updated which includes the original modification date?
// Used in the web-GUI to check against a modification by an other user while editing the entry.
// It's now disabled for xmlrpc, as otherwise the xmlrpc code need to be changed!
$xmlrpc = is_object($GLOBALS['server']) && $GLOBALS['server']->last_method;
$check_modified = $values['info_datemodified'] && !$xmlrpc ? $values['info_datemodified']-$this->tz_offset_s : false;
$check_modified = $values['info_datemodified'] && !$xmlrpc ? $to_write['info_datemodified'] : false;
$values['info_datemodified'] = $this->user_time_now;
$to_write['info_datemodified'] = $this->now;
}
if ($touch_modified || !$values['info_modifier'])
{
$values['info_modifier'] = $this->so->user;
$to_write['info_modifier'] = $this->so->user;
}
//_debug_array($values);
// error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()\n".array2string($values)."\n",3,'/tmp/infolog');
$to_write = $values;
if ($status_only && !$undelete) $values = array_merge($backup_values,$values);
// convert user- to system-time
foreach($this->timestamps as $time)
{
if ($to_write[$time]) $to_write[$time] -= $this->tz_offset_s;
}
// we need to get the old values to update the links in customfields and for the tracking
if ($values['info_id'])
{
$old = $this->read($values['info_id'],false);
$old = $this->read($values['info_id'], false, 'server');
}
if(($info_id = $this->so->write($to_write,$check_modified)))
if (($info_id = $this->so->write($to_write,$check_modified)))
{
if (!isset($values['info_type']) || $status_only)
{
$values = $this->read($info_id);
$values = $this->read($info_id, true, 'server');
}
if($values['info_id'] && $old['info_status'] != 'deleted')
if ($values['info_id'] && $old['info_status'] != 'deleted')
{
// update
$GLOBALS['egw']->contenthistory->updateTimeStamp(
@ -705,11 +839,13 @@ class infolog_bo
);
}
$values['info_id'] = $info_id;
$to_write['info_id'] = $info_id;
// if the info responbsible array is not passed, fetch it from old.
if (!array_key_exists('info_responsible',$values)) $values['info_responsible'] = $old['info_responsible'];
if (!is_array($values['info_responsible'])) // this should not happen, bug it does ;-)
{
$values['info_responsible'] = $values['info_responsible'] ? explode(',',$values['info_responsible']) : array();
$to_write['info_responsible'] = $values['info_responsible'];
}
// create (and remove) links in custom fields
customfields_widget::update_customfield_links('infolog',$values,$old,'info_id');
@ -729,8 +865,9 @@ class infolog_bo
if ($old && ($missing_fields = array_diff_key($old,$values)))
{
$values = array_merge($values,$missing_fields);
$to_write = array_merge($to_write,$missing_fields);
}
$this->tracking->track($values,$old,$this->user,$values['info_status'] == 'deleted' || $old['info_status'] == 'deleted');
$this->tracking->track($to_write,$old,$this->user,$values['info_status'] == 'deleted' || $old['info_status'] == 'deleted');
}
if ($info_from_set) $values['info_from'] = '';
@ -764,18 +901,40 @@ class infolog_bo
function &search(&$query)
{
//echo "<p>boinfolog::search(".print_r($query,True).")</p>\n";
if (!empty($query['start']))
{
$query['start'] = egw_time::user2server($query['start'],'ts');
}
$ret = $this->so->search($query);
// convert system- to user-time
if (is_array($ret))
{
foreach($ret as $id => &$data)
foreach ($ret as $id => &$data)
{
if($this->tz_offset_s)
if (!$this->check_access($data,EGW_ACL_READ))
{
foreach($this->timestamps as $time)
unset($ret[$id]);
continue;
}
// convert system- to user-time
foreach ($this->timestamps as $key)
{
if ($data[$key])
{
if ($data[$time]) $data[$time] += $this->tz_offset_s;
$time = new egw_time($data[$key], egw_time::$server_timezone);
if ($key == 'info_enddate') $time->setTime(0, 0,0 ); // Set due date to 00:00
if ($time->format('Hi') == '0000')
{
// we keep dates the same in user-time
$arr = egw_time::to($time,'array');
$time = new egw_time($arr, egw_time::$user_timezone);
}
else
{
$time->setTimezone(egw_time::$user_timezone);
}
$data[$key] = egw_time::to($time,'ts');
}
}
// pre-cache title and file access
@ -880,10 +1039,10 @@ class infolog_bo
*
* Is called as hook to participate in the linking
*
* @param int/array $info int info_id or array with infolog entry
* @return string/boolean string with the title, null if $info not found, false if no perms to view
* @param int|array $info int info_id or array with infolog entry
* @return string|boolean string with the title, null if $info not found, false if no perms to view
*/
function link_title( $info )
function link_title($info)
{
if (!is_array($info))
{
@ -902,16 +1061,16 @@ class infolog_bo
*
* @param array $ids
*/
function link_titles( array $ids )
function link_titles(array $ids)
{
$titles = array();
foreach($this->search($params=array(
foreach ($this->search($params=array(
'col_filter' => array('info_id' => $ids),
)) as $info)
{
$titles[$info['info_id']] = $this->link_title($info);
}
foreach(array_diff($ids,array_keys($titles)) as $id)
foreach (array_diff($ids,array_keys($titles)) as $id)
{
$titles[$id] = false; // we assume every not returned entry to be not readable, as we notify the link class about all deletes
}
@ -927,7 +1086,7 @@ class infolog_bo
* @param array $options Array of options for the search
* @return array with info_id - title pairs of the matching entries
*/
function link_query( $pattern, Array &$options = array() )
function link_query($pattern, Array &$options = array())
{
$query = array(
'search' => $pattern,
@ -940,7 +1099,7 @@ class infolog_bo
$content = array();
if (is_array($ids))
{
foreach($ids as $id => $info )
foreach ($ids as $id => $info )
{
$content[$id] = $this->link_title($id);
}
@ -1010,10 +1169,11 @@ class infolog_bo
}
while ($infos = $this->search($query))
{
foreach($infos as $info)
foreach ($infos as $info)
{
$time = (int) adodb_date('Hi',$info['info_startdate']);
$date = adodb_date('Y/m/d',$info['info_startdate']);
$start = new egw_time($info['info_startdate'],egw_time::$user_timezone);
$time = (int) $start->format('Hi');
$date = $start->format('Y/m/d');
/* As event-like infologs are not showen in current calendar,
we need to present all open infologs to the user! (2006-06-27 nelius)
if ($do_events && !$time ||
@ -1021,11 +1181,11 @@ class infolog_bo
{
continue;
}*/
$title = ($do_events?$GLOBALS['egw']->common->formattime(adodb_date('H',$info['info_startdate']),adodb_date('i',$info['info_startdate'])).' ':'').
$title = ($do_events?common::formattime($start->format('H'),$start->format('i')).' ':'').
$info['info_subject'];
$view = egw_link::view('infolog',$info['info_id']);
$content=array();
foreach($icons = array(
foreach ($icons = array(
$info['info_type'] => 'infolog',
$this->status[$info['info_type']][$info['info_status']] => 'infolog',
) as $name => $app)
@ -1064,7 +1224,7 @@ class infolog_bo
if (isset($args['infolog']) && count($args['infolog']))
{
$icons = $this->so->get_status($args['infolog']);
foreach((array) $icons as $id => $status)
foreach ((array) $icons as $id => $status)
{
if ($status && substr($status,-1) != '%')
{
@ -1092,18 +1252,17 @@ class infolog_bo
{
$this->categories = new categories($this->user,'infolog');
}
if($info_id && $info_id > 0)
$old_cats_preserve = array();
if ($info_id && $info_id > 0)
{
// preserve categories without users read access
$old_infolog = $this->read($info_id);
$old_categories = explode(',',$old_infolog['info_cat']);
$old_cats_preserve = array();
if(is_array($old_categories) && count($old_categories) > 0)
if (is_array($old_categories) && count($old_categories) > 0)
{
foreach($old_categories as $cat_id)
foreach ($old_categories as $cat_id)
{
if($cat_id && !$this->categories->check_perms(EGW_ACL_READ, $cat_id))
if ($cat_id && !$this->categories->check_perms(EGW_ACL_READ, $cat_id))
{
$old_cats_preserve[] = $cat_id;
}
@ -1112,7 +1271,7 @@ class infolog_bo
}
$cat_id_list = array();
foreach($catname_list as $cat_name)
foreach ((array)$catname_list as $cat_name)
{
$cat_name = trim($cat_name);
$cat_id = $this->categories->name2id($cat_name, 'X-');
@ -1133,7 +1292,7 @@ class infolog_bo
}
}
if(is_array($old_cats_preserve) && count($old_cats_preserve) > 0)
if (count($old_cats_preserve) > 0)
{
$cat_id_list = array_merge($old_cats_preserve, $cat_id_list);
}
@ -1234,19 +1393,19 @@ class infolog_bo
{
case 'notify_due_responsible':
$info['message'] = lang('%1 you are responsible for is due at %2',$this->enums['type'][$info['info_type']],
$this->tracking->datetime($info['info_enddate']-$this->tz_offset_s,false));
$this->tracking->datetime($info['info_enddate'],false));
break;
case 'notify_due_delegated':
$info['message'] = lang('%1 you delegated is due at %2',$this->enums['type'][$info['info_type']],
$this->tracking->datetime($info['info_enddate']-$this->tz_offset_s,false));
$this->tracking->datetime($info['info_enddate'],false));
break;
case 'notify_start_responsible':
$info['message'] = lang('%1 you are responsible for is starting at %2',$this->enums['type'][$info['info_type']],
$this->tracking->datetime($info['info_startdate']-$this->tz_offset_s,null));
$this->tracking->datetime($info['info_startdate'],null));
break;
case 'notify_start_delegated':
$info['message'] = lang('%1 you delegated is starting at %2',$this->enums['type'][$info['info_type']],
$this->tracking->datetime($info['info_startdate']-$this->tz_offset_s,null));
$this->tracking->datetime($info['info_startdate'],null));
break;
}
//error_log("notifiying $user($email) about $info[info_subject]: $info[message]");
@ -1363,109 +1522,303 @@ class infolog_bo
/**
* Try to find a matching db entry
* This expects timestamps to be in server-time.
*
* @param array $egwData the vTODO data we try to find
* @param array $infoData the infolog data we try to find
* @param boolean $relax=false if asked to relax, we only match against some key fields
* @return the infolog_id of the matching entry or false (if none matches)
* @param string $tzid=null timezone, null => user time
*
* @return array of infolog_ids of matching entries
*/
function findVTODO($egwData, $relax=false)
function findInfo($infoData, $relax=false, $tzid=null)
{
if (!empty($egwData['info_uid']))
{
$filter = array('col_filter' => array('info_uid' => $egwData['info_uid']));
if (($found = $this->search($filter))
&& ($uidmatch = array_shift($found)))
{
return $uidmatch['info_id'];
}
}
unset($egwData['info_uid']);
$foundInfoLogs = array();
$filter = array();
$description = '';
if (!empty($egwData['info_des'])) {
$description = trim(preg_replace("/\r?\n?\\[[A-Z_]+:.*\\]/i", '', $egwData['info_des']));
unset($egwData['info_des']);
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
. '('. ($relax ? 'RELAX, ': 'EXACT, ') . $tzid . ')[InfoData]:'
. array2string($infoData));
}
if ($infoData['info_id']
&& ($egwData = $this->read($infoData['info_id'], true, 'server')))
{
// we only do a simple consistency check
if (!$relax || strpos($egwData['info_subject'], $infoData['info_subject']) === 0)
{
return array($egwData['info_id']);
}
if (!$relax) return array();
}
unset($infoData['info_id']);
if (!$relax && !empty($infoData['info_uid']))
{
$filter = array('col_filter' => array('info_uid' => $infoData['info_uid']));
foreach($this->so->search($filter) as $egwData)
{
if (!$this->check_access($egwData,EGW_ACL_READ)) continue;
$foundInfoLogs[$egwData['info_id']] = $egwData['info_id'];
}
return $foundInfoLogs;
}
unset($infoData['info_uid']);
if (empty($infoData['info_des']))
{
$description = false;
}
else
{
// ignore meta information appendices
$description = trim(preg_replace('/\s*\[[A-Z_]+:.*\].*/im', '', $infoData['info_des']));
$text = trim(preg_replace('/\s*\[[A-Z_]+:.*\]/im', '', $infoData['info_des']));
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
. "()[description]: $description");
}
// Avoid quotation problems
$description = preg_replace("/[^\x20-\x7F].*/", '', $description);
if (strlen($description)) {
$filter['search'] = $description;
}
}
if ($egwData['info_id']
&& ($found = $this->read($egwData['info_id'])))
{
// We only do a simple consistency check
if ($found['info_subject'] == $egwData['info_subject']
&& strpos($found['info_des'], $description) === 0)
if (preg_match_all('/[\x20-\x7F]*/m', $text, $matches, PREG_SET_ORDER))
{
return $found['info_id'];
$text = '';
foreach ($matches as $chunk)
{
if (strlen($text) < strlen($chunk[0]))
{
$text = $chunk[0];
}
}
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
. "()[search]: $text");
}
$filter['search'] = $text;
}
}
unset($egwData['info_id']);
$this->time2time($infoData, $tzid, false);
$filter['col_filter'] = $infoData;
// priority does not need to match
unset($egwData['info_priority']);
unset($filter['col_filter']['info_priority']);
// we ignore description and location first
unset($filter['col_filter']['info_des']);
unset($filter['col_filter']['info_location']);
$filter['col_filter'] = $egwData;
foreach ($this->so->search($filter) as $itemID => $egwData)
{
if (!$this->check_access($egwData,EGW_ACL_READ)) continue;
if($foundItems = $this->search($filter)) {
if(count($foundItems) > 0) {
$itemIDs = array_keys($foundItems);
return $itemIDs[0];
switch ($infoData['info_type'])
{
case 'task':
if (!empty($egwData['info_location']))
{
$egwData['info_location'] = str_replace("\r\n", "\n", $egwData['info_location']);
}
if (!$relax &&
!empty($infoData['info_location']) && (empty($egwData['info_location'])
|| strpos($egwData['info_location'], $infoData['info_location']) !== 0))
{
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
. '()[location mismatch]: '
. $infoData['info_location'] . ' <> ' . $egwData['info_location']);
}
continue;
}
default:
if (!empty($egwData['info_des']))
{
$egwData['info_des'] = str_replace("\r\n", "\n", $egwData['info_des']);
}
if (!$relax && ($description && empty($egwData['info_des'])
|| !empty($egwData['info_des']) && empty($infoData['info_des'])
|| strpos($egwData['info_des'], $description) === false))
{
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
. '()[description mismatch]: '
. $infoData['info_des'] . ' <> ' . $egwData['info_des']);
}
continue;
}
// no further criteria to match
$foundInfoLogs[$egwData['info_id']] = $egwData['info_id'];
}
}
$filter = array();
if (!$relax && strlen($description)) {
$filter['search'] = $description;
if (!$relax && !empty($foundInfoLogs))
{
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
. '()[FOUND]:' . array2string($foundInfoLogs));
}
return $foundInfoLogs;
}
$filter['col_filter'] = $egwData;
if ($relax)
{
unset($filter['search']);
}
// search for date only match
// search for matches by date only
unset($filter['col_filter']['info_startdate']);
unset($filter['col_filter']['info_enddate']);
unset($filter['col_filter']['info_datecompleted']);
// Some devices support lesser stati
unset($filter['col_filter']['info_status']);
// try tasks without category
unset($filter['col_filter']['info_cat']);
#Horde::logMessage("findVTODO Filter\n"
# . print_r($filter, true),
# __FILE__, __LINE__, PEAR_LOG_DEBUG);
foreach ($this->search($filter) as $itemID => $taskData) {
# Horde::logMessage("findVTODO Trying\n"
# . print_r($taskData, true),
# __FILE__, __LINE__, PEAR_LOG_DEBUG);
if (isset($egwData['info_cat'])
&& isset($taskData['info_cat']) && $taskData['info_cat']
&& $egwData['info_cat'] != $taskData['info_cat']) continue;
if (isset($egwData['info_startdate'])
&& isset($taskData['info_startdate']) && $taskData['info_startdate']) {
$parts = @getdate($taskData['info_startdate']);
$startdate = @mktime(0, 0, 0, $parts['mon'], $parts['mday'], $parts['year']);
if ($egwData['info_startdate'] != $startdate) continue;
// Horde::logMessage("findVTODO Filter\n"
// . print_r($filter, true),
// __FILE__, __LINE__, PEAR_LOG_DEBUG);
foreach ($this->so->search($filter) as $itemID => $egwData)
{
if (!$this->check_access($egwData,EGW_ACL_READ)) continue;
// Horde::logMessage("findVTODO Trying\n"
// . print_r($egwData, true),
// __FILE__, __LINE__, PEAR_LOG_DEBUG);
if (isset($infoData['info_cat'])
&& isset($egwData['info_cat']) && $egwData['info_cat']
&& $infoData['info_cat'] != $egwData['info_cat'])
{
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
. '()[category mismatch]: '
. $infoData['info_cat'] . ' <> ' . $egwData['info_cat']);
}
continue;
}
// some clients don't support DTSTART
if (isset($egwData['info_startdate'])
&& (!isset($taskData['info_startdate']) || !$taskData['info_startdate'])
&& !$relax) continue;
if (isset($egwData['info_datecompleted'])
&& isset($taskData['info_datecompleted']) && $taskData['info_datecompleted']) {
$parts = @getdate($taskData['info_datecompleted']);
$enddate = @mktime(0, 0, 0, $parts['mon'], $parts['mday'], $parts['year']);
if ($egwData['info_datecompleted'] != $enddate) continue;
if (isset($infoData['info_startdate']) && $infoData['info_startdate'])
{
// We got a startdate from client
if (isset($egwData['info_startdate']) && $egwData['info_startdate'])
{
// We compare the date only
$taskTime = new egw_time($infoData['info_startdate'],egw_time::$server_timezone);
$egwTime = new egw_time($egwData['info_startdate'],egw_time::$server_timezone);
if ($taskTime->format('Ymd') != $egwTime->format('Ymd'))
{
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
. '()[start mismatch]: '
. $taskTime->format('Ymd') . ' <> ' . $egwTime->format('Ymd'));
}
continue;
}
}
elseif (!$relax)
{
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
. '()[start mismatch]');
}
continue;
}
}
if ((isset($egwData['info_datecompleted'])
&& (!isset($taskData['info_datecompleted']) || !$taskData['info_datecompleted'])) ||
(!isset($egwData['info_datecompleted'])
&& isset($taskData['info_datecompleted']) && $taskData['info_datecompleted'])
&& !$relax) continue;
return($itemID);
if ($infoData['info_type'] == 'task')
{
if (isset($infoData['info_status']) && isset($egwData['info_status'])
&& $egwData['info_status'] == 'done'
&& $infoData['info_status'] != 'done' ||
$egwData['info_status'] != 'done'
&& $infoData['info_status'] == 'done')
{
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
. '()[status mismatch]: '
. $infoData['info_status'] . ' <> ' . $egwData['info_status']);
}
continue;
}
if (isset($infoData['info_enddate']) && $infoData['info_enddate'])
{
// We got a enddate from client
if (isset($egwData['info_enddate']) && $egwData['info_enddate'])
{
// We compare the date only
$taskTime = new egw_time($infoData['info_enddate'],egw_time::$server_timezone);
$egwTime = new egw_time($egwData['info_enddate'],egw_time::$server_timezone);
if ($taskTime->format('Ymd') != $egwTime->format('Ymd'))
{
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
. '()[DUE mismatch]: '
. $taskTime->format('Ymd') . ' <> ' . $egwTime->format('Ymd'));
}
continue;
}
}
elseif (!$relax)
{
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
. '()[DUE mismatch]');
}
continue;
}
}
if (isset($infoData['info_datecompleted']) && $infoData['info_datecompleted'])
{
// We got a completed date from client
if (isset($egwData['info_datecompleted']) && $egwData['info_datecompleted'])
{
// We compare the date only
$taskTime = new egw_time($infoData['info_datecompleted'],egw_time::$server_timezone);
$egwTime = new egw_time($egwData['info_datecompleted'],egw_time::$server_timezone);
if ($taskTime->format('Ymd') != $egwTime->format('Ymd'))
{
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
. '()[completed mismatch]: '
. $taskTime->format('Ymd') . ' <> ' . $egwTime->format('Ymd'));
}
continue;
}
}
elseif (!$relax)
{
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
. '()[completed mismatch]');
}
continue;
}
}
elseif (!$relax && isset($egwData['info_datecompleted']) && $egwData['info_datecompleted'])
{
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
. '()[completed mismatch]');
}
continue;
}
}
$foundInfoLogs[$itemID] = $itemID;
}
return false;
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
. '()[FOUND]:' . array2string($foundInfoLogs));
}
return $foundInfoLogs;
}
}

View File

@ -29,10 +29,11 @@ class infolog_groupdav extends groupdav_handler
* @param string $app 'calendar', 'addressbook' or 'infolog'
* @param int $debug=null debug-level to set
* @param string $base_uri=null base url of handler
* @param string $principalURL=null pricipal url of handler
*/
function __construct($app,$debug=null,$base_uri=null)
function __construct($app,$debug=null,$base_uri=null,$principalURL=null)
{
parent::__construct($app,$debug,$base_uri);
parent::__construct($app,$debug,$base_uri,$principalURL);
$this->bo = new infolog_bo();
}
@ -56,7 +57,7 @@ class infolog_groupdav extends groupdav_handler
if (!is_array($info)) $info = $this->bo->read($info);
$name = $info[self::PATH_ATTRIBUTE];
}
return '/infolog/'.$name.'.ics';
return $name.'.ics';
}
/**
@ -72,33 +73,88 @@ class infolog_groupdav extends groupdav_handler
{
$starttime = microtime(true);
$myself = ($user == $GLOBALS['egw_info']['user']['account_id']);
if ($options['filters'])
{
foreach($options['filters'] as $filter)
{
switch($filter['name'])
{
case 'comp-filter':
if ($this->debug > 1) error_log(__METHOD__."($options[path],...) comp-filter='{$filter['attrs']['name']}'");
switch($filter['attrs']['name'])
{
case 'VCALENDAR':
continue;
case 'VTODO':
break 3;
default: // We don't handle this
return false;
}
}
}
}
// check if we have to return the full calendar data or just the etag's
if (!($calendar_data = $options['props'] == 'all' && $options['root']['ns'] == groupdav::CALDAV) && is_array($options['props']))
{
foreach($options['props'] as $prop)
{
if ($prop['name'] == 'calendar-data')
{
$calendar_data = true;
break;
}
}
}
// todo add a filter to limit how far back entries from the past get synced
$filter = array(
'info_type' => 'task',
);
//if (!$myself) $filter['info_owner'] = $user;
if ($id) $filter['info_id'] = $id; // propfind on a single id
// ToDo: add parameter to only return id & etag
if (($tasks =& $this->bo->search($params=array(
'order' => 'info_datemodified',
'sort' => 'DESC',
'filter' => 'own', // filter my: entries user is responsible for,
// filter own: entries the user own or is responsible for
'filter' => ($myself ? 'own' : 'own'), // filter my: entries user is responsible for,
// filter own: entries the user own or is responsible for
'date_format' => 'server',
'col_filter' => $filter,
))))
{
foreach($tasks as &$task)
{
$props = array(
HTTP_WebDAV_Server::mkprop('getetag',$this->get_etag($task)),
HTTP_WebDAV_Server::mkprop('getcontenttype',$this->agent != 'kde' ?
'text/calendar; charset=utf-8; component=VTODO' : 'text/calendar'), // Konqueror (3.5) dont understand it otherwise
// getlastmodified and getcontentlength are required by WebDAV and Cadaver eg. reports 404 Not found if not set
HTTP_WebDAV_Server::mkprop('getlastmodified', $task['info_datemodified']),
HTTP_WebDAV_Server::mkprop('resourcetype',''), // DAVKit requires that attribute!
HTTP_WebDAV_Server::mkprop('getcontentlength',''),
);
if ($calendar_data)
{
$handler = $this->_get_handler();
$content = $handler->exportVTODO($task,'2.0','PUBLISH');
$props[] = HTTP_WebDAV_Server::mkprop('getcontentlength',bytes($content));
$props[] = HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-data',$content);
}
else
{
$props[] = HTTP_WebDAV_Server::mkprop('getcontentlength', ''); // expensive to calculate and no CalDAV client uses it
}
$files['files'][] = array(
'path' => self::get_path($task),
'props' => array(
HTTP_WebDAV_Server::mkprop('getetag',$this->get_etag($task)),
HTTP_WebDAV_Server::mkprop('getcontenttype',$this->agent != 'kde' ?
'text/calendar; charset=utf-8; component=VTODO' : 'text/calendar'), // Konqueror (3.5) dont understand it otherwise
// getlastmodified and getcontentlength are required by WebDAV and Cadaver eg. reports 404 Not found if not set
HTTP_WebDAV_Server::mkprop('getlastmodified', $task['info_datemodified']),
HTTP_WebDAV_Server::mkprop('getcontentlength',''),
),
'path' => $path.self::get_path($task),
'props' => $props,
);
}
}
@ -120,7 +176,7 @@ class infolog_groupdav extends groupdav_handler
return $task;
}
$handler = $this->_get_handler();
$options['data'] = $handler->exportVTODO($id,'2.0',false,false); // keep UID the client set and no extra charset attributes
$options['data'] = $handler->exportVTODO($id,'2.0','PUBLISH');
$options['mimetype'] = 'text/calendar; charset=utf-8';
header('Content-Encoding: identity');
header('ETag: '.$this->get_etag($task));
@ -137,22 +193,61 @@ class infolog_groupdav extends groupdav_handler
*/
function put(&$options,$id,$user=null)
{
$ok = $this->_common_get_put_delete('PUT',$options,$id);
if (!is_null($ok) && !is_array($ok))
if ($this->debug) error_log(__METHOD__."($id, $user)".print_r($options,true));
$oldTask = $this->_common_get_put_delete('PUT',$options,$id);
if (!is_null($oldTask) && !is_array($oldTask))
{
return $ok;
return $oldTask;
}
$handler = $this->_get_handler();
if (!($info_id = $handler->importVTODO($options['content'],is_numeric($id) ? $id : -1)))
$vTodo = htmlspecialchars_decode($options['content']);
if (is_array($oldTask))
{
$taskId = $oldTask['info_id'];
$retval = true;
}
else
{
// new entry?
if (($foundTasks = $handler->searchVTODO($vTodo)))
{
if (($taskId = array_shift($foundTasks)) &&
($oldTask = $this->bo->read($taskId)))
{
$retval = '301 Moved Permanently';
}
else
{
// to be safe
$taskId = -1;
$retval = '201 Created';
}
}
else
{
// new entry
$taskId = -1;
$retval = '201 Created';
}
}
if (!($infoId = $handler->importVTODO($vTodo, $taskId, false, $user)))
{
if ($this->debug) error_log(__METHOD__."(,$id) import_vtodo($options[content]) returned false");
return '403 Forbidden';
}
header('ETag: '.$this->get_etag($info_id));
if (is_null($ok) || $id != $info_id)
if ($infoId != $taskId) $retval = '201 Created';
header('ETag: '.$this->get_etag($infoId));
if ($retval !== true)
{
header('Location: '.$this->base_uri.self::get_path($info_id));
return '201 Created';
$path = preg_replace('|(.*)/[^/]*|', '\1/', $options['path']);
header('Location: '.$this->base_uri.$path.self::get_path($infoId));
return $retval;
}
return true;
}
@ -181,7 +276,7 @@ class infolog_groupdav extends groupdav_handler
*/
function read($id)
{
return $this->bo->read($id,false);
return $this->bo->read($id,false,'server');
}
/**
@ -206,13 +301,45 @@ class infolog_groupdav extends groupdav_handler
{
if (!is_array($info))
{
$info = $this->bo->read($info);
$info = $this->bo->read($info,true,'server');
}
if (!is_array($info) || !isset($info['info_id']) || !isset($info['info_datemodified']))
{
return false;
}
return '"'.$info['info_id'].':'.$info['info_datemodified'].'"';
return 'EGw-'.$info['info_id'].':'.$info['info_datemodified'].'-wGE';
}
/**
* Add extra properties for calendar collections
*
* @param array $props=array() regular props by the groupdav handler
* @param string $displayname
* @param string $base_uri=null base url of handler
* @return array
*/
static function extra_properties(array $props=array(), $displayname, $base_uri=null)
{
// calendar description
$displayname = $GLOBALS['egw']->translation->convert(lang('Tasks of') . ' ' .
$displayname,
$GLOBALS['egw']->translation->charset(),'utf-8');
$props[] = HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-description',$displayname);
// email of the current user, see caldav-sheduling draft
$props[] = HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-user-address-set',array(
HTTP_WebDAV_Server::mkprop('href','MAILTO:'.$GLOBALS['egw_info']['user']['email'])));
// supported components, currently only VEVENT
$props[] = HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'supported-calendar-component-set',array(
// HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'comp',array('name' => 'VEVENT')),
HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'comp',array('name' => 'VTODO')),
));
$props[] = HTTP_WebDAV_Server::mkprop('supported-report-set',array(
HTTP_WebDAV_Server::mkprop('supported-report',array(
HTTP_WebDAV_Server::mkprop('report',
HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-multiget'))))));
return $props;
}
/**

View File

@ -73,6 +73,13 @@ class infolog_ical extends infolog_bo
*/
var $uidExtension = false;
/**
* user preference: Use this timezone for import from and export to device
*
* @var string
*/
var $tzid = null;
/**
* Client CTCap Properties
*
@ -104,14 +111,21 @@ class infolog_ical extends infolog_bo
/**
* Exports one InfoLog tast to an iCalendar VTODO
*
* @param int $_taskID info_id
* @param int|array $task infolog_id or infolog-tasks data
* @param string $_version='2.0' could be '1.0' too
* @param string $_method='PUBLISH'
* @return string/boolean string with vCal or false on error (eg. no permission to read the event)
*/
function exportVTODO($_taskID, $_version='2.0',$_method='PUBLISH')
function exportVTODO($task, $_version='2.0',$_method='PUBLISH')
{
$taskData = $this->read($_taskID);
if (is_array($task))
{
$taskData = $task;
}
else
{
if (!($taskData = $this->read($task, true, 'server'))) return false;
}
if ($taskData['info_id_parent'])
{
@ -144,10 +158,49 @@ class infolog_ical extends infolog_bo
$taskData = $GLOBALS['egw']->translation->convert($taskData,
$GLOBALS['egw']->translation->charset(), 'UTF-8');
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()\n" .
array2string($taskData)."\n",3,$this->logfile);
}
$vcal = new Horde_iCalendar;
$vcal->setAttribute('VERSION',$_version);
$vcal->setAttribute('METHOD',$_method);
$tzid = $this->tzid;
if ($tzid && $tzid != 'UTC')
{
// check if we have vtimezone component data for tzid of event, if not default to user timezone (default to server tz)
if (!($vtimezone = calendar_timezones::tz2id($tzid,'component')))
{
error_log(__METHOD__."() unknown TZID='$tzid', defaulting to user timezone '".egw_time::$user_timezone->getName()."'!");
$vtimezone = calendar_timezones::tz2id($tzid=egw_time::$user_timezone->getName(),'component');
$tzid = null;
}
if (!isset(self::$tz_cache[$tzid]))
{
self::$tz_cache[$tzid] = calendar_timezones::DateTimeZone($tzid);
}
// $vtimezone is a string with a single VTIMEZONE component, afaik Horde_iCalendar can not add it directly
// --> we have to parse it and let Horde_iCalendar add it again
$horde_vtimezone = Horde_iCalendar::newComponent('VTIMEZONE',$container=false);
$horde_vtimezone->parsevCalendar($vtimezone,'VTIMEZONE');
// DTSTART must be in local time!
$standard = $horde_vtimezone->findComponent('STANDARD');
$dtstart = $standard->getAttribute('DTSTART');
$dtstart = new egw_time($dtstart, egw_time::$server_timezone);
$dtstart->setTimezone(self::$tz_cache[$tzid]);
$standard->setAttribute('DTSTART', $dtstart->format('Ymd\THis'), array(), false);
$daylight = $horde_vtimezone->findComponent('DAYLIGHT');
$dtstart = $daylight->getAttribute('DTSTART');
$dtstart = new egw_time($dtstart, egw_time::$server_timezone);
$dtstart->setTimezone(self::$tz_cache[$tzid]);
$daylight->setAttribute('DTSTART', $dtstart->format('Ymd\THis'), array(), false);
$vcal->addComponent($horde_vtimezone);
}
$vevent = Horde_iCalendar::newComponent('VTODO',$vcal);
if (!isset($this->clientProperties['SUMMARY']['Size']))
@ -253,15 +306,15 @@ class infolog_ical extends infolog_bo
if ($taskData['info_startdate'])
{
self::setDateOrTime($vevent,'DTSTART',$taskData['info_startdate']);
self::setDateOrTime($vevent, 'DTSTART', $taskData['info_startdate'], $tzid);
}
if ($taskData['info_enddate'])
{
self::setDateOrTime($vevent,'DUE',$taskData['info_enddate']);
self::setDateOrTime($vevent, 'DUE', $taskData['info_enddate'], false); // export always as date
}
if ($taskData['info_datecompleted'])
{
self::setDateOrTime($vevent,'COMPLETED',$taskData['info_datecompleted']);
self::setDateOrTime($vevent, 'COMPLETED', $taskData['info_datecompleted'], $tzid);
}
$vevent->setAttribute('DTSTAMP',time());
@ -298,27 +351,67 @@ class infolog_ical extends infolog_bo
}
/**
* Check if use set a date or date+time and export it as such
* set date-time attribute to DATE or DATE-TIME depending on value
* 00:00 uses DATE else DATE-TIME
*
* @param Horde_iCalendar_* $vevent
* @param string $attr attribute name
* @param int $value timestamp
* @param int $time timestamp in server-time
* @param string $tzid timezone to use for client, null for user-time, false for server-time
*/
static function setDateOrTime($vevent,$attr,$value)
static function setDateOrTime(&$vevent, $attr, $time, $tzid)
{
// check if use set only a date --> export it as such
if (date('H:i',$value) == '00:00')
$params = array();
if ($tzid)
{
$vevent->setAttribute($attr,array(
'year' => date('Y',$value),
'month' => date('m',$value),
'mday' => date('d',$value),
),array('VALUE' => 'DATE'));
if (!isset(self::$tz_cache[$tzid]))
{
self::$tz_cache[$tzid] = calendar_timezones::DateTimeZone($tzid);
}
$tz = self::$tz_cache[$tzid];
}
elseif(is_null($tzid))
{
$tz = egw_time::$user_timezone;
}
else
{
$vevent->setAttribute($attr,$value);
$tz = egw_time::$server_timezone;
}
if (!is_a($time,'DateTime'))
{
$time = new egw_time($time,egw_time::$server_timezone);
}
$time->setTimezone($tz);
// check for date --> export it as such
if ($time->format('Hi') == '0000')
{
$arr = egw_time::to($time, 'array');
$value = array(
'year' => $arr['year'],
'month' => $arr['month'],
'mday' => $arr['day']);
$params['VALUE'] = 'DATE';
}
else
{
if ($tzid == 'UTC')
{
$value = $time->format('Ymd\THis\Z');
}
elseif ($tzid)
{
$value = $time->format('Ymd\THis');
$params['TZID'] = $tzid;
}
else
{
$value = egw_time::to($time, 'ts');
}
}
$vevent->setAttribute($attr, $value, $params);
}
/**
@ -327,11 +420,26 @@ class infolog_ical extends infolog_bo
* @param string $_vcalData
* @param int $_taskID=-1 info_id, default -1 = new entry
* @param boolean $merge=false merge data with existing entry
* @param int $user=null delegate new task to this account_id, default null
* @return int|boolean integer info_id or false on error
*/
function importVTODO(&$_vcalData, $_taskID=-1, $merge=false)
function importVTODO(&$_vcalData, $_taskID=-1, $merge=false, $user=null)
{
if (!($taskData = $this->vtodotoegw($_vcalData,$_taskID))) return false;
if ($this->tzid)
{
date_default_timezone_set($this->tzid);
}
$taskData = $this->vtodotoegw($_vcalData,$_taskID);
if ($this->tzid)
{
date_default_timezone_set($GLOBALS['egw_info']['server']['server_timezone']);
}
if (!$taskData) return false;
// keep the dates
$this->time2time($taskData, $this->tzid, false);
// we suppose that a not set status in a vtodo means that the task did not started yet
if (empty($taskData['info_status']))
@ -344,13 +452,18 @@ class infolog_ical extends infolog_bo
$taskData['info_datecompleted'] = 0;
}
if (!is_null($user))
{
$taskData['info_owner'] = $user;
}
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()\n" .
array2string($taskData)."\n",3,$this->logfile);
}
return $this->write($taskData);
return $this->write($taskData, true, true, false);
}
/**
@ -359,18 +472,29 @@ class infolog_ical extends infolog_bo
* @param string $_vcalData VTODO
* @param int $contentID=null infolog_id (or null, if unkown)
* @param boolean $relax=false if true, a weaker match algorithm is used
* @return infolog_id of a matching entry or false, if nothing was found
*
* @return array of infolog_ids of matching entries
*/
function searchVTODO($_vcalData, $contentID=null, $relax=false) {
$result = false;
function searchVTODO($_vcalData, $contentID=null, $relax=false)
{
$result = array();
if (($egwData = $this->vtodotoegw($_vcalData,$contentID)))
if ($this->tzid)
{
date_default_timezone_set($this->tzid);
}
$taskData = $this->vtodotoegw($_vcalData,$contentID);
if ($this->tzid)
{
date_default_timezone_set($GLOBALS['egw_info']['server']['server_timezone']);
}
if ($taskData)
{
if ($contentID)
{
$egwData['info_id'] = $contentID;
$taskData['info_id'] = $contentID;
}
$result = $this->findVTODO($egwData, $relax);
$result = $this->findInfo($taskData, $relax, $this->tzid);
}
return $result;
}
@ -386,7 +510,7 @@ class infolog_ical extends infolog_bo
{
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()\n" .
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."($_taskID)\n" .
array2string($_vcalData)."\n",3,$this->logfile);
}
@ -412,13 +536,10 @@ class infolog_ical extends infolog_bo
$minimum_uid_length = 8;
}
$taskData = false;
foreach ($vcal->getComponents() as $component)
{
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()\n" .
array2string($component)."\n",3,$this->logfile);
}
if (!is_a($component, 'Horde_iCalendar_vtodo'))
{
if ($this->log)
@ -426,140 +547,135 @@ class infolog_ical extends infolog_bo
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
"(): Not a vTODO container, skipping...\n",3,$this->logfile);
}
continue;
}
else
$taskData = array();
$taskData['info_type'] = 'task';
if ($_taskID > 0)
{
$taskData = array();
$taskData['info_type'] = 'task';
if ($_taskID > 0)
{
$taskData['info_id'] = $_taskID;
}
foreach ($component->_attributes as $attributes)
{
//$attributes['value'] = trim($attributes['value']);
if (!strlen($attributes['value'])) continue;
switch ($attributes['name'])
{
case 'CLASS':
$taskData['info_access'] = strtolower($attributes['value']);
break;
case 'DESCRIPTION':
$value = $attributes['value'];
if (preg_match('/\s*\[UID:(.+)?\]/Usm', $value, $matches))
{
if (!isset($taskData['info_uid'])
&& strlen($matches[1]) >= $minimum_uid_length)
{
$taskData['info_uid'] = $matches[1];
}
//$value = str_replace($matches[0], '', $value);
}
if (preg_match('/\s*\[PARENT_UID:(.+)?\]/Usm', $value, $matches))
{
if (!isset($taskData['info_id_parent'])
&& strlen($matches[1]) >= $minimum_uid_length)
{
$taskData['info_id_parent'] = $this->getParentID($matches[1]);
}
//$value = str_replace($matches[0], '', $value);
}
$taskData['info_des'] = $value;
break;
case 'LOCATION':
$taskData['info_location'] = $attributes['value'];
break;
case 'DUE':
// eGroupWare uses date only
$parts = @getdate($attributes['value']);
$value = @mktime(0, 0, 0, $parts['mon'], $parts['mday'], $parts['year']);
$taskData['info_enddate'] = $value;
break;
case 'COMPLETED':
$taskData['info_datecompleted'] = $attributes['value'];
break;
case 'DTSTART':
$taskData['info_startdate'] = $attributes['value'];
break;
case 'PRIORITY':
if (0 <= $attributes['value'] && $attributes['value'] <= 9) {
if ($this->productManufacturer == 'funambol' &&
(strpos($this->productName, 'outlook') !== false
|| strpos($this->productName, 'pocket pc') !== false))
{
$taskData['info_priority'] = (int) $this->priority_funambol2egw[$attributes['value']];
}
else
{
$taskData['info_priority'] = (int) $this->priority_ical2egw[$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 'RELATED-TO':
$taskData['info_id_parent'] = $this->getParentID($attributes['value']);
break;
case 'CATEGORIES':
if ($attributes['value'])
{
if($version == '1.0')
{
$vcats = $this->find_or_add_categories(explode(';',$attributes['value']), $_taskID);
}
else
{
$cats = $this->find_or_add_categories(explode(',',$attributes['value']), $_taskID);
}
$taskData['info_cat'] = $cats[0];
}
break;
case 'UID':
if (strlen($attributes['value']) >= $minimum_uid_length) {
$taskData['info_uid'] = $attributes['value'];
}
break;
case 'PERCENT-COMPLETE':
$taskData['info_percent'] = (int) $attributes['value'];
break;
}
}
# the horde ical class does already convert in parsevCalendar
# do NOT convert here
#$taskData = $GLOBALS['egw']->translation->convert($taskData, 'UTF-8');
Horde::logMessage("vtodotoegw:\n" . print_r($taskData, true), __FILE__, __LINE__, PEAR_LOG_DEBUG);
return $taskData;
$taskData['info_id'] = $_taskID;
}
foreach ($component->_attributes as $attribute)
{
//$attribute['value'] = trim($attribute['value']);
if (!strlen($attribute['value'])) continue;
switch ($attribute['name'])
{
case 'CLASS':
$taskData['info_access'] = strtolower($attribute['value']);
break;
case 'DESCRIPTION':
$value = str_replace("\r\n", "\n", $attribute['value']);
if (preg_match('/\s*\[UID:(.+)?\]/Usm', $value, $matches))
{
if (!isset($taskData['info_uid'])
&& strlen($matches[1]) >= $minimum_uid_length)
{
$taskData['info_uid'] = $matches[1];
}
//$value = str_replace($matches[0], '', $value);
}
if (preg_match('/\s*\[PARENT_UID:(.+)?\]/Usm', $value, $matches))
{
if (!isset($taskData['info_id_parent'])
&& strlen($matches[1]) >= $minimum_uid_length)
{
$taskData['info_id_parent'] = $this->getParentID($matches[1]);
}
//$value = str_replace($matches[0], '', $value);
}
$taskData['info_des'] = $value;
break;
case 'LOCATION':
$taskData['info_location'] = str_replace("\r\n", "\n", $attribute['value']);
break;
case 'DUE':
// eGroupWare uses date only
$parts = @getdate($attribute['value']);
$value = @mktime(0, 0, 0, $parts['mon'], $parts['mday'], $parts['year']);
$taskData['info_enddate'] = $value;
break;
case 'COMPLETED':
$taskData['info_datecompleted'] = $attribute['value'];
break;
case 'DTSTART':
$taskData['info_startdate'] = $attribute['value'];
break;
case 'PRIORITY':
if (0 <= $attribute['value'] && $attribute['value'] <= 9)
{
if ($this->productManufacturer == 'funambol' &&
(strpos($this->productName, 'outlook') !== false
|| strpos($this->productName, 'pocket pc') !== false))
{
$taskData['info_priority'] = (int) $this->priority_funambol2egw[$attribute['value']];
}
else
{
$taskData['info_priority'] = (int) $this->priority_ical2egw[$attribute['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($attribute['value'],
$attr['name'] == 'X-INFOLOG-STATUS' ? $attr['value'] : null);
break;
case 'SUMMARY':
$taskData['info_subject'] = str_replace("\r\n", "\n", $attribute['value']);
break;
case 'RELATED-TO':
$taskData['info_id_parent'] = $this->getParentID($attribute['value']);
break;
case 'CATEGORIES':
if (!empty($attribute['value']))
{
$cats = $this->find_or_add_categories(explode(',',$attribute['value']), $_taskID);
$taskData['info_cat'] = $cats[0];
}
break;
case 'UID':
if (strlen($attribute['value']) >= $minimum_uid_length)
{
$taskData['info_uid'] = $attribute['value'];
}
break;
case 'PERCENT-COMPLETE':
$taskData['info_percent'] = (int) $attribute['value'];
break;
}
}
break;
}
return false;
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."($_taskID)\n" .
($taskData ? array2string($taskData) : 'FALSE') . "\n",3,$this->logfile);
}
return $taskData;
}
/**
@ -571,7 +687,8 @@ class infolog_ical extends infolog_bo
*/
function exportVNOTE($_noteID, $_type)
{
$note = $this->read($_noteID);
if(!($note = $this->read($_noteID, true, 'server'))) return false;
$note = $GLOBALS['egw']->translation->convert($note,
$GLOBALS['egw']->translation->charset(), 'UTF-8');
@ -580,7 +697,6 @@ class infolog_ical extends infolog_bo
case 'text/plain':
$txt = $note['info_subject']."\n\n".$note['info_des'];
return $txt;
break;
case 'text/x-vnote':
if (!empty($note['info_cat']))
@ -631,7 +747,10 @@ class infolog_ical extends infolog_bo
{
$vnote->setAttribute('DCREATED',$note['info_startdate']);
}
$vnote->setAttribute('DCREATED',$GLOBALS['egw']->contenthistory->getTSforAction('infolog_note',$_noteID,'add'));
else
{
$vnote->setAttribute('DCREATED',$GLOBALS['egw']->contenthistory->getTSforAction('infolog_note',$_noteID,'add'));
}
$vnote->setAttribute('LAST-MODIFIED',$GLOBALS['egw']->contenthistory->getTSforAction('infolog_note',$_noteID,'modify'));
#$vnote->setAttribute('CLASS',$taskData['info_access'] == 'public' ? 'PUBLIC' : 'PRIVATE');
@ -676,7 +795,7 @@ class infolog_ical extends infolog_bo
array2string($note)."\n",3,$this->logfile);
}
return $this->write($note);
return $this->write($note, true, true, false);
}
/**
@ -684,40 +803,19 @@ class infolog_ical extends infolog_bo
*
* @param string $_vcalData VNOTE
* @param int $contentID=null infolog_id (or null, if unkown)
* @param boolean $relax=false if true, a weaker match algorithm is used
*
* @return infolog_id of a matching entry or false, if nothing was found
*/
function searchVNOTE($_vcalData, $_type, $contentID=null)
function searchVNOTE($_vcalData, $_type, $contentID=null, $relax=false)
{
if (!($note = $this->vnotetoegw($_vcalData,$_type,$contentID))) return false;
if (!($note = $this->vnotetoegw($_vcalData,$_type,$contentID))) return array();
if ($contentID) $note['info_id'] = $contentID;
unset($note['info_startdate']);
$filter = array();
if (!empty($note['info_des']))
{
$description = trim(preg_replace("/\r?\n?\\[[A-Z_]+:.*\\]/i", '', $note['info_des']));
unset($note['info_des']);
if (strlen($description))
{
$filter['search'] = $description;
}
}
$filter['col_filter'] = $note;
if (($foundItems = $this->search($filter)))
{
if (count($foundItems) > 0)
{
$itemIDs = array_keys($foundItems);
return $itemIDs[0];
}
}
return false;
return $this->findInfo($note, $relax, $this->tzid);
}
/**
@ -730,6 +828,13 @@ class infolog_ical extends infolog_bo
*/
function vnotetoegw($_data, $_type, $_noteID=-1)
{
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."($_type, $_noteID)\n" .
array2string($_data)."\n",3,$this->logfile);
}
$note = false;
switch ($_type)
{
case 'text/plain':
@ -739,19 +844,15 @@ class infolog_ical extends infolog_bo
$txt = $botranslation->convert($_data, 'utf-8');
$txt = str_replace("\r\n", "\n", $txt);
if (preg_match("/^(^\n)\n\n(.*)$/", $txt, $match))
if (preg_match('/([^\n]+)\n\n(.*)/m', $txt, $match))
{
$note['info_subject'] = $match[0];
$note['info_des'] = $match[1];
$note['info_subject'] = $match[1];
$note['info_des'] = $match[2];
}
else
{
// should better be imported as subject, but causes duplicates
// TODO: should be examined
$note['info_des'] = $txt;
$note['info_subject'] = $txt;
}
return $note;
break;
case 'text/x-vnote':
@ -772,34 +873,31 @@ class infolog_ical extends infolog_bo
switch ($attribute['name'])
{
case 'BODY':
$note['info_des'] = $attribute['value'];
$note['info_des'] = str_replace("\r\n", "\n", $attribute['value']);
break;
case 'SUMMARY':
$note['info_subject'] = $attribute['value'];
$note['info_subject'] = str_replace("\r\n", "\n", $attribute['value']);
break;
case 'CATEGORIES':
if ($attribute['value'])
{
if($version == '1.0')
{
$cats = $this->find_or_add_categories(explode(';',$attribute['value']), $_noteID);
}
else
{
$cats = $this->find_or_add_categories(explode(',',$attribute['value']), $_noteID);
}
$cats = $this->find_or_add_categories(explode(',',$attribute['value']), $_noteID);
$note['info_cat'] = $cats[0];
}
break;
}
}
}
return $note;
}
}
return false;
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."($_type, $_noteID)\n" .
($note ? array2string($note) : 'FALSE') ."\n",3,$this->logfile);
}
return $note;
}
/**
@ -842,9 +940,36 @@ class infolog_ical extends infolog_bo
{
$this->uidExtension = true;
}
if (isset($deviceInfo['tzid']) &&
$deviceInfo['tzid'])
{
switch ($deviceInfo['tzid'])
{
case 1:
$this->tzid = false;
break;
case 2:
$this->tzid = null;
break;
default:
$this->tzid = $deviceInfo['tzid'];
}
}
}
Horde::logMessage('setSupportedFields(' . $this->productManufacturer . ', ' . $this->productName .')', __FILE__, __LINE__, PEAR_LOG_DEBUG);
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
'(' . $this->productManufacturer .
', '. $this->productName .', ' .
($this->tzid ? $this->tzid : egw_time::$user_timezone->getName()) .
")\n" , 3, $this->logfile);
}
Horde::logMessage('setSupportedFields(' . $this->productManufacturer . ', '
. $this->productName .', ' .
($this->tzid ? $this->tzid : egw_time::$user_timezone->getName()) .')',
__FILE__, __LINE__, PEAR_LOG_DEBUG);
}
}

View File

@ -90,6 +90,67 @@ class infolog_sif extends infolog_bo
*/
var $uidExtension = false;
/**
* user preference: Use this timezone for import from and export to device
*
* @var string
*/
var $tzid = null;
/**
* Set Logging
*
* @var boolean
*/
var $log = false;
var $logfile="/tmp/log-infolog-sif";
/**
* Constructor
*
*/
function __construct()
{
parent::__construct();
if ($this->log) $this->logfile = $GLOBALS['egw_info']['server']['temp_dir']."/log-infolog-sif";
$this->vCalendar = new Horde_iCalendar;
}
/**
* Get DateTime value for a given time and timezone
*
* @param int|string|DateTime $time in server-time as returned by calendar_bo for $data_format='server'
* @param string $tzid TZID of event or 'UTC' or NULL for palmos timestamps in usertime
*
*/
function getDateTime($time, $tzid)
{
if (empty($tzid) || $tzid == 'UTC')
{
return $this->vCalendar->_exportDateTime(egw_time::to($time,'ts'));
}
if (!is_a($time,'DateTime'))
{
$time = new egw_time($time,egw_time::$server_timezone);
}
if (!isset(self::$tz_cache[$tzid]))
{
self::$tz_cache[$tzid] = calendar_timezones::DateTimeZone($tzid);
}
// check for date --> export it as such
if ($time->format('Hi') == '0000')
{
$arr = egw_time::to($time, 'array');
$time = new egw_time($arr, self::$tz_cache[$tzid]);
$value = $time->format('Y-m-d');
}
else
{
$time->setTimezone(self::$tz_cache[$tzid]);
$value = $time->format('Ymd\THis');
}
return $value;
}
function startElement($_parser, $_tag, $_attributes)
{
@ -121,14 +182,15 @@ class infolog_sif extends infolog_bo
*/
function siftoegw($sifData, $_sifType, $_id=-1)
{
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."($_sifType, $_id)\n" .
array2string($sifData) . "\n", 3, $this->logfile);
}
$sysCharSet = $GLOBALS['egw']->translation->charset();
#$tmpfname = tempnam('/tmp/sync/contents','sift_');
#$handle = fopen($tmpfname, "w");
#fwrite($handle, $sifData);
#fclose($handle);
switch ($_sifType)
{
case 'note':
@ -136,9 +198,12 @@ class infolog_sif extends infolog_bo
break;
case 'task':
default:
$this->_currentSIFMapping = $this->_sifTaskMapping;
break;
default:
// we don't know how to handle this
return false;
}
$this->xml_parser = xml_parser_create('UTF-8');
@ -156,16 +221,22 @@ class infolog_sif extends infolog_bo
return false;
}
if (!array($this->_extractedSIFData)) return false;
if (!array($this->_extractedSIFData))
{
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()[PARSER FAILD]\n",
3, $this->logfile);
}
return false;
}
$infoData = array();
switch ($_sifType)
{
case 'task':
$taskData = array();
$vcal = new Horde_iCalendar;
$taskData['info_type'] = 'task';
$taskData['info_status'] = 'not-started';
$infoData['info_type'] = 'task';
$infoData['info_status'] = 'not-started';
if (isset($GLOBALS['egw_info']['user']['preferences']['syncml']['minimum_uid_length']))
{
@ -186,7 +257,7 @@ class infolog_sif extends infolog_bo
switch($key)
{
case 'info_access':
$taskData[$key] = ((int)$value > 0) ? 'private' : 'public';
$infoData[$key] = ((int)$value > 0) ? 'private' : 'public';
break;
case 'info_datecompleted':
@ -194,9 +265,9 @@ class infolog_sif extends infolog_bo
case 'info_startdate':
if (!empty($value))
{
$taskData[$key] = $vcal->_parseDateTime($value);
$infoData[$key] = $this->vCalendar->_parseDateTime($value);
// somehow the client always deliver a timestamp about 3538 seconds, when no startdate set.
if ($taskData[$key] < 10000) unset($taskData[$key]);
if ($infoData[$key] < 10000) unset($infoData[$key]);
}
break;
@ -205,49 +276,48 @@ class infolog_sif extends infolog_bo
if (!empty($value))
{
$categories = $this->find_or_add_categories(explode(';', $value), $_id);
$taskData['info_cat'] = $categories[0];
$infoData['info_cat'] = $categories[0];
}
break;
case 'info_priority':
$taskData[$key] = (int)$value;
$infoData[$key] = (int)$value;
break;
case 'info_status':
switch ($value)
{
case '0':
$taskData[$key] = 'not-started';
$infoData[$key] = 'not-started';
break;
case '1':
$taskData[$key] = 'ongoing';
$infoData[$key] = 'ongoing';
break;
case '2':
$taskData[$key] = 'done';
$taskData['info_percent'] = 100;
$infoData[$key] = 'done';
$infoData['info_percent'] = 100;
break;
case '3':
$taskData[$key] = 'waiting';
$infoData[$key] = 'waiting';
break;
case '4':
if ($this->productName == 'blackberry plug-in')
{
$taskData[$key] = 'deferred';
$infoData[$key] = 'deferred';
}
else
{
$taskData[$key] = 'cancelled';
$infoData[$key] = 'cancelled';
}
break;
default:
$taskData[$key] = 'ongoing';
break;
$infoData[$key] = 'ongoing';
}
break;
case 'complete':
$taskData['info_status'] = 'done';
$taskData['info_percent'] = 100;
$infoData['info_status'] = 'done';
$infoData['info_percent'] = 100;
break;
case 'info_des':
@ -256,7 +326,7 @@ class infolog_sif extends infolog_bo
{
if (strlen($matches[1]) >= $minimum_uid_length)
{
$taskData['info_uid'] = $matches[1];
$infoData['info_uid'] = $matches[1];
}
//$value = str_replace($matches[0], '', $value);
}
@ -264,25 +334,28 @@ class infolog_sif extends infolog_bo
{
if (strlen($matches[1]) >= $minimum_uid_length)
{
$taskData['info_id_parent'] = $this->getParentID($matches[1]);
$infoData['info_id_parent'] = $this->getParentID($matches[1]);
}
//$value = str_replace($matches[0], '', $value);
}
default:
$taskData[$key] = $value;
break;
$infoData[$key] = str_replace("\r\n", "\n", $value);
}
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()\n" .
"key=$key => value=" . $infoData[$key] . "\n", 3, $this->logfile);
}
#error_log("infolog task key=$key => value=" . $taskData[$key]);
}
return $taskData;
if (empty($infoData['info_datecompleted']))
{
$infoData['info_datecompleted'] = 0;
}
break;
case 'note':
$noteData = array();
$noteData['info_type'] = 'note';
$vcal = new Horde_iCalendar;
$infoData['info_type'] = 'note';
foreach ($this->_extractedSIFData as $key => $value)
{
@ -295,13 +368,13 @@ class infolog_sif extends infolog_bo
case 'info_startdate':
if (!empty($value))
{
$noteData[$key] = $vcal->_parseDateTime($value);
$infoData[$key] = $this->vCalendar->_parseDateTime($value);
// somehow the client always deliver a timestamp about 3538 seconds, when no startdate set.
if ($noteData[$key] < 10000) $noteData[$key] = '';
if ($infoData[$key] < 10000) $infoData[$key] = '';
}
else
{
$noteData[$key] = '';
$infoData[$key] = '';
}
break;
@ -309,23 +382,26 @@ class infolog_sif extends infolog_bo
if (!empty($value))
{
$categories = $this->find_or_add_categories(explode(';', $value), $_id);
$noteData['info_cat'] = $categories[0];
$infoData['info_cat'] = $categories[0];
}
break;
default:
$noteData[$key] = $value;
break;
$infoData[$key] = str_replace("\r\n", "\n", $value);
}
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()\n" .
"key=$key => value=" . $infoData[$key] . "\n", 3, $this->logfile);
}
#error_log("infolog note key=$key => value=".$noteData[$key]);
}
return $noteData;
break;
default:
return false;
}
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()\n" .
array2string($infoData) . "\n", 3, $this->logfile);
}
return $infoData;
}
/**
@ -339,28 +415,13 @@ class infolog_sif extends infolog_bo
*/
function searchSIF($_sifData, $_sifType, $contentID=null, $relax=false)
{
if (!($egwData = $this->siftoegw($_sifData, $_sifType, $contentID))) return false;
if (!($egwData = $this->siftoegw($_sifData, $_sifType, $contentID))) return array();
if ($contentID) $egwData['info_id'] = $contentID;
if ($_sifType == 'task') return $this->findVTODO($egwData, $relax);
if ($_sifType == 'note') unset($egwData['info_startdate']);
$filter = array();
$filter['col_filter'] = $egwData;
if ($foundItems = $this->search($filter))
{
if (count($foundItems) > 0)
{
$itemIDs = array_keys($foundItems);
return $itemIDs[0];
}
}
return false;
return $this->findInfo($egwData, $relax, $this->tzid);
}
/**
@ -373,18 +434,20 @@ class infolog_sif extends infolog_bo
*/
function addSIF($_sifData, $_id, $_sifType, $merge=false)
{
if (!($egwData = $this->siftoegw($_sifData, $_sifType, $_id))) return false;
if ($this->tzid)
{
date_default_timezone_set($this->tzid);
}
$egwData = $this->siftoegw($_sifData, $_sifType, $_id);
if ($this->tzid)
{
date_default_timezone_set($GLOBALS['egw_info']['server']['server_timezone']);
}
if (!$egwData) return false;
if ($_id > 0) $egwData['info_id'] = $_id;
if (empty($taskData['info_datecompleted']))
{
$taskData['info_datecompleted'] = 0;
}
$egwID = $this->write($egwData, false);
return $egwID;
return $this->write($egwData, true, true, false);
}
@ -399,189 +462,161 @@ class infolog_sif extends infolog_bo
{
$sysCharSet = $GLOBALS['egw']->translation->charset();
if (!($infoData = $this->read($_id, true, 'server'))) return false;
switch($_sifType)
{
case 'task':
if (($taskData = $this->read($_id)))
if ($infoData['info_id_parent'])
{
$vcal = new Horde_iCalendar('1.0');
if ($taskData['info_id_parent'])
{
$parent = $this->read($taskData['info_id_parent']);
$taskData['info_id_parent'] = $parent['info_uid'];
}
else
{
$taskData['info_id_parent'] = '';
}
if (!preg_match('/\[UID:.+\]/m', $taskData['info_des']))
{
$taskData['info_des'] .= "\r\n[UID:" . $taskData['info_uid'] . "]";
if ($taskData['info_id_parent'] != '')
{
$taskData['info_des'] .= "\r\n[PARENT_UID:" . $taskData['info_id_parent'] . "]";
}
}
$sifTask = self::xml_decl . "\n<task>" . self::SIF_decl;
foreach ($this->_sifTaskMapping as $sifField => $egwField)
{
if (empty($egwField)) continue;
$value = $GLOBALS['egw']->translation->convert($taskData[$egwField], $sysCharSet, 'utf-8');
switch ($sifField)
{
case 'Complete':
// is handled with DateCompleted
break;
case 'DateCompleted':
if ($taskData[info_status] == 'done')
{
$sifTask .= "<Complete>1</Complete>";
}
else
{
$sifTask .= "<DateCompleted></DateCompleted><Complete>0</Complete>";
continue;
}
case 'DueDate':
if (!empty($value))
{
$hdate = new Horde_Date($value);
$value = $vcal->_exportDate($hdate, '000000Z');
$sifTask .= "<$sifField>$value</$sifField>";
}
else
{
$sifTask .= "<$sifField></$sifField>";
}
break;
case 'StartDate':
if (!empty($value))
{
$value = $vcal->_exportDateTime($value);
$sifTask .= "<$sifField>$value</$sifField>";
}
else
{
$sifTask .= "<$sifField></$sifField>";
}
break;
case 'Importance':
if ($value > 3) $value = 3;
$sifTask .= "<$sifField>$value</$sifField>";
break;
case 'Sensitivity':
$value = ($value == 'private' ? '2' : '0');
$sifTask .= "<$sifField>$value</$sifField>";
break;
case 'Status':
switch ($value)
{
case 'cancelled':
case 'deferred':
$value = '4';
break;
case 'waiting':
case 'nonactive':
$value = '3';
break;
case 'done':
case 'archive':
case 'billed':
$value = '2';
break;
case 'not-started':
case 'template':
$value = '0';
break;
default: //ongoing
$value = 1;
break;
}
$sifTask .= "<$sifField>$value</$sifField>";
break;
case 'Categories':
if (!empty($value) && $value)
{
$value = implode('; ', $this->get_categories(array($value)));
$value = $GLOBALS['egw']->translation->convert($value, $sysCharSet, 'utf-8');
}
else
{
break;
}
default:
$value = @htmlspecialchars($value, ENT_QUOTES, 'utf-8');
$sifTask .= "<$sifField>$value</$sifField>";
break;
}
}
$sifTask .= '<ActualWork>0</ActualWork><IsRecurring>0</IsRecurring></task>';
return $sifTask;
$parent = $this->read($infoData['info_id_parent']);
$infoData['info_id_parent'] = $parent['info_uid'];
}
break;
else
{
$infoData['info_id_parent'] = '';
}
if (!preg_match('/\[UID:.+\]/m', $infoData['info_des']))
{
$infoData['info_des'] .= "\r\n[UID:" . $infoData['info_uid'] . "]";
if ($infoData['info_id_parent'] != '')
{
$infoData['info_des'] .= "\r\n[PARENT_UID:" . $infoData['info_id_parent'] . "]";
}
}
$sifTask = self::xml_decl . "\n<task>" . self::SIF_decl;
foreach ($this->_sifTaskMapping as $sifField => $egwField)
{
if (empty($egwField)) continue;
$value = $GLOBALS['egw']->translation->convert($infoData[$egwField], $sysCharSet, 'utf-8');
switch ($sifField)
{
case 'Complete':
// is handled with DateCompleted
break;
case 'DateCompleted':
if ($infoData[info_status] != 'done')
{
$sifTask .= "<DateCompleted></DateCompleted><Complete>0</Complete>";
continue;
}
$sifTask .= "<Complete>1</Complete>";
case 'DueDate':
case 'StartDate':
$sifTask .= "<$sifField>";
if (!empty($value))
{
$sifTask .= $this->getDateTime($value, $this->tzid);
}
$sifTask .= "</$sifField>";
break;
case 'Importance':
if ($value > 3) $value = 3;
$sifTask .= "<$sifField>$value</$sifField>";
break;
case 'Sensitivity':
$value = ($value == 'private' ? '2' : '0');
$sifTask .= "<$sifField>$value</$sifField>";
break;
case 'Status':
switch ($value)
{
case 'cancelled':
case 'deferred':
$value = '4';
break;
case 'waiting':
case 'nonactive':
$value = '3';
break;
case 'done':
case 'archive':
case 'billed':
$value = '2';
break;
case 'not-started':
case 'template':
$value = '0';
break;
default: //ongoing
$value = 1;
break;
}
$sifTask .= "<$sifField>$value</$sifField>";
break;
case 'Categories':
if (!empty($value) && $value)
{
$value = implode('; ', $this->get_categories(array($value)));
$value = $GLOBALS['egw']->translation->convert($value, $sysCharSet, 'utf-8');
}
else
{
break;
}
default:
$value = @htmlspecialchars($value, ENT_QUOTES, 'utf-8');
$sifTask .= "<$sifField>$value</$sifField>";
break;
}
}
$sifTask .= '<ActualWork>0</ActualWork><IsRecurring>0</IsRecurring></task>';
return $sifTask;
case 'note':
if (($taskData = $this->read($_id)))
$sifNote = self::xml_decl . "\n<note>" . self::SIF_decl;
foreach ($this->_sifNoteMapping as $sifField => $egwField)
{
$vcal = new Horde_iCalendar('1.0');
if(empty($egwField)) continue;
$sifNote = self::xml_decl . "\n<note>" . self::SIF_decl;
$value = $GLOBALS['egw']->translation->convert($infoData[$egwField], $sysCharSet, 'utf-8');
foreach ($this->_sifNoteMapping as $sifField => $egwField)
switch ($sifField)
{
if(empty($egwField)) continue;
case 'Date':
$sifNote .= '<$sifField>';
if (!empty($value))
{
$sifNote .= $this->getDateTime($value, $this->tzid);
}
$sifNote .= '</$sifField>';
break;
$value = $GLOBALS['egw']->translation->convert($taskData[$egwField], $sysCharSet, 'utf-8');
switch ($sifField)
{
case 'Date':
if (!empty($value))
{
$value = $vcal->_exportDateTime($value);
}
$sifNote .= "<$sifField>$value</$sifField>";
case 'Categories':
if (!empty($value))
{
$value = implode('; ', $this->get_categories(array($value)));
$value = $GLOBALS['egw']->translation->convert($value, $sysCharSet, 'utf-8');
}
else
{
break;
}
case 'Categories':
if (!empty($value))
{
$value = implode('; ', $this->get_categories(array($value)));
$value = $GLOBALS['egw']->translation->convert($value, $sysCharSet, 'utf-8');
}
else
{
break;
}
default:
$value = @htmlspecialchars($value, ENT_QUOTES, 'utf-8');
$sifNote .= "<$sifField>$value</$sifField>";
break;
}
default:
$value = @htmlspecialchars($value, ENT_QUOTES, 'utf-8');
$sifNote .= "<$sifField>$value</$sifField>";
break;
}
$sifNote .= '</note>';
return $sifNote;
}
break;
default;
return false;
$sifNote .= '</note>';
return $sifNote;
}
return false;
}
/**
@ -604,6 +639,21 @@ class infolog_sif extends infolog_bo
{
$this->uidExtension = true;
}
if (isset($deviceInfo['tzid']) &&
$deviceInfo['tzid'])
{
switch ($deviceInfo['tzid'])
{
case 1:
$this->tzid = false;
break;
case 2:
$this->tzid = null;
break;
default:
$this->tzid = $deviceInfo['tzid'];
}
}
}
// store product name and version, to be able to use it elsewhere
if ($_productName)

View File

@ -139,19 +139,27 @@ class infolog_so
/**
* Filter for a given responsible user: info_responsible either contains a the user or one of his memberships
*
* @param int $user
* @param int|array $users one or more account_ids
* @return string
*
* @todo make the responsible a second table and that filter a join with the responsible table
*/
function responsible_filter($user)
function responsible_filter($users)
{
if (!$user) return '0';
if (!$users) return '0';
$responsible = $user > 0 ? $GLOBALS['egw']->accounts->memberships($user,true) :
$GLOBALS['egw']->accounts->members($user,true);
$responsible[] = $user;
$responsible = array();
foreach((array)$users as $user)
{
$responsible = array_merge($responsible,
$user > 0 ? $GLOBALS['egw']->accounts->memberships($user,true) :
$GLOBALS['egw']->accounts->members($user,true));
$responsible[] = $user;
}
if (is_array($users))
{
$responsible = array_unique($responsible);
}
foreach($responsible as $key => $uid)
{
$responsible[$key] = $this->db->concat("','",'info_responsible',"','")." LIKE '%,$uid,%'";
@ -171,14 +179,18 @@ class infolog_so
*/
function aclFilter($filter = False)
{
preg_match('/(my|responsible|delegated|own|privat|private|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]);
$f_user = $vars[2];
if (isset($this->acl_filter[$filter.$f_user]))
{
return $this->acl_filter[$filter.$f_user]; // used cached filter if found
}
if ($f_user && strpos($f_user,',') !== false)
{
$f_user = explode(',',$f_user);
}
$filtermethod = " (info_owner=$this->user"; // user has all rights
@ -236,9 +248,11 @@ class infolog_so
}
$filtermethod .= ') ';
if ($filter == 'user' && $f_user > 0)
if ($filter == 'user' && $f_user)
{
$filtermethod .= " AND (info_owner=$f_user AND info_responsible='0' OR ".$this->responsible_filter($f_user).')';
$filtermethod .= $this->db->expression($this->info_table,' AND (',array(
'info_owner' => $f_user,
)," AND info_responsible='0' OR ",$this->responsible_filter($f_user),')');
}
}
//echo "<p>aclFilter(filter='$filter_was',user='$user') = '$filtermethod', privat_user_list=".print_r($privat_user_list,True).", public_user_list=".print_r($public_user_list,True)."</p>\n";

View File

@ -163,17 +163,17 @@ class infolog_tracking extends bo_tracking
if (!$old || $old['info_status'] == 'deleted')
{
return lang('New %1 created by %2 at %3',lang($this->infolog->enums['type'][$data['info_type']]),
$GLOBALS['egw']->common->grab_owner_name($this->infolog->user),$this->datetime(time()));
common::grab_owner_name($this->infolog->user),$this->datetime('now'));
}
elseif($data['info_status'] == 'deleted')
{
return lang('%1 deleted by %2 at %3',lang($this->infolog->enums['type'][$data['info_type']]),
$GLOBALS['egw']->common->grab_owner_name($data['info_modifier']),
$this->datetime($data['info_datemodified']-$this->infolog->tz_offset_s));
common::grab_owner_name($data['info_modifier']),
$this->datetime($data['info_datemodified']));
}
return lang('%1 modified by %2 at %3',lang($this->infolog->enums['type'][$data['info_type']]),
$GLOBALS['egw']->common->grab_owner_name($data['info_modifier']),
$this->datetime($data['info_datemodified']-$this->infolog->tz_offset_s));
common::grab_owner_name($data['info_modifier']),
$this->datetime($data['info_datemodified']));
}
/**
@ -192,7 +192,7 @@ class infolog_tracking extends bo_tracking
{
foreach($data['info_responsible'] as $uid)
{
$responsible[] = $GLOBALS['egw']->common->grab_owner_name($uid);
$responsible[] = common::grab_owner_name($uid);
}
}
if ($GLOBALS['egw_info']['user']['preferences']['infolog']['show_id'])
@ -205,13 +205,13 @@ class infolog_tracking extends bo_tracking
'info_addr' => $data['info_addr'],
'info_cat' => $data['info_cat'] ? $GLOBALS['egw']->categories->id2name($data['info_cat']) : '',
'info_priority' => lang($this->infolog->enums['priority'][$data['info_priority']]),
'info_owner' => $GLOBALS['egw']->common->grab_owner_name($data['info_owner']),
'info_owner' => common::grab_owner_name($data['info_owner']),
'info_status' => lang($data['info_status']=='deleted'?'deleted':$this->infolog->status[$data['info_type']][$data['info_status']]),
'info_percent' => (int)$data['info_percent'].'%',
'info_datecompleted' => $data['info_datecompleted'] ? $this->datetime($data['info_datecompleted']-$this->infolog->tz_offset_s) : '',
'info_datecompleted' => $data['info_datecompleted'] ? $this->datetime($data['info_datecompleted']) : '',
'info_location' => $data['info_location'],
'info_startdate' => $data['info_startdate'] ? $this->datetime($data['info_startdate']-$this->infolog->tz_offset_s,null) : '',
'info_enddate' => $data['info_enddate'] ? $this->datetime($data['info_enddate']-$this->infolog->tz_offset_s,false) : '',
'info_startdate' => $data['info_startdate'] ? $this->datetime($data['info_startdate'],null) : '',
'info_enddate' => $data['info_enddate'] ? $this->datetime($data['info_enddate'],false) : '',
'info_responsible' => implode(', ',$responsible),
'info_subject' => $data['info_subject'],
) as $name => $value)