SyncML Content Handling

* Improved find-methods
* Timezone support for InfoLog
* SyncML Preferences
    - addressbook and address list are now joined
    - Primary User Group for addressbook and calendar
* SlowSync uses old mapping information (can be disabled within the preferences)
This commit is contained in:
Jörg Lehrke 2010-02-09 21:56:39 +00:00
parent 9bf462d616
commit b6097fa156
11 changed files with 1374 additions and 820 deletions

View File

@ -18,12 +18,6 @@
*/
class addressbook_bo extends addressbook_so
{
/**
* @var int $tz_offset_s 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 $tz_offset_s;
/**
* @var int $now_su actual user (!) time
*/
@ -95,6 +89,13 @@ class addressbook_bo extends addressbook_so
var $business_contact_fields = array();
var $home_contact_fields = array();
/**
* Set Logging
*
* @var boolean
*/
var $log = false;
/**
* Number and message of last error or false if no error, atm. only used for saving
*
@ -128,7 +129,7 @@ class addressbook_bo extends addressbook_so
/**
* Tracking changes
*
*
* @var object
*/
protected $tracking;
@ -137,8 +138,7 @@ class addressbook_bo extends addressbook_so
{
parent::__construct($contact_app);
$this->tz_offset_s = 3600 * $GLOBALS['egw_info']['user']['preferences']['common']['tz_offset'];
$this->now_su = time() + $this->tz_offset_s;
$this->now_su = new egw_time('now');
$this->prefs =& $GLOBALS['egw_info']['user']['preferences']['addressbook'];
// get the default addressbook from the users prefs
@ -400,7 +400,7 @@ class addressbook_bo extends addressbook_so
*/
function set_all_fileas($fileas_type,$all=false,&$errors=null,$ignore_acl=false)
{
if ($type != '' && !in_array($type,$this->fileas_types))
if ($fileas_type != '' && !in_array($fileas_type, $this->fileas_types))
{
return false;
}
@ -572,15 +572,18 @@ class addressbook_bo extends addressbook_so
* This function needs to be reimplemented in the derived class
*
* @param array $data
* @param $date_format='ts' date-formats: 'ts'=timestamp, 'server'=timestamp in server-time, 'array'=array or string with date-format
*
* @return array updated data
*/
function db2data($data)
function db2data($data, $date_format='ts')
{
// convert timestamps from server-time in the db to user-time
foreach($this->timestamps as $name)
foreach ($this->timestamps as $name)
{
if(isset($data[$name]))
if (isset($data[$name]))
{
$data[$name] += $this->tz_offset_s;
$data[$name] = egw_time::server2user($data[$name], $date_format);
}
}
$data['photo'] = $this->photo_src($data['id'],$data['jpegphoto']);
@ -618,15 +621,18 @@ class addressbook_bo extends addressbook_so
* this needs to be reimplemented in the derived class
*
* @param array $data
* @param $date_format='ts' date-formats: 'ts'=timestamp, 'server'=timestamp in server-time, 'array'=array or string with date-format
*
* @return array upated data
*/
function data2db($data)
function data2db($data, $date_format='ts')
{
// convert timestamps from user-time to server-time in the db
foreach($this->timestamps as $name)
foreach ($this->timestamps as $name)
{
if(isset($data[$name]))
if (isset($data[$name]))
{
$data[$name] -= $this->tz_offset_s;
$data[$name] = egw_time::server2user($data[$name], $date_format);
}
}
return $data;
@ -748,8 +754,8 @@ class addressbook_bo extends addressbook_so
}
// Get old record for tracking changes
$old = $this->read($contact['id']);
$old = $this->data2db($this->read($contact['id']));
// we dont update the content-history, if we run inside setup (admin-account-creation)
if(!($this->error = parent::save($to_write)) && is_object($GLOBALS['egw']->contenthistory))
{
@ -1308,6 +1314,7 @@ class addressbook_bo extends addressbook_so
function merge($ids)
{
$this->error = false;
$account = null;
foreach(parent::search(array('id'=>$ids),false) as $contact) // $this->search calls the extended search from ui!
{
if ($contact['account_id'])
@ -1661,38 +1668,55 @@ class addressbook_bo extends addressbook_so
*
* @param array $contact the contact data we try to find
* @param boolean $relax=false if asked to relax, we only match against some key fields
* @return the contact_id of the matching entry or false (if none matches)
* @return array od matching contact_ids
*/
function find_contact($contact, $relax=false)
{
if (!empty($contact['uid']))
if ($this->log)
{
// Try the given UID first
Horde::logMessage('Addressbook find UID: '. $contact['uid'],
__FILE__, __LINE__, PEAR_LOG_DEBUG);
$criteria = array ('contact_uid' => $contact['uid']);
if (($found = parent::search($criteria))
&& ($uidmatch = array_shift($found)))
{
return $uidmatch['contact_id'];
}
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
. '('. ($relax ? 'RELAX': 'EXACT') . ')[ContactData]:'
. array2string($contact));
}
unset($contact['uid']);
$matchingContacts = array();
if ($contact['id'] && ($found = $this->read($contact['id'])))
{
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
. '()[ContactID]: ' . $contact['id']);
}
// We only do a simple consistency check
Horde::logMessage('Addressbook find ID: '. $contact['id'],
__FILE__, __LINE__, PEAR_LOG_DEBUG);
if ((empty($found['n_family']) || $found['n_family'] == $contact['n_family'])
&& (empty($found['n_given']) || $found['n_given'] == $contact['n_given'])
&& (empty($found['org_name']) || $found['org_name'] == $contact['org_name']))
{
return $found['id'];
return array($found['id']);
}
}
unset($contact['id']);
if (!$relax && !empty($contact['uid']))
{
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
. '()[ContactUID]: ' . $contact['uid']);
}
// Try the given UID first
$criteria = array ('contact_uid' => $contact['uid']);
if (($foundContacts = parent::search($criteria)))
{
foreach ($foundContacts as $egwContact)
{
$matchingContacts[] = $egwContact['id'];
}
}
return $matchingContacts;
}
unset($contact['uid']);
$columns_to_search = array('n_family', 'n_given', 'n_middle', 'n_prefix', 'n_suffix',
'bday', 'org_name', 'org_unit', 'title', 'role',
'email', 'email_home');
@ -1813,28 +1837,47 @@ class addressbook_bo extends addressbook_so
}
}
}
Horde::logMessage("Addressbook find step 1:\n" . print_r($criteria, true) .
"filter:\n" . print_r($filter, true), __FILE__, __LINE__, PEAR_LOG_DEBUG);
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
. '()[Addressbook FIND Step 1]: '
. 'FILTER:' . array2string($filter)
. 'CRITERIA' . array2string($criteria));
}
// first try full match
if (($foundContacts = parent::search($criteria, true, '', '', '', False, 'AND', false, $filter)))
{
$result = $foundContacts[0]['id'];
foreach ($foundContacts as $egwContact)
{
$matchingContacts[] = $egwContact['id'];
}
}
// No need for more searches for relaxed matching
if (!$relax && !$result && !$this->all_empty($contact, $addr_one_fields)
if (!$relax || count($matchingContacts)) return $matchingContacts;
if (!$this->all_empty($contact, $addr_one_fields)
&& $this->all_empty($contact, $addr_two_fields))
{
// try given address and ignore the second one in EGW
$filter = array_diff($filter, $no_addr_two);
Horde::logMessage("Addressbook find step 2:\n" . print_r($criteria, true) .
"filter:\n" . print_r($filter, true), __FILE__, __LINE__, PEAR_LOG_DEBUG);
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
. '()[Addressbook FIND Step 2]: '
. 'FILTER:' . array2string($filter)
. 'CRITERIA' . array2string($criteria));
}
if (($foundContacts = parent::search($criteria, true, '', '', '', False, 'AND', false, $filter)))
{
$result = $foundContacts[0]['id'];
foreach ($foundContacts as $egwContact)
{
$matchingContacts[] = $egwContact['id'];
}
}
else
{
@ -1859,16 +1902,25 @@ class addressbook_bo extends addressbook_so
$filter = $filter + $no_addr_one;
Horde::logMessage("Addressbook find step 3:\n" . print_r($criteria, true) .
"filter:\n" . print_r($filter, true), __FILE__, __LINE__, PEAR_LOG_DEBUG);
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
. '()[Addressbook FIND Step 3]: '
. 'FILTER:' . array2string($filter)
. 'CRITERIA' . array2string($criteria));
}
if (($foundContacts = parent::search($criteria, true, '', '', '', False, 'AND', false, $filter)))
{
$result = $foundContacts[0]['id'];
foreach ($foundContacts as $egwContact)
{
$matchingContacts[] = $egwContact['id'];
}
}
}
}
elseif (!$relax && !$result)
{ // No more searches for relaxed matching, try again after address swap
else
{ // try again after address swap
$filter = $empty_columns;
@ -1907,15 +1959,26 @@ class addressbook_bo extends addressbook_so
$filter[] = "(" . $db_col . " IS NULL OR " . $db_col . " = '')";
}
}
Horde::logMessage("Addressbook find step 4:\n" . print_r($criteria, true) .
"filter:\n" . print_r($filter, true), __FILE__, __LINE__, PEAR_LOG_DEBUG);
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
. '()[Addressbook FIND Step 4]: '
. 'FILTER:' . array2string($filter)
. 'CRITERIA' . array2string($criteria));
}
if(($foundContacts = parent::search($criteria, true, '', '', '', False, 'AND', false, $filter)))
{
$result = $foundContacts[0]['id'];
foreach ($foundContacts as $egwContact)
{
$matchingContacts[] = $egwContact['id'];
}
}
}
return $result;
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
. '()[FOUND]:' . array2string($matchingContacts));
}
return $matchingContacts;
}
}

View File

@ -79,7 +79,7 @@ class addressbook_contactform
require_once(EGW_INCLUDE_ROOT.'/addressbook/inc/class.addressbook_tracking.inc.php');
$tracking = new addressbook_tracking($contact);
}
if ($tracking->do_notifications($content,null))
if ($tracking->do_notifications($contact->data2db($content),null))
{
return '<p align="center">'.$content['msg'].'</p>';
}

View File

@ -159,7 +159,7 @@ class addressbook_sif extends addressbook_bo
break;
default:
$finalContact[$key] = $value;
$finalContact[$key] = str_replace("\r\n", "\n", $value);
break;
}
}
@ -173,18 +173,20 @@ class addressbook_sif extends addressbook_bo
* Search an exactly matching entry (used for slow sync)
*
* @param string $_sifdata
* @return boolean/int/string contact-id or false, if not found
* @return array of matching contact-ids
*/
function search($_sifdata, $contentID=null, $relax=false)
{
$result = false;
$result = array();
if($contact = $this->siftoegw($_sifdata, $contentID)) {
if ($contentID) {
$contact['contact_id'] = $contentID;
}
$result = $this->find_contact($contact, $relax);
}
if($contact = $this->siftoegw($_sifdata, $contentID))
{
if ($contentID)
{
$contact['contact_id'] = $contentID;
}
$result = $this->find_contact($contact, $relax);
}
return $result;
}
@ -198,22 +200,44 @@ class addressbook_sif extends addressbook_bo
*/
function addSIF($_sifdata, $_abID=null, $merge=false)
{
#error_log('ABID: '.$_abID);
#error_log(base64_decode($_sifdata));
if(!$contact = $this->siftoegw($_sifdata, $_abID)) {
if(!$contact = $this->siftoegw($_sifdata, $_abID))
{
return false;
}
if($_abID) {
if($_abID)
{
if (($old_contact = $this->read($_abID)))
{
if ($merge)
{
foreach ($contact as $key => $value)
{
if (!empty($old_contact[$key]))
{
$contact[$key] = $old_contact[$key];
}
}
}
else
{
if (isset($old_contact['account_id']))
{
$contact['account_id'] = $old_contact['account_id'];
}
}
}
// update entry
$contact['id'] = $_abID;
}
elseif (array_key_exists('filter_addressbook', $GLOBALS['egw_info']['user']['preferences']['syncml']))
{
$contact['owner'] = (int) $GLOBALS['egw_info']['user']['preferences']['syncml']['filter_addressbook'];
if ($contact['owner'] == -1)
{
$contact['owner'] = $GLOBALS['egw_info']['user']['account_primary_group'];
}
}
return $this->save($contact);
}

View File

@ -72,7 +72,8 @@ class addressbook_tracking extends bo_tracking
$this->contacts =& $bocontacts;
if(is_object($bocontacts->somain)) {
if (is_object($bocontacts->somain))
{
$this->field2history = array_combine($bocontacts->somain->db_cols, $bocontacts->somain->db_cols);
unset($this->field2history['modified']);
unset($this->field2history['modifier']);
@ -156,8 +157,7 @@ class addressbook_tracking extends bo_tracking
* Get the details of an entry
*
* @param array $data
* @param string $datetime_format of user to notify, eg. 'Y-m-d H:i'
* @param int $tz_offset_s offset in sec to be add to server-time to get the user-time of the user to notify
*
* @return array of details as array with values for keys 'label','value','type'
*/
function get_details($data)
@ -241,4 +241,4 @@ class addressbook_tracking extends bo_tracking
}
return $details;
}
}
}

View File

@ -130,10 +130,9 @@ class addressbook_vcal extends addressbook_bo
if($_abID)
{
if ($merge)
if (($old_contact = $this->read($_abID)))
{
$old_contact = $this->read($_abID);
if ($old_contact)
if ($merge)
{
foreach ($contact as $key => $value)
{
@ -143,6 +142,13 @@ class addressbook_vcal extends addressbook_bo
}
}
}
else
{
if (isset($old_contact['account_id']))
{
$contact['account_id'] = $old_contact['account_id'];
}
}
}
// update entry
$contact['id'] = $_abID;
@ -150,6 +156,10 @@ class addressbook_vcal extends addressbook_bo
elseif (array_key_exists('filter_addressbook', $GLOBALS['egw_info']['user']['preferences']['syncml']))
{
$contact['owner'] = (int) $GLOBALS['egw_info']['user']['preferences']['syncml']['filter_addressbook'];
if ($contact['owner'] == -1)
{
$contact['owner'] = $GLOBALS['egw_info']['user']['account_primary_group'];
}
}
return $this->save($contact);
}
@ -443,7 +453,7 @@ class addressbook_vcal extends addressbook_bo
function search($_vcard, $contentID=null, $relax=false)
{
$result = false;
$result = array();
if (($contact = $this->vcardtoegw($_vcard, $contentID)))
{
@ -887,8 +897,7 @@ class addressbook_vcal extends addressbook_bo
break;
case 'note':
// note may contain ','s but maybe this needs to be fixed in vcard parser...
$contact[$fieldName] = $vcardValues[$vcardKey]['value'];
$contact[$fieldName] = str_replace("\r\n", "\n", $vcardValues[$vcardKey]['value']);
break;
case 'uid':

View File

@ -67,6 +67,13 @@ class calendar_boupdate extends calendar_bo
*/
var $log = false;
/**
* Cached timezone data
*
* @var array id => data
*/
protected static $tz_cache = array();
/**
* Constructor
*/
@ -1417,7 +1424,7 @@ class calendar_boupdate extends calendar_bo
* @param string filter='exact' exact -> check for identical matches
* relax -> be more tolerant
* master -> try to find a releated series master
* @return array calendar_id's of matching entries
* @return array calendar_ids of matching entries
*/
function find_event($event, $filter='exact')
{
@ -1432,23 +1439,21 @@ class calendar_boupdate extends calendar_bo
if ($filter == 'master')
{
if (empty($event['uid']) || !isset($event['recurrence']))
{
$recur_date = $event['start']; // without UID we ignore RECURRENCE-IDs
}
else
if (isset($event['recurrence']))
{
$recur_date = $event['recurrence'];
}
else
{
$recur_date = $event['start'];
}
$recur_date = $this->date2usertime($recur_date);
}
else
{
$recur_date = $event['start'];
$recur_date = 0;
}
$recur_date = $this->date2usertime($recur_date);
if ($event['id'])
{
if ($this->log)
@ -1465,9 +1470,7 @@ class calendar_boupdate extends calendar_bo
}
// Just a simple consistency check
if ($filter == 'master' && $egwEvent['recur_type'] != MCAL_RECUR_NONE ||
$egwEvent['title'] == $event['title'] &&
abs($egwEvent['start'] - $event['start']) < 60 &&
abs($egwEvent['end'] - $event['end']) < 120)
strpos($egwEvent['title'], $event['title']) === 0)
{
$retval = $egwEvent['id'];
if ($egwEvent['recur_type'] != MCAL_RECUR_NONE &&
@ -1483,41 +1486,6 @@ class calendar_boupdate extends calendar_bo
}
unset($event['id']);
if (isset($event['whole_day']) && $event['whole_day'])
{
if ($filter == 'relax')
{
$delta = 1800;
}
else
{
$delta = 60;
}
// check length with some tolerance
$length = $event['end'] - $event['start'] - $delta;
$query[] = ('(cal_end-cal_start)>' . $length);
$length += 2 * $delta;
$query[] = ('(cal_end-cal_start)<' . $length);
}
elseif (isset($event['start']))
{
if ($filter != 'master')
{
if ($filter == 'relax')
{
$query[] = ('cal_start>' . ($event['start'] - 3600));
$query[] = ('cal_start<' . ($event['start'] + 3600));
}
else
{
// we accept a tiny tolerance
$query[] = ('cal_start>' . ($event['start'] - 2));
$query[] = ('cal_start<' . ($event['start'] + 2));
}
}
}
if ($filter == 'master')
{
$query[] = 'recur_type!='. MCAL_RECUR_NONE;
@ -1574,20 +1542,45 @@ class calendar_boupdate extends calendar_bo
}
}
}
if (empty($event['uid']))
if ($filter == 'relax' || empty($event['uid']))
{
$matchFields = array('title', 'priority', 'public', 'non_blocking');
switch ($filter)
if (isset($event['whole_day']) && $event['whole_day'])
{
case 'relax':
$matchFields[] = 'location';
case 'master':
break;
default:
$matchFields[] = 'description';
$matchFields[] = 'location';
}
if ($filter == 'relax')
{
$delta = 1800;
}
else
{
$delta = 60;
}
// check length with some tolerance
$length = $event['end'] - $event['start'] - $delta;
$query[] = ('(cal_end-cal_start)>' . $length);
$length += 2 * $delta;
$query[] = ('(cal_end-cal_start)<' . $length);
$query[] = ('cal_start>' . ($event['start'] - 86400));
$query[] = ('cal_start<' . ($event['start'] + 86400));
}
elseif (isset($event['start']))
{
if ($filter != 'master')
{
if ($filter == 'relax')
{
$query[] = ('cal_start>' . ($event['start'] - 3600));
$query[] = ('cal_start<' . ($event['start'] + 3600));
}
else
{
// we accept a tiny tolerance
$query[] = ('cal_start>' . ($event['start'] - 2));
$query[] = ('cal_start<' . ($event['start'] + 2));
}
}
}
$matchFields = array('priority', 'public', 'non_blocking', 'recurrence');
foreach ($matchFields as $key)
{
if (!empty($event[$key])) $query['cal_'.$key] = $event[$key];
@ -1601,7 +1594,7 @@ class calendar_boupdate extends calendar_bo
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
'(' . $event['uid'] . ')[EventUID]');
}
if ($filter != 'relax' && isset($event['recurrence']))
if (isset($event['recurrence']))
{
$query['cal_recurrence'] = $event['recurrence'];
}
@ -1635,6 +1628,12 @@ class calendar_boupdate extends calendar_bo
'[FOUND]: ' . array2string($egwEvent));
}
if ($filter == 'exact' && !empty($event['uid']))
{
$matchingEvents[] = $egwEvent['id']; // UID found
continue;
}
// convert timezone id of event to tzid (iCal id like 'Europe/Berlin')
if (!$egwEvent['tz_id'] || !($egwEvent['tzid'] = calendar_timezones::id2tz($egwEvent['tz_id'])))
{
@ -1671,10 +1670,35 @@ class calendar_boupdate extends calendar_bo
}
}
if ($filter != 'master')
// check for real match
$matchFields = array('title');
switch ($filter)
{
case 'master':
break;
case 'relax':
$matchFields[] = 'location';
default:
$matchFields[] = 'description';
}
foreach ($matchFields as $key)
{
if (!empty($event[$key]) && (empty($egwEvent[$key])
|| strpos($egwEvent[$key], $event[$key]) !== 0))
{
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
"() event[$key] differ: '" . $event[$key] .
"' <> '" . $egwEvent[$key]) . "'";
}
continue 2; // next foundEvent
}
}
if ($filter != 'master' && is_array($event['category']))
{
// check categories
if (!is_array($event['category'])) $event['category'] = array();
$egwCategories = explode(',', $egwEvent['category']);
foreach ($egwCategories as $cat_id)
{
@ -1700,24 +1724,6 @@ class calendar_boupdate extends calendar_bo
continue;
}
}
/*
// check information
$matchFields = array();
foreach ($matchFields as $key)
{
if (isset($event[$key])
&& $event[$key] != $egwEvent[$key])
{
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
"() events[$key] differ: " . $event[$key] .
' <> ' . $egwEvent[$key]);
}
continue 2; // next foundEvent
}
}
*/
if ($filter != 'relax' && $filter != 'master')
{
@ -1924,6 +1930,7 @@ class calendar_boupdate extends calendar_bo
in_array($event['recurrence'], $master_event['recur_exception']))
{
$type = 'SERIES-PSEUDO-EXCEPTION'; // could also be a real one
$recurrence_event = $event;
break;
}
elseif (in_array($event['start'], $master_event['recur_exception']))
@ -2028,7 +2035,7 @@ class calendar_boupdate extends calendar_bo
);
}
/*
/**
* Translates all timestamps for a given event from server-time to user-time.
* The update() and save() methods expect timestamps in user-time.
* @param &$event the event we are working on

View File

@ -128,13 +128,6 @@ class calendar_ical extends calendar_boupdate
*/
var $tzid = null;
/**
* Cached timezone data
*
* @var array id => data
*/
protected static $tz_cache = array();
/**
* Device CTCap Properties
*
@ -257,7 +250,7 @@ class calendar_ical extends calendar_boupdate
// explicit device timezone
$tzid = $this->tzid;
}
else
elseif ($this->tzid === false)
{
// use event's timezone
$tzid = $event['tzid'];
@ -334,6 +327,7 @@ class calendar_ical extends calendar_boupdate
{
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]))
{
@ -525,7 +519,6 @@ class calendar_ical extends calendar_boupdate
foreach (array('start' => 'DTSTART','end-nextday' => 'DTEND') as $f => $t)
{
$time = new egw_time($event[$f],egw_time::$server_timezone);
$time->setTimezone(self::$tz_cache[$event['tzid']]);
$arr = egw_time::to($time,'array');
$vevent->setAttribute($t, array('year' => $arr['year'],'month' => $arr['month'],'mday' => $arr['day']),
array('VALUE' => 'DATE'));
@ -1005,6 +998,10 @@ class calendar_ical extends calendar_boupdate
{
$event['uid'] = $event_info['stored_event']['uid']; // restore the UID if it was not delivered
}
elseif (empty($event['id']))
{
$event['id'] = $event_info['stored_event']['id']; // CalDAV does only provide UIDs
}
if ($merge)
{
if ($this->log)
@ -1246,7 +1243,6 @@ class calendar_ical extends calendar_boupdate
if ($event_info['acl_edit'])
{
// Force SINGLE
unset($event['recurrence']);
$event['reference'] = 0;
$event_to_store = $event; // prevent $event from being changed by the update method
$this->server2usertime($event_to_store);
@ -1515,9 +1511,8 @@ class calendar_ical extends calendar_boupdate
if ($this->log)
{
$recur_date = $this->date2usertime($event_info['stored_event']['start']);
$event_info['stored_event'] = $this->read($event_info['stored_event']['id'], $recur_date);
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()\n" .
$event_info['stored_event'] = $this->read($event_info['stored_event']['id']);
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()[$updated_id]\n" .
array2string($event_info['stored_event'])."\n",3,$this->logfile);
}
}
@ -1630,11 +1625,25 @@ class calendar_ical extends calendar_boupdate
if (isset($deviceInfo['tzid']) &&
$deviceInfo['tzid'])
{
$this->tzid = $deviceInfo['tzid'];
switch ($deviceInfo['tzid'])
{
case 1:
$this->tzid = false;
break;
case 2:
$this->tzid = null;
break;
default:
$this->tzid = $deviceInfo['tzid'];
}
}
if (isset($GLOBALS['egw_info']['user']['preferences']['syncml']['calendar_owner']))
{
$owner = $GLOBALS['egw_info']['user']['preferences']['syncml']['calendar_owner'];
if ($owner == 0)
{
$owner = $GLOBALS['egw_info']['user']['account_primary_group'];
}
if (0 < (int)$owner && $this->check_perms(EGW_ACL_EDIT,0,$owner))
{
$this->calendarOwner = $owner;
@ -1656,11 +1665,15 @@ class calendar_ical extends calendar_boupdate
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
'(' . $this->productManufacturer .
', '. $this->productName . ")\n",3,$this->logfile);
', '. $this->productName .', ' .
($this->tzid ? $this->tzid : egw_time::$user_timezone->getName()) .
")\n" , 3, $this->logfile);
}
//Horde::logMessage('setSupportedFields(' . $this->productManufacturer
// . ', ' . $this->productName .')', __FILE__, __LINE__, PEAR_LOG_DEBUG);
//Horde::logMessage('setSupportedFields(' . $this->productManufacturer . ', '
// . $this->productName .', ' .
// ($this->tzid ? $this->tzid : egw_time::$user_timezone->getName()) .')',
// __FILE__, __LINE__, PEAR_LOG_DEBUG);
$defaultFields['minimal'] = array(
'public' => 'public',
@ -1863,8 +1876,14 @@ class calendar_ical extends calendar_boupdate
if ($this->tzid)
{
date_default_timezone_set($this->tzid);
$tzid = $this->tzid;
}
else
{
$tzid = egw_time::$user_timezone->getName();
}
date_default_timezone_set($tzid);
$vcal = new Horde_iCalendar;
if (!$vcal->parsevCalendar($_vcalData))
@ -1912,20 +1931,21 @@ class calendar_ical extends calendar_boupdate
}
}
$event['alarm'] = $alarms;
if ($this->tzid || empty($event['tzid']))
{
$event['tzid'] = $tzid;
}
$events[] = $event;
}
}
}
if ($this->tzid)
{
date_default_timezone_set($GLOBALS['egw_info']['server']['server_timezone']);
}
date_default_timezone_set($GLOBALS['egw_info']['server']['server_timezone']);
// if cal_id, etag or recur_date is given, use/set it for 1. event
if ($cal_id > 0) $events[0]['id'] = $cal_id;
if (!is_null($etag)) $events[0]['etag'] = $etag;
if (!is_null($etag)) $events[0]['etag'] = (int) $etag;
if ($recur_date) $events[0]['recurrence'] = $recur_date;
return $events;
@ -1974,32 +1994,34 @@ class calendar_ical extends calendar_boupdate
$dtstart_ts = is_numeric($attributes['value']) ? $attributes['value'] : $this->date2ts($attributes['value']);
$vcardData['start'] = $dtstart_ts;
if ($this->tzid)
{
// enforce device settings
$event['tzid'] = $this->tzid;
}
elseif (!empty($attributes['params']['TZID']))
{
// import TZID, if PHP understands it (we only care about TZID of starttime, as we store only a TZID for the whole event)
try
{
$tz = calendar_timezones::DateTimeZone($attributes['params']['TZID']);
$event['tzid'] = $tz->getName();
}
catch(Exception $e)
{
error_log(__METHOD__."() unknown TZID='{$attributes['params']['TZID']}', defaulting to user timezone '".egw_time::$user_timezone->getName()."'!");
$tz = egw_time::$user_timezone;
$event['tzid'] = egw_time::$user_timezone->getName(); // default to user timezone
}
}
else
{
$event['tzid'] = egw_time::$user_timezone->getName(); // default to user timezone
$event['tzid'] = date_default_timezone_get();
if (!empty($attributes['params']['TZID']))
{
// import TZID, if PHP understands it (we only care about TZID of starttime,
// as we store only a TZID for the whole event)
try
{
$tz = calendar_timezones::DateTimeZone($attributes['params']['TZID']);
$event['tzid'] = $tz->getName();
}
catch(Exception $e)
{
error_log(__METHOD__ . '() unknown TZID='
. $attributes['params']['TZID'] . ', defaulting to timezone "'
. date_default_timezone_get() . '".');
$event['tzid'] = date_default_timezone_get(); // default to current timezone
}
}
}
break;
case 'DTEND':
$dtend_ts = is_numeric($attributes['value']) ? $attributes['value'] : $this->date2ts($attributes['value']);
if (date('H:i:s',$dtend_ts) == '00:00:00')
@ -2028,7 +2050,6 @@ class calendar_ical extends calendar_boupdate
break;
case 'DESCRIPTION':
$vcardData['description'] = str_replace("\r\n", "\n", $attributes['value']);
$vcardData['description'] = str_replace("\r", "\n", $vcardData['description']);
if (preg_match('/\s*\[UID:(.+)?\]/Usm', $attributes['value'], $matches))
{
if (!isset($vCardData['uid'])
@ -2044,7 +2065,6 @@ class calendar_ical extends calendar_boupdate
break;
case 'LOCATION':
$vcardData['location'] = str_replace("\r\n", "\n", $attributes['value']);
$vcardData['location'] = str_replace("\r", "\n", $vcardData['location']);
break;
case 'RRULE':
$recurence = $attributes['value'];
@ -2301,7 +2321,6 @@ class calendar_ical extends calendar_boupdate
break;
case 'SUMMARY':
$vcardData['title'] = str_replace("\r\n", "\n", $attributes['value']);
$vcardData['title'] = str_replace("\r", "\n", $vcardData['title']);
break;
case 'UID':
if (strlen($attributes['value']) >= $minimum_uid_length)
@ -2457,11 +2476,16 @@ class calendar_ical extends calendar_boupdate
{
$attributes['params']['ROLE'] = 'CHAIR';
}
// add quantity and role
$event['participants'][$uid] =
calendar_so::combine_status($status,
$attributes['params']['X-EGROUPWARE-QUANTITY'],
$attributes['params']['ROLE']);
if (!isset($event['participants'][$uid]) ||
$event['participants'][$uid][0] != 'A')
{
// for multiple entries the ACCEPT wins
// add quantity and role
$event['participants'][$uid] =
calendar_so::combine_status($status,
$attributes['params']['X-EGROUPWARE-QUANTITY'],
$attributes['params']['ROLE']);
}
break;
case 'ORGANIZER':
@ -2575,6 +2599,8 @@ class calendar_ical extends calendar_boupdate
if ($this->calendarOwner) $event['owner'] = $this->calendarOwner;
if ($cal_id > 0) $event['id'] = $cal_id;
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()\n" .
@ -2595,6 +2621,7 @@ class calendar_ical extends calendar_boupdate
$filter = $relax ? 'relax' : 'exact';
$event = array_shift($events);
if ($this->so->isWholeDay($event)) $event['whole_day'] = true;
if ($contentID) $event['id'] = $contentID;
return $this->find_event($event, $filter);
}
if ($this->log)

View File

@ -96,13 +96,6 @@ class calendar_sif extends calendar_boupdate
*/
var $tzid = null;
/**
* Cached timezone data
*
* @var array id => data
*/
protected static $tz_cache = array();
/**
* Device CTCap Properties
*
@ -211,7 +204,7 @@ class calendar_sif extends calendar_boupdate
}
$time->setTimezone(self::$tz_cache[$tzid]);
return $this->vCalendar->_exportDateTime($time->format('Ymd\THis'));
return $time->format('Ymd\THis');
}
function siftoegw($sifData, $_calID=-1)
@ -220,13 +213,18 @@ class calendar_sif extends calendar_boupdate
$this->event = array();
$sysCharSet = $GLOBALS['egw']->translation->charset();
if ($this->tzid)
{
// enforce device settings
date_default_timezone_set($this->tzid);
$finalEvent['tzid'] = $this->tzid;
$tzid = $this->tzid;
}
else
{
$tzid = egw_time::$user_timezone->getName();
}
date_default_timezone_set($tzid);
$finalEvent['tzid'] = $tzid;
$this->xml_parser = xml_parser_create('UTF-8');
xml_set_object($this->xml_parser, $this);
@ -239,10 +237,7 @@ class calendar_sif extends calendar_boupdate
error_log(sprintf("XML error: %s at line %d",
xml_error_string(xml_get_error_code($this->xml_parser)),
xml_get_current_line_number($this->xml_parser)));
if ($this->tzid)
{
date_default_timezone_set($GLOBALS['egw_info']['server']['server_timezone']);
}
date_default_timezone_set($GLOBALS['egw_info']['server']['server_timezone']);
return false;
}
@ -379,17 +374,14 @@ class calendar_sif extends calendar_boupdate
}
default:
$finalEvent[$key] = $value;
$finalEvent[$key] = str_replace("\r\n", "\n", $value);
break;
}
}
if ($this->calendarOwner) $finalEvent['owner'] = $this->calendarOwner;
if ($this->tzid)
{
date_default_timezone_set($GLOBALS['egw_info']['server']['server_timezone']);
}
date_default_timezone_set($GLOBALS['egw_info']['server']['server_timezone']);
if ($_calID > 0) $finalEvent['id'] = $_calID;
@ -862,7 +854,7 @@ class calendar_sif extends calendar_boupdate
// explicit device timezone
$tzid = $this->tzid;
}
else
elseif ($this->tzid === false)
{
// use event's timezone
$tzid = $event['tzid'];
@ -1203,11 +1195,25 @@ class calendar_sif extends calendar_boupdate
if (isset($deviceInfo['tzid']) &&
$deviceInfo['tzid'])
{
$this->tzid = $deviceInfo['tzid'];
switch ($deviceInfo['tzid'])
{
case 1:
$this->tzid = false;
break;
case 2:
$this->tzid = null;
break;
default:
$this->tzid = $deviceInfo['tzid'];
}
}
if (isset($GLOBALS['egw_info']['user']['preferences']['syncml']['calendar_owner']))
{
$owner = $GLOBALS['egw_info']['user']['preferences']['syncml']['calendar_owner'];
if ($owner == 0)
{
$owner = $GLOBALS['egw_info']['user']['account_primary_group'];
}
if (0 < (int)$owner && $this->check_perms(EGW_ACL_EDIT,0,$owner))
{
$this->calendarOwner = $owner;

View File

@ -31,6 +31,21 @@ class infolog_bo
var $link_pathes = array();
var $send_file_ips = array();
/**
* 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
*
@ -403,6 +418,85 @@ 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
*
@ -452,30 +546,21 @@ class infolog_bo
}
if ($run_link_id2from) $this->link_id2from($data);
if ($data['info_enddate'])
{
// Set due date to 00:00
$due = new egw_time($data['info_enddate'], egw_time::$server_timezone);
$due->setTime(0, 0, 0);
if ($date_format != 'server')
{
$arr = egw_time::to($due,'array');
$due = new egw_time($arr, egw_time::$user_timezone);
}
$data['info_enddate'] = egw_time::to($due,'server');
}
// convert system- to user-time
foreach($this->timestamps as $time)
{
if ($data[$time]) $data[$time] = $this->date2usertime($data[$time],$date_format);
}
// convert server- to user-time
if ($date_format == 'ts')
{
$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');
}
return $data;
}
@ -520,7 +605,7 @@ class infolog_bo
}
}
}
if (!($info = $this->read($info_id, true, 'server'))) return false; // should not happen
if (!($info = $this->read($info_id, true, 'server'))) return false; // should not happen
$deleted = $info;
$deleted['info_status'] = 'deleted';
@ -696,37 +781,21 @@ class infolog_bo
$to_write = $values;
if ($user2server)
{
// convert user- to system-time
foreach($this->timestamps as $time)
{
if ($to_write[$time]) $to_write[$time] = egw_time::user2server($to_write[$time],'ts');
}
if ($values['info_enddate'])
{
// Set due date to 00:00
$due = new egw_time($values['info_enddate'], egw_time::$user_timezone);
$due->setTime(0, 0, 0);
$values['info_enddate'] = egw_time::to($due,'ts');
$arr = egw_time::to($due,'array');
$to_write['info_enddate'] = mktime(0, 0, 0, $arr['month'], $arr['day'], $arr['year']);
}
// 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::$user_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
foreach($this->timestamps as $time)
{
if ($values[$time]) $values[$time] = egw_time::server2user($values[$time],'ts');
}
if ($to_write['info_enddate'])
{
$due = new egw_time($to_write['info_enddate'], egw_time::$server_timezone);
$due->setTime(0, 0, 0);
$to_write['info_enddate'] = egw_time::to($due,'ts');
$arr = egw_time::to($due,'array');
$due = new egw_time($arr, egw_time::$user_timezone);
$values['info_enddate'] = egw_time::to($due,'ts');
}
$this->time2time($values);
}
if ($touch_modified || !$values['info_datemodified'])
@ -776,7 +845,7 @@ class infolog_bo
}
$values['info_id'] = $info_id;
$to_write['info_id'] = $info_id;
// if the info responsible array is not passed, fetch it from old.
// 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 ;-)
{
@ -843,25 +912,35 @@ class infolog_bo
}
$ret = $this->so->search($query);
// convert system- to user-time
if (is_array($ret))
{
foreach($ret as $id => &$data)
{
if ($data['info_enddate'])
if (!$this->check_access($data,EGW_ACL_READ))
{
// Set due date to 00:00 in user-time
$due = new egw_time($data['info_enddate'], egw_time::$server_timezone);
$due->setTime(0, 0, 0);
$arr = egw_time::to($due,'array');
$due = new egw_time($arr, egw_time::$user_timezone);
$data['info_enddate'] = egw_time::to($due,'server');
unset($ret[$id]);
continue;
}
foreach($this->timestamps as $time)
// convert system- to user-time
foreach($this->timestamps as $key)
{
if ($data[$time]) $data[$time] = $this->date2usertime($data[$time]);
if ($data[$key])
{
$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
self::set_link_cache($data);
}
@ -1177,13 +1256,12 @@ 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)
{
foreach($old_categories as $cat_id)
@ -1197,7 +1275,7 @@ class infolog_bo
}
$cat_id_list = array();
foreach($catname_list as $cat_name)
foreach ($catname_list as $cat_name)
{
$cat_name = trim($cat_name);
$cat_id = $this->categories->name2id($cat_name, 'X-');
@ -1218,7 +1296,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);
}
@ -1448,74 +1526,158 @@ class infolog_bo
/**
* Try to find a matching db entry
* This method is working completely in server-time and
* expects all time entriey to be 00:00 normalized.
* 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->so->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 (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->so->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']);
@ -1523,41 +1685,144 @@ class infolog_bo
// Horde::logMessage("findVTODO Filter\n"
// . print_r($filter, true),
// __FILE__, __LINE__, PEAR_LOG_DEBUG);
foreach ($this->so->search($filter) as $itemID => $taskData)
foreach ($this->so->search($filter) as $itemID => $egwData)
{
if (!$this->check_access($egwData,EGW_ACL_READ)) continue;
// Horde::logMessage("findVTODO Trying\n"
// . print_r($taskData, true),
// . print_r($egwData, 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'])
if (isset($infoData['info_cat'])
&& isset($egwData['info_cat']) && $egwData['info_cat']
&& $infoData['info_cat'] != $egwData['info_cat'])
{
$time = new egw_time($taskData['info_startdate'],egw_time::$server_timezone);
$time->setTime(0, 0, 0);
$startdate = egw_time::to($time,'server');
if ($egwData['info_startdate'] != $startdate) continue;
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'])
if (isset($infoData['info_startdate']) && $infoData['info_startdate'])
{
$time = new egw_time($taskData['info_datecompleted'],egw_time::$server_timezone);
$time->setTime(0, 0, 0);
$enddate = egw_time::to($time,'server');
if ($egwData['info_datecompleted'] != $enddate) continue;
// 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

@ -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
*
@ -148,6 +155,39 @@ class infolog_ical extends infolog_bo
$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 +293,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 +338,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);
}
/**
@ -331,7 +411,21 @@ class infolog_ical extends infolog_bo
*/
function importVTODO(&$_vcalData, $_taskID=-1, $merge=false)
{
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']))
@ -359,18 +453,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 +491,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 +517,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 +528,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;
}
/**
@ -580,7 +677,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 +727,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');
@ -684,40 +783,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 +808,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 +824,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 +853,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 +920,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,65 @@ 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
* @return mixed attribute value to set: integer timestamp if $tzid == 'UTC' otherwise Ymd\THis string IN $tzid
*/
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]);
}
else
{
$time->setTimezone(self::$tz_cache[$tzid]);
}
return $time->format('Ymd\THis');
}
function startElement($_parser, $_tag, $_attributes)
{
@ -121,14 +180,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 +196,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 +219,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 +255,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 +263,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 +274,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 +324,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 +332,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 +366,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 +380,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 +413,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 +432,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, true, false);
return $egwID;
return $this->write($egwData, true, true, false);
}
@ -399,189 +460,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, true, 'server')))
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, true, 'server')))
$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 +637,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)