diff --git a/infolog/inc/class.infolog_bo.inc.php b/infolog/inc/class.infolog_bo.inc.php index f497e46caa..de4f9e931c 100644 --- a/infolog/inc/class.infolog_bo.inc.php +++ b/infolog/inc/class.infolog_bo.inc.php @@ -30,16 +30,27 @@ class infolog_bo var $vfs_basedir='/infolog'; var $link_pathes = array(); var $send_file_ips = array(); - - var $tz_offset = 0; + /** + * Set Logging + * + * @var boolean + */ + var $log = false; /** * 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 * * @var int */ + var $tz_offset = 0; var $tz_offset_s = 0; + /** + * current time as timestamp in user-time and server-time + * + * @var int + */ var $user_time_now; + var $now; /** * name of timestamps in an InfoLog entry * @@ -235,7 +246,8 @@ class infolog_bo $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 = $this->now + $this->tz_offset_s; $this->grants = $GLOBALS['egw']->acl->get_grants('infolog',$this->group_owners ? $this->group_owners : true); $this->so = new infolog_so($this->grants); @@ -411,9 +423,12 @@ class infolog_bo * @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 + * @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 +452,32 @@ class infolog_bo } if ($run_link_id2from) $this->link_id2from($data); - // convert system- to user-time - foreach($this->timestamps as $time) + if ($data['info_enddate']) { - if ($data[$time]) $data[$time] += $this->tz_offset_s; + // Set due date to 00:00 + $data['info_enddate'] = mktime(0, 0, 0, + date('m', $data['info_enddate']), + date('d', $data['info_enddate']), + date('Y', $data['info_enddate'])); + } + + // convert server- to user-time + if ($date_format == 'ts' && $this->tz_offset_s) + { + foreach ($this->timestamps as $time) + { + // we keep dates the same in user-time + if ($data[$time] && date('Hi', $data[$time]) != '0000') + { + $data[$time] += $this->tz_offset_s; + } + } + } + if ($date_format == 'ts' || !$this->tz_offset_s) + { + // pre-cache title and file access + self::set_link_cache($data); } - // pre-cache title and file access - self::set_link_cache($data); return $data; } @@ -488,7 +522,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 +566,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 +608,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 +641,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,43 +687,78 @@ 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); + + + if (isset($values['info_enddate']) && $values['info_enddate']) + { + // Set due date to 00:00 + $values['info_enddate'] = mktime(0, 0, 0, + date('m', $values['info_enddate']), + date('d', $values['info_enddate']), + date('Y', $values['info_enddate'])); + } + + $to_write = $values; + if ($user2server) + { + // convert user- to server-time + foreach ($this->timestamps as $time) + { + if ($to_write[$time] && date('Hi', $to_write[$time]) != '0000') + { + $to_write[$time] -= $this->tz_offset_s; + } + } + } + else + { + // convert server- to user-time + foreach ($this->timestamps as $time) + { + if ($values[$time] && date('Hi', $values[$time]) != '0000') + { + $values[$time] += $this->tz_offset_s; + } + } + } + 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); - $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; - } + // error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()\n".array2string($values)."\n",3,'/tmp/infolog'); + // 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( @@ -704,11 +775,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'); @@ -725,11 +798,12 @@ class infolog_bo $this->tracking = new infolog_tracking($this); } - if (($missing_fields = array_diff_key($old,$values))) + 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'] = ''; @@ -763,18 +837,43 @@ class infolog_bo function &search(&$query) { //echo "

boinfolog::search(".print_r($query,True).")

\n"; + if (!empty($query['start'])) + { + $query['start'] -= $this->tz_offset_s; + } + $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; + } + + if ($data['info_enddate']) + { + // Set due date to 00:00 + $data['info_enddate'] = mktime(0, 0, 0, + date('m', $data['info_enddate']), + date('d', $data['info_enddate']), + date('Y', $data['info_enddate'])); + } + + if ($this->tz_offset_s) + { + // convert system- to user-time + foreach ($this->timestamps as $time) { - if ($data[$time]) $data[$time] += $this->tz_offset_s; + // we keep dates the same in user-time + if ($data[$time] && date('Hi', $data[$time]) != '0000') + { + $data[$time] += $this->tz_offset_s; + } } } // pre-cache title and file access @@ -882,7 +981,7 @@ class infolog_bo * @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)) { @@ -901,16 +1000,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 } @@ -925,7 +1024,7 @@ class infolog_bo * @param string $pattern pattern to search * @return array with info_id - title pairs of the matching entries */ - function link_query( $pattern ) + function link_query($pattern) { $query = array( 'search' => $pattern, @@ -936,7 +1035,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); } @@ -1006,7 +1105,7 @@ 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']); @@ -1021,7 +1120,7 @@ class infolog_bo $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) @@ -1088,18 +1187,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) { - if(!$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; } @@ -1108,7 +1206,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-'); @@ -1129,7 +1227,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); } @@ -1230,19 +1328,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]"); @@ -1359,109 +1457,319 @@ 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 boolean $user2server=true conversion between user- and server-time necessary + * + * @return array of infolog_ids of matching entries */ - function findVTODO($egwData, $relax=false) + function findInfo($infoData, $relax=false, $user2server=true) { - 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, ') + . ($user2server ? 'USERTIME': 'SERVERTIME'). ')[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']); + // Set due date to 00:00 + $infoData['info_enddate'] = mktime(0, 0, 0, + date('m', $infoData['info_enddate']), + date('d', $infoData['info_enddate']), + date('Y', $infoData['info_enddate'])); + + $filter['col_filter'] = $infoData; + + if ($user2server) + { + // convert user- to server-time + foreach($this->timestamps as $time) + { + if (isset($infoData[$time]) && + $infoData[$time] && + date('Hi', $infoData[$time]) != '0000') + { + $filter['col_filter'][$time] -= $this->tz_offset_s; + } + } + } + + $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 + if (date('Ymd', $infoData['info_startdate']) != date('Ymd', $egwData['info_startdate'])) + { + 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 + if (date('Ymd', $infoData['info_enddate']) != date('Ymd', $egwData['info_enddate'])) + { + 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 + if (date('Ymd', $infoData['info_datecompleted']) != date('Ymd', $egwData['info_datecompleted'])) + { + 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; } } diff --git a/infolog/inc/class.infolog_groupdav.inc.php b/infolog/inc/class.infolog_groupdav.inc.php index 80bc82a109..528e7bbba2 100644 --- a/infolog/inc/class.infolog_groupdav.inc.php +++ b/infolog/inc/class.infolog_groupdav.inc.php @@ -7,7 +7,7 @@ * @package infolog * @subpackage groupdav * @author Ralf Becker - * @copyright (c) 2007/8 by Ralf Becker + * @copyright (c) 2007-9 by Ralf Becker * @version $Id$ */ @@ -70,6 +70,8 @@ class infolog_groupdav extends groupdav_handler */ function propfind($path,$options,&$files,$user,$id='') { + $starttime = microtime(true); + // todo add a filter to limit how far back entries from the past get synced $filter = array( 'info_type' => 'task', @@ -77,7 +79,7 @@ class infolog_groupdav extends groupdav_handler 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( + if (($tasks =& $this->bo->search($params=array( 'order' => 'info_datemodified', 'sort' => 'DESC', 'filter' => 'own', // filter my: entries user is responsible for, @@ -85,7 +87,7 @@ class infolog_groupdav extends groupdav_handler 'col_filter' => $filter, )))) { - foreach($tasks as $task) + foreach($tasks as &$task) { $files['files'][] = array( 'path' => self::get_path($task), @@ -100,6 +102,7 @@ class infolog_groupdav extends groupdav_handler ); } } + if ($this->debug) error_log(__METHOD__."($path) took ".(microtime(true) - $starttime).' to return '.count($files['files']).' items'); return true; } @@ -178,7 +181,7 @@ class infolog_groupdav extends groupdav_handler */ function read($id) { - return $this->bo->read($id,false); + return $this->bo->read($id,false,'server'); } /** @@ -203,7 +206,7 @@ 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'])) { diff --git a/infolog/inc/class.infolog_hooks.inc.php b/infolog/inc/class.infolog_hooks.inc.php index 3d3f1f0eaa..4485bf08dc 100644 --- a/infolog/inc/class.infolog_hooks.inc.php +++ b/infolog/inc/class.infolog_hooks.inc.php @@ -241,7 +241,7 @@ class infolog_hooks ), 'cat_add_default' => array( 'type' => 'select', - 'label' => 'Default categorie for new Infolog entries', + 'label' => 'Default category for new Infolog entries', 'name' => 'cat_add_default', 'values' => self::all_cats(), 'help' => 'You can choose a categorie to be preselected, when you create a new Infolog entry', diff --git a/infolog/inc/class.infolog_ical.inc.php b/infolog/inc/class.infolog_ical.inc.php index 13151e01d2..be2486e5e7 100644 --- a/infolog/inc/class.infolog_ical.inc.php +++ b/infolog/inc/class.infolog_ical.inc.php @@ -20,23 +20,42 @@ require_once EGW_SERVER_ROOT.'/phpgwapi/inc/horde/lib/core.php'; class infolog_ical extends infolog_bo { /** - * @var array conversion of the priority egw => ical + * @var array $priority_egw2ical conversion of the priority egw => ical */ - var $egw_priority2vcal_priority = array( - 0 => 9, // low - 1 => 5, // normal - 2 => 3, // high - 3 => 1, // urgent + var $priority_egw2ical = array( + 0 => 9, // low + 1 => 5, // normal + 2 => 3, // high + 3 => 1, // urgent ); /** - * @var array conversion of the priority ical => egw + * @var array $priority_ical2egw conversion of the priority ical => egw */ - var $vcal_priority2egw_priority = array( - 9 => 0, 8 => 0, 7 => 0, // low - 6 => 1, 5 => 1, 4 => 1, 0 => 1, // normal - 3 => 2, 2 => 2, // high - 1 => 3, // urgent + var $priority_ical2egw = array( + 9 => 0, 8 => 0, 7 => 0, // low + 6 => 1, 5 => 1, 4 => 1, 0 => 1, // normal + 3 => 2, 2 => 2, // high + 1 => 3, // urgent + ); + + /** + * @var array $priority_egw2funambol conversion of the priority egw => funambol + */ + var $priority_egw2funambol = array( + 0 => 0, // low + 1 => 1, // normal + 2 => 2, // high + 3 => 2, // urgent + ); + + /** + * @var array $priority_funambol2egw conversion of the priority funambol => egw + */ + var $priority_funambol2egw = array( + 0 => 0, // low + 1 => 1, // normal + 2 => 3, // high ); /** @@ -54,6 +73,13 @@ class infolog_ical extends infolog_bo */ var $uidExtension = false; + /** + * user preference: use server timezone for exports to device + * + * @var boolean + */ + var $useServerTZ = false; + /** * Client CTCap Properties * @@ -61,6 +87,15 @@ class infolog_ical extends infolog_bo */ var $clientProperties; + /** + * Set Logging + * + * @var boolean + */ + var $log = false; + var $logfile="/tmp/log-infolog-vcal"; + + /** * Constructor * @@ -69,7 +104,7 @@ class infolog_ical extends infolog_bo function __construct(&$_clientProperties = array()) { parent::__construct(); - + if ($this->log) $this->logfile = $GLOBALS['egw_info']['server']['temp_dir']."/log-infolog-vcal"; $this->clientProperties = $_clientProperties; } @@ -83,7 +118,15 @@ class infolog_ical extends infolog_bo */ function exportVTODO($_taskID, $_version='2.0',$_method='PUBLISH') { - $taskData = $this->read($_taskID); + if ($this->useServerTZ) + { + $date_format = 'server'; + } + else + { + $date_format = 'ts'; + } + if (!($taskData = $this->read($_taskID, true, $date_format))) return false; if ($taskData['info_id_parent']) { @@ -116,10 +159,27 @@ 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); + if ($this->useServerTZ) + { + $event = array('start' => $taskData['info_startdate'] ? $taskData['info_startdate'] : time()); + $serverTZ = calendar_ical::generate_vtimezone($event, $vcal); + } + else + { + $serverTZ = false; + } + $vevent = Horde_iCalendar::newComponent('VTODO',$vcal); if (!isset($this->clientProperties['SUMMARY']['Size'])) @@ -142,8 +202,14 @@ class infolog_ical extends infolog_bo { $size = $this->clientProperties[$field]['Size']; $noTruncate = $this->clientProperties[$field]['NoTruncate']; - #Horde::logMessage("VTODO $field Size: $size, NoTruncate: " . - # ($noTruncate ? 'TRUE' : 'FALSE'), __FILE__, __LINE__, PEAR_LOG_DEBUG); + if ($this->log && $size > 0) + { + error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ . + "() $field Size: $size, NoTruncate: " . + ($noTruncate ? 'TRUE' : 'FALSE') . "\n",3,$this->logfile); + } + //Horde::logMessage("VTODO $field Size: $size, NoTruncate: " . + // ($noTruncate ? 'TRUE' : 'FALSE'), __FILE__, __LINE__, PEAR_LOG_DEBUG); } else { @@ -155,55 +221,79 @@ class infolog_ical extends infolog_bo { if ($noTruncate) { - Horde::logMessage("VTODO $field omitted due to maximum size $size", - __FILE__, __LINE__, PEAR_LOG_WARNING); + if ($this->log) + { + error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ . + "() $field omitted due to maximum size $size\n",3,$this->logfile); + } + //Horde::logMessage("VTODO $field omitted due to maximum size $size", + // __FILE__, __LINE__, PEAR_LOG_WARNING); continue; // skip field } // truncate the value to size $value = substr($value, 0, $size -1); - #Horde::logMessage("VTODO $field truncated to maximum size $size", - # __FILE__, __LINE__, PEAR_LOG_INFO); + if ($this->log) + { + error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ . + "() $field truncated to maximum size $size\n",3,$this->logfile); + } + //Horde::logMessage("VTODO $field truncated to maximum size $size", + // __FILE__, __LINE__, PEAR_LOG_INFO); } if (empty($value) && ($size < 0 || $noTruncate)) continue; if ($field == 'RELATED-TO') { - $options = array('RELTYPE' => 'PARENT'); + $options = array('RELTYPE' => 'PARENT'); } else { $options = array(); } - /*if(preg_match('/([\000-\012\015\016\020-\037\075])/', $value)) { - $options['ENCODING'] = 'QUOTED-PRINTABLE'; - }*/ - if ($this->productManufacturer != 'groupdav' - && preg_match('/([\177-\377])/',$value)) + if (preg_match('/[^\x20-\x7F]/', $value)) { - $options['CHARSET'] = 'UTF-8'; + $options['CHARSET'] = 'UTF-8'; + switch ($this->productManufacturer) + { + case 'groupdav': + if ($this->productName == 'kde') + { + $options['ENCODING'] = 'QUOTED-PRINTABLE'; + } + else + { + $options['CHARSET'] = ''; + + if (preg_match('/([\000-\012\015\016\020-\037\075])/', $value)) + { + $options['ENCODING'] = 'QUOTED-PRINTABLE'; + } + else + { + $options['ENCODING'] = ''; + } + } + break; + case 'funambol': + $options['ENCODING'] = 'FUNAMBOL-QP'; + } } $vevent->setAttribute($field, $value, $options); } - $dateOnly = false; - if ($taskData['info_startdate']) { - $dateOnly = self::setDateOrTime($vevent, 'DTSTART', $taskData['info_startdate']); + self::setDateOrTime($vevent, 'DTSTART', $taskData['info_startdate'], $serverTZ); } - if ($taskData['info_enddate']) { - $parts = @getdate($taskData['info_enddate']); - $value = @mktime(12, 0, 0, $parts['mon'], $parts['mday'], $parts['year']); - self::setDateOrTime($vevent, 'DUE', $value, $dateOnly); + self::setDateOrTime($vevent, 'DUE', $taskData['info_enddate'], $serverTZ); } - if ($taskData['info_datecompleted']) { - self::setDateOrTime($vevent, 'COMPLETED', $taskData['info_datecompleted']); + self::setDateOrTime($vevent, 'COMPLETED', $taskData['info_datecompleted'], $serverTZ); } $vevent->setAttribute('DTSTAMP',time()); @@ -214,16 +304,69 @@ class infolog_ical extends infolog_bo // 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('PERCENT-COMPLETE',$taskData['info_percent']); - $vevent->setAttribute('PRIORITY',$this->egw_priority2vcal_priority[$taskData['info_priority']]); - + if ($this->productManufacturer == 'funambol' && + (strpos($this->productName, 'outlook') !== false + || strpos($this->productName, 'pocket pc') !== false)) + { + $priority = (int) $this->priority_egw2funambol[$taskData['info_priority']]; + } + else + { + $priority = (int) $this->priority_egw2ical[$taskData['info_priority']]; + } + $vevent->setAttribute('PRIORITY', $priority); $vcal->addComponent($vevent); $retval = $vcal->exportvCalendar(); - Horde::logMessage("exportVTODO:\n" . print_r($retval, true), __FILE__, __LINE__, PEAR_LOG_DEBUG); + if ($this->log) + { + error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()\n" . + array2string($retval)."\n",3,$this->logfile); + } + // Horde::logMessage("exportVTODO:\n" . print_r($retval, true), + // __FILE__, __LINE__, PEAR_LOG_DEBUG); return $retval; } + /** + * 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 $time as timestamp + * @param string $tzid=false timezone to use for client, false for user-time + */ + static function setDateOrTime(&$vevent, $attr, $time, $tzid=false) + { + $params = array(); + + // check for date --> export it as such + if (date('Hi', $time) == '0000') + { + $value = array( + 'year' => date('Y', $time), + 'month' => date('m', $time), + 'mday' => date('d', $time)); + $params['VALUE'] = 'DATE'; + } + else + { + if (!$tzid || $tzid == 'UTC') + { + $value = $time; + } + else + { + $value = date('Ymd\THis', $time); + $params['TZID'] = $tzid; + } + + } + $vevent->setAttribute($attr, $value, $params); + } + /** * Import a VTODO component of an iCal * @@ -234,7 +377,7 @@ class infolog_ical extends infolog_bo */ function importVTODO(&$_vcalData, $_taskID=-1, $merge=false) { - if (!$taskData = $this->vtodotoegw($_vcalData,$_taskID)) return false; + 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'])) @@ -247,7 +390,13 @@ class infolog_ical extends infolog_bo $taskData['info_datecompleted'] = 0; } - return $this->write($taskData); + if ($this->log) + { + error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()\n" . + array2string($taskData)."\n",3,$this->logfile); + } + + return $this->write($taskData, true, true, false); } /** @@ -256,18 +405,22 @@ 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))) + $taskData = $this->vtodotoegw($_vcalData,$contentID); + + if ($taskData) { if ($contentID) { - $egwData['info_id'] = $contentID; + $taskData['info_id'] = $contentID; } - $result = $this->findVTODO($egwData, $relax); + $result = $this->findInfo($taskData, $relax, !$this->useServerTZ); } return $result; } @@ -281,8 +434,24 @@ class infolog_ical extends infolog_bo */ function vtodotoegw($_vcalData, $_taskID=-1) { + if ($this->log) + { + error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."($_taskID)\n" . + array2string($_vcalData)."\n",3,$this->logfile); + } + $vcal = new Horde_iCalendar; - if (!($vcal->parsevCalendar($_vcalData))) return false; + if (!($vcal->parsevCalendar($_vcalData))) + { + if ($this->log) + { + error_log(__FILE__.'['.__LINE__.'] '.__METHOD__. + "(): No vCalendar Container found!\n",3,$this->logfile); + } + return false; + } + + $version = $vcal->getAttribute('VERSION'); if (isset($GLOBALS['egw_info']['user']['preferences']['syncml']['minimum_uid_length'])) { @@ -293,126 +462,146 @@ class infolog_ical extends infolog_bo $minimum_uid_length = 8; } - $components = $vcal->getComponents(); + $taskData = false; - foreach ($components as $component) + foreach ($vcal->getComponents() as $component) { - if (is_a($component, 'Horde_iCalendar_vtodo')) + if (!is_a($component, 'Horde_iCalendar_vtodo')) { - $taskData = array(); - $taskData['info_type'] = 'task'; - - if ($_taskID > 0) + if ($this->log) { - $taskData['info_id'] = $_taskID; + error_log(__FILE__.'['.__LINE__.'] '.__METHOD__. + "(): Not a vTODO container, skipping...\n",3,$this->logfile); } - foreach ($component->_attributes as $attributes) + continue; + } + + $taskData = array(); + $taskData['info_type'] = 'task'; + + if ($_taskID > 0) + { + $taskData['info_id'] = $_taskID; + } + foreach ($component->_attributes as $attribute) + { + //$attribute['value'] = trim($attribute['value']); + if (!strlen($attribute['value'])) continue; + + switch ($attribute['name']) { - //$attributes['value'] = trim($attributes['value']); - if (empty($attributes['value'])) continue; - switch ($attributes['name']) - { - case 'CLASS': - $taskData['info_access'] = strtolower($attributes['value']); - break; + case 'CLASS': + $taskData['info_access'] = strtolower($attribute['value']); + break; - case 'DESCRIPTION': - $value = $attributes['value']; - if (preg_match('/\s*\[UID:(.+)?\]/Usm', $value, $matches)) + 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) { - if (!isset($taskData['info_uid']) - && strlen($matches[1]) >= $minimum_uid_length) - { - $taskData['info_uid'] = $matches[1]; - } - //$value = str_replace($matches[0], '', $value); + $taskData['info_uid'] = $matches[1]; } - if (preg_match('/\s*\[PARENT_UID:(.+)?\]/Usm', $value, $matches)) + //$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) { - 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_id_parent'] = $this->getParentID($matches[1]); } - $taskData['info_des'] = $value; - break; + //$value = str_replace($matches[0], '', $value); + } + $taskData['info_des'] = $value; + break; - case 'LOCATION': - $taskData['info_location'] = $attributes['value']; - break; + case 'LOCATION': + $taskData['info_location'] = str_replace("\r\n", "\n", $attribute['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 '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'] = $attributes['value']; - break; + case 'COMPLETED': + $taskData['info_datecompleted'] = $attribute['value']; + break; - case 'DTSTART': - $taskData['info_startdate'] = $attributes['value']; - break; + case 'DTSTART': + $taskData['info_startdate'] = $attribute['value']; + break; - case 'PRIORITY': - if (1 <= $attributes['value'] && $attributes['value'] <= 9) + 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'] = $this->vcal_priority2egw_priority[$attributes['value']]; + $taskData['info_priority'] = (int) $this->priority_funambol2egw[$attribute['value']]; } else { - $taskData['info_priority'] = 1; // default = normal + $taskData['info_priority'] = (int) $this->priority_ical2egw[$attribute['value']]; } - break; + } + 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 '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'] = $attributes['value']; - break; + case 'SUMMARY': + $taskData['info_subject'] = str_replace("\r\n", "\n", $attribute['value']); + break; - case 'RELATED-TO': - $taskData['info_id_parent'] = $this->getParentID($attributes['value']); - break; + case 'RELATED-TO': + $taskData['info_id_parent'] = $this->getParentID($attribute['value']); + break; - case 'CATEGORIES': - $cats = $this->find_or_add_categories(explode(',', $attributes['value']), $_taskID); + case 'CATEGORIES': + if (!empty($attribute['value'])) + { + $cats = $this->find_or_add_categories(explode(',',$attribute['value']), $_taskID); $taskData['info_cat'] = $cats[0]; - break; + } + break; - case 'UID': - if (strlen($attributes['value']) >= $minimum_uid_length) { - $taskData['info_uid'] = $attributes['value']; - } - break; + case 'UID': + if (strlen($attribute['value']) >= $minimum_uid_length) + { + $taskData['info_uid'] = $attribute['value']; + } + break; - case 'PERCENT-COMPLETE': - $taskData['info_percent'] = (int) $attributes['value']; - break; - } + case 'PERCENT-COMPLETE': + $taskData['info_percent'] = (int) $attribute['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; } + break; } - return false; + if ($this->log) + { + error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."($_taskID)\n" . + ($taskData ? array2string($taskData) : 'FALSE') . "\n",3,$this->logfile); + } + return $taskData; } /** @@ -424,7 +613,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'); @@ -433,78 +623,75 @@ 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'])) + { + $cats = $this->get_categories(array($note['info_cat'])); + $note['info_cat'] = $GLOBALS['egw']->translation->convert($cats[0], + $GLOBALS['egw']->translation->charset(), 'UTF-8'); + } $vnote = new Horde_iCalendar_vnote(); - $options = array('CHARSET' => 'UTF-8'); $vNote->setAttribute('VERSION', '1.1'); - foreach (array( 'SUMMARY' => $note['info_subject'], - 'BODY' => $note['info_des'], + foreach (array( 'SUMMARY' => $note['info_subject'], + 'BODY' => $note['info_des'], + 'CATEGORIES' => $note['info_cat'], ) as $field => $value) { - $vnote->setAttribute($field, $value); - if ($this->productManufacturer != 'groupdav' - && preg_match('/([\177-\377])/', $value)) + $options = array(); + if (preg_match('/[^\x20-\x7F]/', $value)) { - $vevent->setParameter($field, $options); + $options['CHARSET'] = 'UTF-8'; + switch ($this->productManufacturer) + { + case 'groupdav': + if ($this->productName == 'kde') + { + $options['ENCODING'] = 'QUOTED-PRINTABLE'; + } + else + { + $options['CHARSET'] = ''; + + if (preg_match('/([\000-\012\015\016\020-\037\075])/', $value)) + { + $options['ENCODING'] = 'QUOTED-PRINTABLE'; + } + else + { + $options['ENCODING'] = ''; + } + } + break; + case 'funambol': + $options['ENCODING'] = 'FUNAMBOL-QP'; + } } + $vevent->setAttribute($field, $value, $options); } if ($note['info_startdate']) { $vnote->setAttribute('DCREATED',$note['info_startdate']); } - $vnote->setAttribute('DCREATED',$GLOBALS['egw']->contenthistory->getTSforAction('infolog_note',$_noteID,'add')); - $vnote->setAttribute('LAST-MODIFIED',$GLOBALS['egw']->contenthistory->getTSforAction('infolog_note',$_noteID,'modify')); - - if (!empty($note['info_cat'])) + else { - $cats = $this->get_categories(array($note['info_cat'])); - $value = $cats[0]; - $vnote->setAttribute('CATEGORIES', $value); - if ($this->productManufacturer != 'groupdav' - && preg_match('/([\177-\377])/', $value)) - { - $vevent->setParameter('CATEGORIES', $options); - } + $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'); - return $vnote->exportvCalendar(); - break; + $retval = $vnote->exportvCalendar(); + if ($this->log) + { + error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()\n" . + array2string($retval)."\n",3,$this->logfile); + } + return $retval; } return false; } - /** - * Check whether to export a date or date+time - * - * @param Horde_iCalendar_* $vevent - * @param string $attr attribute name - * @param int $value timestamp - * @param boolean $force=false, set DATE in any case - * @return boolean true, if date only - */ - static function setDateOrTime($vevent,$attr,$value,$force=false) - { - if ($force || date('Hi', $value) == '0000') - { - $vevent->setAttribute($attr, array( - 'year' => date('Y', $value), - 'month' => date('m', $value), - 'mday' => date('d', $value), - ), array('VALUE' => 'DATE')); - return true; - } - else - { - $vevent->setAttribute($attr, $value); - } - return false; - } - - /** * Import a VNOTE component of an iCal * @@ -516,14 +703,25 @@ class infolog_ical extends infolog_bo */ function importVNOTE(&$_vcalData, $_type, $_noteID=-1, $merge=false) { + if ($this->log) + { + error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()\n" . + array2string($_vcalData)."\n",3,$this->logfile); + } + if (!($note = $this->vnotetoegw($_vcalData, $_type, $_noteID))) 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); + if ($this->log) + { + error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()\n" . + array2string($note)."\n",3,$this->logfile); + } + + return $this->write($note, true, true, false); } /** @@ -531,40 +729,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->useServerTZ); } /** @@ -577,6 +754,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': @@ -586,24 +770,21 @@ 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': $vnote = new Horde_iCalendar; if (!$vcal->parsevCalendar($_data)) return false; + $version = $vcal->getAttribute('VERSION'); $components = $vnote->getComponent(); foreach ($components as $component) @@ -618,24 +799,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': - $cats = $this->find_or_add_categories(explode(',', $attribute['value']), $_noteID); - $note['info_cat'] = $cats[0]; + if ($attribute['value']) + { + $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; } /** @@ -678,9 +866,31 @@ class infolog_ical extends infolog_bo { $this->uidExtension = true; } + if (isset($deviceInfo['tzid']) && + $deviceInfo['tzid']) + { + $this->useServerTZ = ($deviceInfo['tzid'] == 1); + } } - Horde::logMessage('setSupportedFields(' . $this->productManufacturer . ', ' . $this->productName .')', __FILE__, __LINE__, PEAR_LOG_DEBUG); + if (in_array($this->productManufacturer,array('file','groupdav'))) + { + $this->useServerTZ = true; + } + + if ($this->log) + { + error_log(__FILE__.'['.__LINE__.'] '.__METHOD__. + '(' . $this->productManufacturer . + ', '. $this->productName .', ' . + ($this->useServerTZ ? 'SERVERTIME' : 'USERTIME') . + ")\n" , 3, $this->logfile); + } + + Horde::logMessage('setSupportedFields(' . $this->productManufacturer . ', ' + . $this->productName .', ' . + ($this->useServerTZ ? 'SERVERTIME' : 'USERTIME') .')', + __FILE__, __LINE__, PEAR_LOG_DEBUG); } } diff --git a/infolog/inc/class.infolog_sif.inc.php b/infolog/inc/class.infolog_sif.inc.php index 602e7fca21..120a426662 100644 --- a/infolog/inc/class.infolog_sif.inc.php +++ b/infolog/inc/class.infolog_sif.inc.php @@ -90,6 +90,59 @@ class infolog_sif extends infolog_bo */ var $uidExtension = false; + /** + * user preference: use server timezone for exports to device + * + * @var boolean + */ + var $useServerTZ = false; + + /** + * 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 $servertime=false if true => export in server-time + * + */ + function getDateTime($time, $servertime=false) + { + // check for date --> export it as such + if (date('Hi', $time) == '0000') + { + $value = date('Y-m-d', $time); + } + else + { + if ($servertime) + { + $value = date('Ymd\THis', $time); + } + else + { + $value = $this->vCalendar->_exportDateTime($time); + } + } + return $value; + } function startElement($_parser, $_tag, $_attributes) { @@ -121,14 +174,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 +190,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 +213,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 +249,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 +257,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; @@ -204,53 +267,49 @@ class infolog_sif extends infolog_bo case 'info_cat': if (!empty($value)) { - $categories1 = explode(',', $value); - $categories2 = explode(';', $value); - $categories = count($categories1) > count($categories2) ? $categories1 : $categories2; - $categories = $this->find_or_add_categories($categories, $_id); - $taskData['info_cat'] = $categories[0]; + $categories = $this->find_or_add_categories(explode(';', $value), $_id); + $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': @@ -259,7 +318,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); } @@ -267,25 +326,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) { @@ -298,40 +360,40 @@ 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; case 'info_cat': if (!empty($value)) { - $categories1 = explode(',', $value); - $categories2 = explode(';', $value); - $categories = count($categories1) > count($categories2) ? $categories1 : $categories2; - $categories = $this->find_or_add_categories($categories, $_id); - $noteData['info_cat'] = $categories[0]; + $categories = $this->find_or_add_categories(explode(';', $value), $_id); + $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; } /** @@ -345,28 +407,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); } /** @@ -379,18 +426,12 @@ class infolog_sif extends infolog_bo */ function addSIF($_sifData, $_id, $_sifType, $merge=false) { + if (!($egwData = $this->siftoegw($_sifData, $_sifType, $_id))) 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); } @@ -405,189 +446,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" . 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 .= "1"; - } - else - { - $sifTask .= "0"; - continue; - } - case 'DueDate': - if (!empty($value)) - { - $hdate = new Horde_Date($value); - $value = $vcal->_exportDate($hdate, '000000Z'); - $sifTask .= "<$sifField>$value"; - } - else - { - $sifTask .= "<$sifField>"; - } - break; - case 'StartDate': - if (!empty($value)) - { - $value = $vcal->_exportDateTime($value); - $sifTask .= "<$sifField>$value"; - } - else - { - $sifTask .= "<$sifField>"; - } - break; - - case 'Importance': - if ($value > 3) $value = 3; - $sifTask .= "<$sifField>$value"; - break; - - case 'Sensitivity': - $value = ($value == 'private' ? '2' : '0'); - $sifTask .= "<$sifField>$value"; - 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"; - 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"; - break; - } - } - $sifTask .= '00'; - 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" . 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 .= "0"; + continue; + } + $sifTask .= "1"; + + case 'DueDate': + case 'StartDate': + $sifTask .= "<$sifField>"; + if (!empty($value)) + { + $sifTask .= $this->getDateTime($value, $this->useServerTZ); + } + $sifTask .= ""; + break; + + case 'Importance': + if ($value > 3) $value = 3; + $sifTask .= "<$sifField>$value"; + break; + + case 'Sensitivity': + $value = ($value == 'private' ? '2' : '0'); + $sifTask .= "<$sifField>$value"; + 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"; + 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"; + break; + } + } + $sifTask .= '00'; + return $sifTask; case 'note': - if (($taskData = $this->read($_id))) + $sifNote = self::xml_decl . "\n" . self::SIF_decl; + + foreach ($this->_sifNoteMapping as $sifField => $egwField) { - $vcal = new Horde_iCalendar('1.0'); + if(empty($egwField)) continue; - $sifNote = self::xml_decl . "\n" . 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->useServerTZ); + } + $sifNote .= ''; + 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"; + 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"; - break; - } + default: + $value = @htmlspecialchars($value, ENT_QUOTES, 'utf-8'); + $sifNote .= "<$sifField>$value"; + break; } - $sifNote .= ''; - return $sifNote; } - break; - - default; - return false; + $sifNote .= ''; + return $sifNote; } - + return false; } /** @@ -610,6 +623,11 @@ class infolog_sif extends infolog_bo { $this->uidExtension = true; } + if (isset($deviceInfo['tzid']) && + $deviceInfo['tzid']) + { + $this->useServerTZ = ($deviceInfo['tzid'] == 1); + } } // store product name and version, to be able to use it elsewhere if ($_productName) diff --git a/infolog/inc/class.infolog_so.inc.php b/infolog/inc/class.infolog_so.inc.php index c57a1327ec..95684c8e20 100644 --- a/infolog/inc/class.infolog_so.inc.php +++ b/infolog/inc/class.infolog_so.inc.php @@ -5,7 +5,7 @@ * @link http://www.egroupware.org * @author Ralf Becker * @package infolog - * @copyright (c) 2003-8 by Ralf Becker + * @copyright (c) 2003-9 by Ralf Becker * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License * @version $Id$ */ @@ -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 "

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)."

\n"; @@ -724,18 +738,24 @@ class infolog_so if (substr($col,0,5) != 'info_' && substr($col,0,1)!='#') $col = 'info_'.$col; if (!empty($data) && preg_match('/^[a-z_0-9]+$/i',$col)) { - if ($col == 'info_responsible') + switch ($col) { - $data = (int) $data; - if (!$data) continue; - $filtermethod .= " AND (".$this->responsible_filter($data)." OR info_responsible='0' AND ". - $this->db->expression($this->info_table,array( - 'info_owner' => $data > 0 ? $data : $GLOBALS['egw']->accounts->members($data,true) - )).')'; - } - else - { - $filtermethod .= ' AND '.$this->db->expression($this->info_table,array($col => $data)); + case 'info_responsible': + $data = (int) $data; + if (!$data) continue; + $filtermethod .= ' AND ('.$this->responsible_filter($data)." OR info_responsible='0' AND ". + $this->db->expression($this->info_table,array( + 'info_owner' => $data > 0 ? $data : $GLOBALS['egw']->accounts->members($data,true) + )).')'; + break; + + case 'info_id': // info_id itself is ambigous + $filtermethod .= ' AND '.$this->db->expression($this->info_table,'main.',array('info_id' => $data)); + break; + + default: + $filtermethod .= ' AND '.$this->db->expression($this->info_table,array($col => $data)); + break; } } if ($col[0] == '#' && $query['custom_fields'] && $data) diff --git a/infolog/inc/class.infolog_tracking.inc.php b/infolog/inc/class.infolog_tracking.inc.php index 7871b58026..b4d7beed57 100644 --- a/infolog/inc/class.infolog_tracking.inc.php +++ b/infolog/inc/class.infolog_tracking.inc.php @@ -169,11 +169,11 @@ class infolog_tracking extends bo_tracking { 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)); + $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)); + $this->datetime($data['info_datemodified'])); } /** @@ -207,10 +207,10 @@ class infolog_tracking extends bo_tracking 'info_owner' => $GLOBALS['egw']->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_datecomplete'] ? $this->datetime($data['info_datecompleted']-$this->infolog->tz_offset_s) : '', + 'info_datecompleted' => $data['info_datecomplete'] ? $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)