GroupDAV updates

This commit is contained in:
Jörg Lehrke 2010-03-06 23:06:43 +00:00
parent 79824c2e0c
commit a8804a44ef
16 changed files with 1462 additions and 422 deletions

View File

@ -824,8 +824,8 @@ class addressbook_bo extends addressbook_so
/**
* reads contacts matched by key and puts all cols in the data array
*
* @param int/string $contact_id
* @return array/boolean array with contact data, null if not found or false on no view perms
* @param int|string $contact_id
* @return array|boolean array with contact data, null if not found or false on no view perms
*/
function read($contact_id)
{

View File

@ -51,10 +51,11 @@ class addressbook_groupdav extends groupdav_handler
* @param string $app 'calendar', 'addressbook' or 'infolog'
* @param int $debug=null debug-level to set
* @param string $base_uri=null base url of handler
* @param string $principalURL=null pricipal url of handler
*/
function __construct($app,$debug=null,$base_uri=null)
function __construct($app,$debug=null,$base_uri=null,$principalURL=null)
{
parent::__construct($app,$debug,$base_uri);
parent::__construct($app,$debug,$base_uri,$principalURL);
$this->bo = new addressbook_bo();
}
@ -67,7 +68,7 @@ class addressbook_groupdav extends groupdav_handler
*/
static function get_path($contact)
{
return '/addressbook/'.$contact[self::PATH_ATTRIBUTE].'.vcf';
return $contact[self::PATH_ATTRIBUTE].'.vcf';
}
/**
@ -95,8 +96,9 @@ class addressbook_groupdav extends groupdav_handler
}
if ($this->debug) error_log(__METHOD__."($path,".array2string($options).",,$user,$id) filter=".array2string($filter));
// check if we have to return the full calendar data or just the etag's
if (!($filter['address_data'] = $options['props'] == 'all' && $options['root']['ns'] == groupdav::CARDDAV) && is_array($options['props']))
// check if we have to return the full contact data or just the etag's
if (!($filter['address_data'] = $options['props'] == 'all' &&
$options['root']['ns'] == groupdav::CARDDAV) && is_array($options['props']))
{
foreach($options['props'] as $prop)
{
@ -108,7 +110,7 @@ class addressbook_groupdav extends groupdav_handler
}
}
// return iterator, calling ourself to return result in chunks
$files['files'] = new groupdav_propfind_iterator($this,$filter,$files['files']);
$files['files'] = new groupdav_propfind_iterator($this,$path,$filter,$files['files']);
return true;
}
@ -116,11 +118,12 @@ class addressbook_groupdav extends groupdav_handler
/**
* Callback for profind interator
*
* @param string $path
* @param array $filter
* @param array|boolean $start=false false=return all or array(start,num)
* @return array with "files" array with values for keys path and props
*/
function &propfind_callback(array $filter,$start=false)
function &propfind_callback($path,array $filter,$start=false)
{
$starttime = microtime(true);
@ -129,7 +132,6 @@ class addressbook_groupdav extends groupdav_handler
$handler = self::_get_handler();
}
unset($filter['address_data']);
$files = array();
// we query etag and modified, as LDAP does not have the strong sql etag
if (($contacts =& $this->bo->search(array(),$address_data ? false : array('id','uid','etag','modified'),'contact_id','','',False,'AND',$start,$filter)))
@ -138,27 +140,27 @@ class addressbook_groupdav extends groupdav_handler
{
$props = array(
HTTP_WebDAV_Server::mkprop('getetag',$this->get_etag($contact)),
HTTP_WebDAV_Server::mkprop('getcontenttype', 'text/x-vcard'),
HTTP_WebDAV_Server::mkprop('getcontenttype', 'text/vcard'),
// getlastmodified and getcontentlength are required by WebDAV and Cadaver eg. reports 404 Not found if not set
HTTP_WebDAV_Server::mkprop('getlastmodified', $contact['modified']),
);
if ($address_data)
{
$content = $handler->getVCard($contact,$this->charset,false);
$content = $handler->getVCard($contact['id'],$this->charset,false);
$props[] = HTTP_WebDAV_Server::mkprop('getcontentlength',bytes($content));
$props[] = HTTP_WebDAV_Server::mkprop(groupdav::CARDDAV,'address-data',$content);
$props[] = HTTP_WebDAV_Server::mkprop(groupdav::CARDDAV,'address-data',$content,true);
}
else
{
$props[] = HTTP_WebDAV_Server::mkprop('getcontentlength', ''); // expensive to calculate and no CalDAV client uses it
}
$files[] = array(
'path' => self::get_path($contact),
'path' => $path.self::get_path($contact),
'props' => $props,
);
}
}
if ($this->debug) error_log(__METHOD__.'('.array2string($filter).','.array2string($start).") took ".(microtime(true) - $starttime).' to return '.count($files).' items');
if ($this->debug) error_log(__METHOD__."($path,".array2string($filter).','.array2string($start).") took ".(microtime(true) - $starttime).' to return '.count($files).' items');
return $files;
}
@ -265,22 +267,70 @@ class addressbook_groupdav extends groupdav_handler
*/
function put(&$options,$id,$user=null)
{
$ok = $this->_common_get_put_delete('PUT',$options,$id);
if (!is_null($ok) && !is_array($ok))
{
return $ok;
}
$handler = self::_get_handler();
$contact = $handler->vcardtoegw($options['content']);
if ($this->debug) error_log(__METHOD__.'('.array2string($options).",$id,$user)");
if (!is_null($ok))
$oldContact = $this->_common_get_put_delete('PUT',$options,$id);
if (!is_null($oldContact) && !is_array($oldContact))
{
$contact['id'] = $ok['id'];
// dont allow the client to overwrite certain values
$contact['uid'] = $ok['uid'];
$contact['owner'] = $ok['owner'];
$contact['private'] = $ok['private'];
return $oldContact;
}
$handler = self::_get_handler();
$vCard = htmlspecialchars_decode($options['content']);
if (is_array($oldContact))
{
$contactId = $oldContact['id'];
$retval = true;
}
else
{
// new entry?
if (($foundContacts = $handler->search($vCard)))
{
if (($contactId = array_shift($foundContacts)) &&
($oldContact = $this->bo->read($contactId)))
{
$retval = '301 Moved Permanently';
}
else
{
// to be safe
$contactId = -1;
$retval = '201 Created';
}
}
else
{
// new entry
$contactId = -1;
$retval = '201 Created';
}
}
$contact = $handler->vcardtoegw($vCard);
if (is_array($contact['category']))
{
$contact['category'] = implode(',',$this->bo->find_or_add_categories($contact['category'], $contactId));
}
elseif ($contactId > 0)
{
$contact['category'] = $oldContact['category'];
}
if (is_array($oldContact))
{
$contact['id'] = $oldContact['id'];
// dont allow the client to overwrite certain values
$contact['uid'] = $oldContact['uid'];
$contact['owner'] = $oldContact['owner'];
$contact['private'] = $oldContact['private'];
}
else
{
$contact['owner'] = $user;
}
if ($this->http_if_match) $contact['etag'] = self::etag2value($this->http_if_match);
if (!($save_ok = $this->bo->save($contact)))
@ -292,24 +342,26 @@ class addressbook_groupdav extends groupdav_handler
}
return false;
}
if (!isset($contact['etag']))
{
$contact = $this->read($contact['id']);
$contact = $this->read($save_ok);
}
header('ETag: '.$this->get_etag($contact));
if (is_null($ok))
if ($retval !== true)
{
header($h='Location: '.$this->base_uri.self::get_path($contact));
if ($this->debug) error_log(__METHOD__."($method,,$id) header('$h'): 201 Created");
return '201 Created';
$path = preg_replace('|(.*)/[^/]*|', '\1/', $options['path']);
header($h='Location: '.$this->base_uri.$path.self::get_path($contact));
if ($this->debug) error_log(__METHOD__."($method,,$id) header('$h'): $retval");
return $retval;
}
return true;
}
/**
* Query ctag for addressbook
*
*
* @return string
*/
public function getctag($path,$user)
@ -319,10 +371,40 @@ class addressbook_groupdav extends groupdav_handler
if ($user && $path != '/addressbook/') $filter['contact_owner'] = $user;
// should we hide the accounts addressbook
if ($GLOBALS['egw_info']['user']['preferences']['addressbook']['hide_accounts']) $filter['account_id'] = null;
$result = $this->bo->search(array(),'MAX(contact_modified) AS contact_modified','','','','','',$filter);
return '"'.$result[0]['modified'].'"';
$ctag = 'EGw-'.$result[0]['modified'].'-wGE';
return $ctag;
}
/**
* Add the privileges of the current user
*
* @param array $props=array() regular props by the groupdav handler
* @return array
*/
static function current_user_privilege_set(array $props=array())
{
$props[] = HTTP_WebDAV_Server::mkprop(groupdav::DAV,'current-user-privilege-set',
array(HTTP_WebDAV_Server::mkprop(groupdav::DAV,'privilege',
array(//HTTP_WebDAV_Server::mkprop(groupdav::DAV,'all',''),
HTTP_WebDAV_Server::mkprop(groupdav::DAV,'read',''),
HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'read-free-busy',''),
//HTTP_WebDAV_Server::mkprop(groupdav::DAV,'read-current-user-privilege-set',''),
HTTP_WebDAV_Server::mkprop(groupdav::DAV,'bind',''),
HTTP_WebDAV_Server::mkprop(groupdav::DAV,'unbind',''),
HTTP_WebDAV_Server::mkprop(groupdav::DAV,'schedule-post',''),
HTTP_WebDAV_Server::mkprop(groupdav::DAV,'schedule-post-vevent',''),
HTTP_WebDAV_Server::mkprop(groupdav::DAV,'schedule-respond',''),
HTTP_WebDAV_Server::mkprop(groupdav::DAV,'schedule-respond-vevent',''),
HTTP_WebDAV_Server::mkprop(groupdav::DAV,'schedule-deliver',''),
HTTP_WebDAV_Server::mkprop(groupdav::DAV,'schedule-deliver-vevent',''),
HTTP_WebDAV_Server::mkprop(groupdav::DAV,'write',''),
HTTP_WebDAV_Server::mkprop(groupdav::DAV,'write-properties',''),
HTTP_WebDAV_Server::mkprop(groupdav::DAV,'write-content',''),
))));
return $props;
}
/**
@ -342,12 +424,19 @@ class addressbook_groupdav extends groupdav_handler
* </supported-report>
* </D:supported-report-set>
* @link http://www.mail-archive.com/calendarserver-users@lists.macosforge.org/msg01156.html
*
*
* @param array $props=array() regular props by the groupdav handler
* @param string $displayname
* @param string $base_uri=null base url of handler
* @return array
*/
static function extra_properties(array $props=array())
static function extra_properties(array $props=array(), $displayname, $base_uri=null)
{
// addressbook description
$displayname = $GLOBALS['egw']->translation->convert(lang('Addressbook of') . ' ' .
$displayname,
$GLOBALS['egw']->translation->charset(),'utf-8');
$props[] = HTTP_WebDAV_Server::mkprop(groupdav::CARDDAV,'addressbook-description',$displayname);
// supported reports (required property for CardDAV)
$props[] = HTTP_WebDAV_Server::mkprop('supported-report-set',array(
HTTP_WebDAV_Server::mkprop('supported-report',array(
@ -357,6 +446,7 @@ class addressbook_groupdav extends groupdav_handler
HTTP_WebDAV_Server::mkprop('report',
HTTP_WebDAV_Server::mkprop(groupdav::CARDDAV,'addressbook-multiget','')))),
));
//$props = self::current_user_privilege_set($props);
return $props;
}
@ -390,7 +480,7 @@ class addressbook_groupdav extends groupdav_handler
{
return '412 Precondition Failed';
}
return $ok;
//return $ok;
}
/**

View File

@ -339,7 +339,7 @@ class addressbook_so
/**
* Read all customfields of the given id's
*
* @param int/array $ids
* @param int|array $ids
* @param array $field_names=null custom fields to read, default all
* @return array id => name => value
*/
@ -374,7 +374,7 @@ class addressbook_so
/**
* Read all distributionlists of the given id's
*
* @param int/array $ids
* @param int|array $ids
* @return array id => name => value
*/
function read_distributionlist($ids, $dl_allowed=array())
@ -544,8 +544,8 @@ class addressbook_so
/**
* reads contact data including custom fields
*
* @param int/string $contact_id contact_id or 'a'.account_id
* @return array/boolean data if row could be retrived else False
* @param int|string $contact_id contact_id or 'a'.account_id
* @return array|boolean data if row could be retrived else False
*/
function read($contact_id)
{
@ -581,10 +581,10 @@ class addressbook_so
*
* '*' and '?' are replaced with sql-wildcards '%' and '_'
*
* @param array/string $criteria array of key and data cols, OR string to search over all standard search fields
* @param boolean/string $only_keys=true True returns only keys, False returns all cols. comma seperated list of keys to return
* @param array|string $criteria array of key and data cols, OR string to search over all standard search fields
* @param boolean|string $only_keys=true True returns only keys, False returns all cols. comma seperated list of keys to return
* @param string $order_by='' fieldnames + {ASC|DESC} separated by colons ',', can also contain a GROUP BY (if it contains ORDER BY)
* @param string/array $extra_cols='' string or array of strings to be added to the SELECT, eg. "count(*) as num"
* @param string|array $extra_cols='' string or array of strings to be added to the SELECT, eg. "count(*) as num"
* @param string $wildcard='' appended befor and after each criteria
* @param boolean $empty=false False=empty criteria are ignored in query, True=empty have to be empty in row
* @param string $op='AND' defaults to 'AND', can be set to 'OR' too, then criteria's are OR'ed together
@ -985,7 +985,7 @@ class addressbook_so
/**
* Deletes a distribution list (incl. it's members)
*
* @param int/array $list list_id(s)
* @param int|array $list list_id(s)
* @return number of members deleted or false if list does not exist
*/
function delete_list($list)
@ -1011,7 +1011,7 @@ class addressbook_so
/**
* Check if distribution lists are availible for a given addressbook
*
* @param int/string $owner='' addressbook (eg. 0 = accounts), default '' = "all" addressbook (uses the main backend)
* @param int|string $owner='' addressbook (eg. 0 = accounts), default '' = "all" addressbook (uses the main backend)
* @return boolean
*/
function lists_available($owner='')

View File

@ -123,7 +123,7 @@ class addressbook_vcal extends addressbook_bo
*/
function addVCard($_vcard, $_abID=null, $merge=false)
{
if (!($contact = $this->vcardtoegw($_vcard, $_abID))) return false;
if (!($contact = $this->vcardtoegw($_vcard))) return false;
if ($_abID)
{
@ -145,6 +145,15 @@ class addressbook_vcal extends addressbook_bo
{
$contact['account_id'] = $old_contact['account_id'];
}
if (is_array($contact['category']))
{
$contact['category'] = implode(',',$this->find_or_add_categories($contact['category'], $_abID));
}
else
{
// restore from orignal
$contact['category'] = $old_contact['category'];
}
}
}
// update entry
@ -157,6 +166,10 @@ class addressbook_vcal extends addressbook_bo
{
$contact['owner'] = $GLOBALS['egw_info']['user']['account_primary_group'];
}
if (is_array($contact['category']))
{
$contact['category'] = implode(',',$this->find_or_add_categories($contact['category'], -1));
}
}
return $this->save($contact);
}
@ -452,8 +465,13 @@ class addressbook_vcal extends addressbook_bo
{
$result = array();
if (($contact = $this->vcardtoegw($_vcard, $contentID)))
if (($contact = $this->vcardtoegw($_vcard)))
{
if (is_array($contact['category']))
{
$contact['category'] = implode(',',$this->find_or_add_categories($contact['category'],
$contentID ? $contentID : -1));
}
if ($contentID)
{
$contact['id'] = $contentID;
@ -472,7 +490,7 @@ class addressbook_vcal extends addressbook_bo
if (is_array($_supportedFields)) $this->supportedFields = $_supportedFields;
}
function vcardtoegw($_vcard, $_abID=null)
function vcardtoegw($_vcard)
{
// the horde class does the charset conversion. DO NOT CONVERT HERE.
// be as flexible as possible
@ -886,7 +904,7 @@ class addressbook_vcal extends addressbook_bo
break;
case 'cat_id':
$contact[$fieldName] = implode(',',$this->find_or_add_categories($vcardValues[$vcardKey]['values'], $_abID));
$contact[$fieldName] = $vcardValues[$vcardKey]['values'];
break;
case 'jpegphoto':

View File

@ -145,9 +145,6 @@ class calendar_boupdate extends calendar_bo
else
{
$old_event = $this->read((int)$event['id'],null,$ignore_acl);
// if no participants are set, set them from the old event, as we might need them to update recuring events
if (!isset($event['participants'])) $event['participants'] = $old_event['participants'];
//echo "old $event[id]="; _debug_array($old_event);
}
// do we need to check, if user is allowed to invite the invited participants
@ -185,7 +182,7 @@ class calendar_boupdate extends calendar_bo
self::has_cat_right(self::CAT_ACL_STATUS,$cat_id,$this->user) === false &&
(!$old_event || $old_event['start'] != $event['start'] || $old_event['end'] != $event['end']))
{
foreach($event['participants'] as $uid => $status)
foreach((array)$event['participants'] as $uid => $status)
{
calendar_so::split_status($status,$q,$r);
if ($status != 'U')
@ -658,8 +655,6 @@ class calendar_boupdate extends calendar_bo
}
}
}
//$currentPrefs = CreateObject('phpgwapi.preferences',$GLOBALS['egw_info']['user']['account_id']);
//$user_prefs = $currentPrefs->read_repository();
$user_prefs = $GLOBALS['egw_info']['user']['preferences'];
foreach($to_notify as $userid => $statusid)
{
@ -676,8 +671,9 @@ class calendar_boupdate extends calendar_bo
continue; // dont notify rejected participants or groups
}
if($userid != $GLOBALS['egw_info']['user']['account_id'] ||
($userid == $GLOBALS['egw_info']['user']['account_id'] && $user_prefs['calendar']['receive_own_updates']==1) ||
if($userid != $GLOBALS['egw_info']['user']['account_id'] ||
($userid == $GLOBALS['egw_info']['user']['account_id'] &&
$user_prefs['calendar']['receive_own_updates']==1) ||
$msg_type == MSG_ALARM)
{
$preferences = CreateObject('phpgwapi.preferences',$userid);
@ -1084,7 +1080,9 @@ class calendar_boupdate extends calendar_bo
"($cal_id, $uid, $status, $recur_date)");
}
$old_event = $this->read($cal_id, $recur_date, false, 'server');
if (($Ok = $this->so->set_status($cal_id,is_numeric($uid)?'u':$uid[0],is_numeric($uid)?$uid:substr($uid,1),$status,$recur_date ? $this->date2ts($recur_date,true) : 0,$role)))
if (($Ok = $this->so->set_status($cal_id,is_numeric($uid)?'u':$uid[0],
is_numeric($uid)?$uid:substr($uid,1),$status,
$recur_date?$this->date2ts($recur_date,true):0,$role)))
{
if ($updateTS) $GLOBALS['egw']->contenthistory->updateTimeStamp('calendar',$cal_id,'modify',time());
@ -1430,27 +1428,24 @@ class calendar_boupdate extends calendar_bo
}
$cat_id_list = array();
if(is_array($catname_list) && count($catname_list) > 0)
foreach ((array)$catname_list as $cat_name)
{
foreach ($catname_list as $cat_name)
$cat_name = trim($cat_name);
$cat_id = $this->categories->name2id($cat_name, 'X-');
if (!$cat_id)
{
$cat_name = trim($cat_name);
$cat_id = $this->categories->name2id($cat_name, 'X-');
if (!$cat_id)
// some SyncML clients (mostly phones) add an X- to the category names
if (strncmp($cat_name, 'X-', 2) == 0)
{
// some SyncML clients (mostly phones) add an X- to the category names
if (strncmp($cat_name, 'X-', 2) == 0)
{
$cat_name = substr($cat_name, 2);
}
$cat_id = $this->categories->add(array('name' => $cat_name, 'descr' => $cat_name, 'access' => 'private'));
$cat_name = substr($cat_name, 2);
}
$cat_id = $this->categories->add(array('name' => $cat_name, 'descr' => $cat_name, 'access' => 'private'));
}
if ($cat_id)
{
$cat_id_list[] = $cat_id;
}
if ($cat_id)
{
$cat_id_list[] = $cat_id;
}
}
@ -1501,7 +1496,6 @@ class calendar_boupdate extends calendar_bo
{
$matchingEvents = array();
$query = array();
$recur_date = 0;
if ($this->log)
{
@ -1511,16 +1505,12 @@ class calendar_boupdate extends calendar_bo
if ($filter == 'master')
{
if (isset($event['recurrence']))
{
$recur_date = $this->date2usertime($event['recurrence']);
}
elseif (isset($event['start']))
{
$recur_date = $this->date2usertime($event['start']);
}
$query[] = 'recur_type!='. MCAL_RECUR_NONE;
$query['cal_recurrence'] = 0;
}
if (!isset($event['recurrence'])) $event['recurrence'] = 0;
if ($event['id'])
{
if ($this->log)
@ -1528,38 +1518,44 @@ class calendar_boupdate extends calendar_bo
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
'(' . $event['id'] . ")[EventID]");
}
if (($egwEvent = $this->read($event['id'], $recur_date, false, 'server')))
if (($egwEvent = $this->read($event['id'], 0, false, 'server')))
{
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
'()[FOUND]:' . array2string($egwEvent));
}
// Just a simple consistency check
if ($filter == 'exact' ||
$filter == 'master' && $egwEvent['recur_type'] != MCAL_RECUR_NONE ||
$filter != 'master' && strpos($egwEvent['title'], $event['title']) === 0)
if ($egwEvent['recur_type'] != MCAL_RECUR_NONE &&
(empty($event['uid']) || $event['uid'] == $egwEvent['uid']))
{
$retval = $egwEvent['id'];
if ($egwEvent['recur_type'] != MCAL_RECUR_NONE &&
$event['recur_type'] == MCAL_RECUR_NONE && $event['recurrence'] != 0)
if ($filter == 'master')
{
$retval .= ':' . (int)$event['recurrence'];
$matchingEvents[] = $egwEvent['id']; // we found the master
}
$matchingEvents[] = $retval;
return $matchingEvents;
if ($event['recur_type'] == $egwEvent['recur_type'])
{
$matchingEvents[] = $egwEvent['id']; // we found the event
}
elseif ($event['recur_type'] == MCAL_RECUR_NONE &&
$event['recurrence'] != 0)
{
$exceptions = $this->so->get_recurrence_exceptions($egwEvent, $event['tzid']);
if (in_array($event['recurrence'], $exceptions))
{
$matchingEvents[] = $egwEvent['id'] . ':' . (int)$event['recurrence'];
}
}
} elseif ($event['recur_type'] == $egwEvent['recur_type'] &&
$filter != 'master' &&
strpos($egwEvent['title'], $event['title']) === 0)
{
$matchingEvents[] = $egwEvent['id']; // we found the event
}
}
if ($filter == 'exact') return array();
if (!empty($matchingEvents) || $filter == 'exact') return $matchingEvents;
}
unset($event['id']);
if ($filter == 'master')
{
$query[] = 'recur_type!='. MCAL_RECUR_NONE;
$query['cal_recurrence'] = 0;
}
// only query calendars of users, we have READ-grants from
$users = array();
foreach(array_keys($this->grants) as $user)
@ -1611,6 +1607,7 @@ class calendar_boupdate extends calendar_bo
}
}
}
if ($filter != 'master' && ($filter != 'exact' || empty($event['uid'])))
{
if (isset($event['whole_day']) && $event['whole_day'])
@ -1661,10 +1658,6 @@ class calendar_boupdate extends calendar_bo
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
'(' . $event['uid'] . ')[EventUID]');
}
if ($filter != 'master' && isset($event['recurrence']))
{
$query['cal_recurrence'] = $event['recurrence'];
}
}
if ($this->log)
@ -1694,19 +1687,41 @@ class calendar_boupdate extends calendar_bo
}
if (in_array($egwEvent['id'], $matchingEvents)) continue;
if (in_array($filter, array('exact', 'master')) && !empty($event['uid']))
{
$matchingEvents[] = $egwEvent['id']; // UID found
if ($filter = 'master') break;
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'])))
{
$egwEvent['tzid'] = egw_time::$server_timezone->getName();
}
if (in_array($filter, array('exact', 'master')) && !empty($event['uid']))
{
// UID found
if ($egwEvent['recurrence'] == $event['recurrence'])
{
// We found the exact match
$matchingEvents[] = $egwEvent['id'];
break;
}
if (!$egwEvent['recurrence'] && $event['recurrence'])
{
// We found the master
if ($filter == 'master')
{
$matchingEvents[] = $egwEvent['id'];
break;
}
$exceptions = $this->so->get_recurrence_exceptions($egwEvent, $event['tzid']);
if (in_array($event['recurrence'], $exceptions))
{
// We found a pseudo exception
$matchingEvents[] = $egwEvent['id'] . ':' . (int)$event['recurrence'];
break;
}
}
continue;
}
if (!empty($event['uid']) && $filter == 'exact') break;
// check times
if ($filter != 'relax')
@ -2129,4 +2144,4 @@ class calendar_boupdate extends calendar_bo
}
}
}
}
}

View File

@ -58,10 +58,11 @@ class calendar_groupdav extends groupdav_handler
* @param string $app 'calendar', 'addressbook' or 'infolog'
* @param int $debug=null debug-level to set
* @param string $base_uri=null base url of handler
* @param string $principalURL=null principal url of handler
*/
function __construct($app,$debug=null, $base_uri=null)
function __construct($app,$debug=null, $base_uri=null, $principalURL=null)
{
parent::__construct($app,$debug,$base_uri);
parent::__construct($app,$debug,$base_uri,$principalURL);
$this->bo = new calendar_boupdate();
}
@ -83,7 +84,7 @@ class calendar_groupdav extends groupdav_handler
if (!is_array($event)) $event = $this->bo->read($event);
$name = $event[self::PATH_ATTRIBUTE];
}
return '/calendar/'.$name.'.ics';
return $name.'.ics';
}
/**
@ -171,7 +172,7 @@ class calendar_groupdav extends groupdav_handler
'text/calendar; charset=utf-8; component=VEVENT' : 'text/calendar'),
// getlastmodified and getcontentlength are required by WebDAV and Cadaver eg. reports 404 Not found if not set
HTTP_WebDAV_Server::mkprop('getlastmodified', $event['modified']),
HTTP_WebDAV_Server::mkprop('resourcetype',''), // iPhone requires that attribute!
HTTP_WebDAV_Server::mkprop('resourcetype',''), // DAVKit requires that attribute!
);
//error_log(__FILE__ . __METHOD__ . "Calendar Data : $calendar_data");
if ($calendar_data)
@ -185,7 +186,7 @@ class calendar_groupdav extends groupdav_handler
$props[] = HTTP_WebDAV_Server::mkprop('getcontentlength', ''); // expensive to calculate and no CalDAV client uses it
}
$files['files'][] = array(
'path' => self::get_path($event),
'path' => $path.self::get_path($event),
'props' => $props,
);
}
@ -228,7 +229,7 @@ class calendar_groupdav extends groupdav_handler
return false; // return nothing for now, todo: check if we can pass it on to the infolog handler
// todos are handled by the infolog handler
//$infolog_handler = new groupdav_infolog();
//return $infolog_handler->propfind($path,$options,$files,$user,$method);
//return $infolog_handler->propfind($options['path'],$options,$options['files'],$user,$method);
case 'VCALENDAR':
case 'VEVENT':
break; // that's our default anyway
@ -434,34 +435,78 @@ class calendar_groupdav extends groupdav_handler
function put(&$options,$id,$user=null)
{
if ($this->debug) error_log(__METHOD__."($id, $user)".print_r($options,true));
$return_no_access=true; // as handled by importVCal anyway and allows it to set the status for participants
$event = $this->_common_get_put_delete('PUT',$options,$id,$return_no_access);
if (!is_null($event) && !is_array($event))
$oldEvent = $this->_common_get_put_delete('PUT',$options,$id,$return_no_access);
if (!is_null($oldEvent) && !is_array($oldEvent))
{
if ($this->debug) error_log(__METHOD__.print_r($event,true).function_backtrace());
return $event;
if ($this->debug) error_log(__METHOD__.print_r($oldEvent,true).function_backtrace());
return $oldEvent;
}
if (is_null($oldEvent) && !$this->bo->check_perms(EGW_ACL_ADD, 0, $user))
{
// we have no add permission on this user's calendar
if ($this->debug) error_log(__METHOD__."(,$user) we have not enough rights on this calendar");
return '403 Forbidden';
}
$handler = $this->_get_handler();
$vCalendar = htmlspecialchars_decode($options['content']);
if (!is_numeric($id) && ($foundEntries = $handler->find_event($options['content'], 'exact')))
if (is_array($oldEvent))
{
$id = array_shift($foundEntries);
$eventId = $oldEvent['id'];
if ($return_no_access)
{
$retval = true;
}
else
{
// let lightning think the event is added
$retval = '201 Created';
}
}
else
{
// new entry?
if (($foundEvents = $handler->search($vCalendar)))
{
if (($eventId = array_shift($foundEvents)) &&
(list($eventId) = explode(':', $eventId)) &&
($oldEvent = $this->bo->read($eventId)))
{
$retval = '301 Moved Permanently';
}
else
{
// to be safe
$eventId = -1;
$retval = '201 Created';
}
}
else
{
// new entry
$eventId = -1;
$retval = '201 Created';
}
}
if (!($cal_id = $handler->importVCal($options['content'],is_numeric($id) ? $id : -1,
self::etag2value($this->http_if_match))))
if (!($cal_id = $handler->importVCal($vCalendar, $eventId,
self::etag2value($this->http_if_match), false, 0, $this->principalURL, $user)))
{
if ($this->debug) error_log(__METHOD__."(,$id) importVCal($options[content]) returned false");
return '403 Forbidden';
}
header('ETag: '.$this->get_etag($cal_id));
if (is_null($event) || !$return_no_access) // let lightning think the event is added
if ($retval !== true)
{
if ($this->debug) error_log(__METHOD__."(,$id,$user) cal_id=$cal_id, is_null(\$event)=".(int)is_null($event));
header('Location: '.$this->base_uri.self::get_path($cal_id));
return '201 Created';
$path = preg_replace('|(.*)/[^/]*|', '\1/', $options['path']);
if ($this->debug) error_log(__METHOD__."(,$id,$user) cal_id=$cal_id: $retval");
header('Location: '.$this->base_uri.$path.self::get_path($cal_id));
return $retval;
}
return true;
}
@ -574,8 +619,7 @@ class calendar_groupdav extends groupdav_handler
*/
function read($id)
{
//$cal_read = $this->bo->read($id,null,false,'server');//njv: do we actually get anything
if ($this->debug > 1) error_log("bo-ical read :$id:");//njv:
if ($this->debug > 1) error_log("bo-ical read :$id:");
return $this->bo->read($id,null,false,'server');
}
@ -616,12 +660,12 @@ class calendar_groupdav extends groupdav_handler
{
if ($recurrence['reference']) // ignore series master
{
$etag .= ':'.substr($this->get_etag($recurrence),1,-1);
$etag .= ':'.substr($this->get_etag($recurrence),4,-4);
}
}
}
//error_log(__METHOD__ . "($entry[id] ($entry[etag]): $entry[title] --> etag=$etag");
return '"'.$etag.'"';
return 'EGw-'.$etag.'-wGE';
}
/**
@ -637,23 +681,74 @@ class calendar_groupdav extends groupdav_handler
}
/**
* Add extra properties for calendar collections
* Add the privileges of the current user
*
* @param array $props=array() regular props by the groupdav handler
* @return array
*/
static function extra_properties(array $props=array())
static function current_user_privilege_set(array $props=array())
{
// calendaring URL of the current user
$props[] = HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-home-set',$_SERVER['SCRIPT_NAME'].'/');
$props[] = HTTP_WebDAV_Server::mkprop(groupdav::DAV,'current-user-privilege-set',
array(HTTP_WebDAV_Server::mkprop(groupdav::DAV,'privilege',
array(//HTTP_WebDAV_Server::mkprop(groupdav::DAV,'all',''),
HTTP_WebDAV_Server::mkprop(groupdav::DAV,'read',''),
HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'read-free-busy',''),
//HTTP_WebDAV_Server::mkprop(groupdav::DAV,'read-current-user-privilege-set',''),
HTTP_WebDAV_Server::mkprop(groupdav::DAV,'bind',''),
HTTP_WebDAV_Server::mkprop(groupdav::DAV,'unbind',''),
HTTP_WebDAV_Server::mkprop(groupdav::DAV,'schedule-post',''),
HTTP_WebDAV_Server::mkprop(groupdav::DAV,'schedule-post-vevent',''),
HTTP_WebDAV_Server::mkprop(groupdav::DAV,'schedule-respond',''),
HTTP_WebDAV_Server::mkprop(groupdav::DAV,'schedule-respond-vevent',''),
HTTP_WebDAV_Server::mkprop(groupdav::DAV,'schedule-deliver',''),
HTTP_WebDAV_Server::mkprop(groupdav::DAV,'schedule-deliver-vevent',''),
HTTP_WebDAV_Server::mkprop(groupdav::DAV,'write',''),
HTTP_WebDAV_Server::mkprop(groupdav::DAV,'write-properties',''),
HTTP_WebDAV_Server::mkprop(groupdav::DAV,'write-content',''),
))));
return $props;
}
/**
* Add extra properties for calendar collections
*
* @param array $props=array() regular props by the groupdav handler
* @param string $displayname
* @param string $base_uri=null base url of handler
* @return array
*/
static function extra_properties(array $props=array(), $displayname, $base_uri=null)
{
// calendar description
$props[] = HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-description',$displayname);
// BOX URLs of the current user
/*
$props[] = HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'schedule-inbox-URL',
array(HTTP_WebDAV_Server::mkprop(self::DAV,'href',$base_uri.'/calendar/')));
$props[] = HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'schedule-outbox-URL',
array(HTTP_WebDAV_Server::mkprop(groupdav::DAV,'href',$base_uri.'/calendar/')));
$props[] = HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'schedule-default-calendar-URL',
array(HTTP_WebDAV_Server::mkprop(groupdav::DAV,'href',$base_uri.'/calendar/')));
$props[] = HTTP_WebDAV_Server::mkprop(groupdav::CALENDARSERVER,'dropbox-home-URL',
array(HTTP_WebDAV_Server::mkprop(groupdav::DAV,'href',$base_uri.'/calendar/')));
$props[] = HTTP_WebDAV_Server::mkprop(groupdav::CALENDARSERVER,'notifications-URL',
array(HTTP_WebDAV_Server::mkprop(groupdav::DAV,'href',$base_uri.'/calendar/')));
*/
// email of the current user, see caldav-sheduling draft
$props[] = HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-user-address-set','MAILTO:'.$GLOBALS['egw_info']['user']['email']);
$props[] = HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-user-address-set',array(
HTTP_WebDAV_Server::mkprop('href','MAILTO:'.$GLOBALS['egw_info']['user']['email'])));
// supported components, currently only VEVENT
$props[] = $sc = HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'supported-calendar-component-set',array(
$props[] = HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'supported-calendar-component-set',array(
HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'comp',array('name' => 'VEVENT')),
// HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'comp',array('name' => 'VTODO')), // not yet supported
// HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'comp',array('name' => 'VTODO')), // not yet supported
));
$props[] = HTTP_WebDAV_Server::mkprop('supported-report-set',array(
HTTP_WebDAV_Server::mkprop('supported-report',array(
HTTP_WebDAV_Server::mkprop('report',
HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-multiget'))))));
//$props = self::current_user_privilege_set($props);
return $props;
}

View File

@ -53,6 +53,7 @@ class calendar_ical extends calendar_boupdate
'DECLINED' => 'R',
'TENTATIVE' => 'T',
'DELEGATED' => 'D',
'X-UNINVITED' => 'G', // removed
);
/**
@ -148,6 +149,13 @@ class calendar_ical extends calendar_boupdate
*/
var $vCalendar;
/**
* Addressbook BO instance
*
* @var array
*/
var $addressbook;
/**
* Set Logging
*
@ -168,21 +176,29 @@ class calendar_ical extends calendar_boupdate
if ($this->log) $this->logfile = $GLOBALS['egw_info']['server']['temp_dir']."/log-vcal";
$this->clientProperties = $_clientProperties;
$this->vCalendar = new Horde_iCalendar;
$this->addressbook = new addressbook_bo;
}
/**
* Exports one calendar event to an iCalendar item
*
* @param int/array $events (array of) cal_id or array of the events
* @param int|array $events (array of) cal_id or array of the events
* @param string $version='1.0' could be '2.0' too
* @param string $method='PUBLISH'
* @param int $recur_date=0 if set export the next recurrence at or after the timestamp,
* default 0 => export whole series (or events, if not recurring)
* @return string/boolean string with iCal or false on error (eg. no permission to read the event)
* @param string $principalURL='' Used for CalDAV exports
* @return string|boolean string with iCal or false on error (eg. no permission to read the event)
*/
function &exportVCal($events, $version='1.0', $method='PUBLISH', $recur_date=0)
function &exportVCal($events, $version='1.0', $method='PUBLISH', $recur_date=0, $principalURL='')
{
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
"($version, $method, $recur_date, $principalURL)\n",
3, $this->logfile);
}
$egwSupportedFields = array(
'CLASS' => 'public',
'SUMMARY' => 'title',
@ -222,7 +238,7 @@ class calendar_ical extends calendar_boupdate
$vtimezones_added = array();
foreach ($events as $event)
{
$mailtoOrganizer = false;
$organizerURL = '';
$organizerCN = false;
$recurrence = $this->date2usertime($recur_date);
$tzid = null;
@ -453,17 +469,17 @@ class calendar_ical extends calendar_boupdate
foreach ((array)$event['participants'] as $uid => $status)
{
if (!($info = $this->resource_info($uid))) continue;
$mailtoParticipant = $info['email'] ? 'MAILTO:'.$info['email'] : '';
$participantURL = $info['email'] ? 'MAILTO:'.$info['email'] : '';
$participantCN = '"' . ($info['cn'] ? $info['cn'] : $info['name']) . '"';
calendar_so::split_status($status, $quantity, $role);
if ($role == 'CHAIR' && $uid != $this->user)
{
$mailtoOrganizer = $mailtoParticipant;
$organizerURL = $participantURL;
$organizerCN = $participantCN;
if ($status == 'U') continue; // saved ORGANIZER
$organizerUID = $uid;
}
// RB: MAILTO href contains only the email-address, NO cn!
$attributes['ATTENDEE'][] = $mailtoParticipant;
$attributes['ATTENDEE'][] = $participantURL;
// RSVP={TRUE|FALSE} // resonse expected, not set in eGW => status=U
$rsvp = $status == 'U' ? 'TRUE' : 'FALSE';
// PARTSTAT={NEEDS-ACTION|ACCEPTED|DECLINED|TENTATIVE|DELEGATED|COMPLETED|IN-PROGRESS} everything from delegated is NOT used by eGW atm.
@ -511,20 +527,32 @@ class calendar_ical extends calendar_boupdate
case 'ORGANIZER':
// according to iCalendar standard, ORGANIZER not used for events in the own calendar
if (!$organizerCN &&
($event['owner'] != $this->user
|| $this->productManufacturer != 'groupdav'
|| $this->productName == 'kde'))
if (!$organizerCN)
{
$mailtoOrganizer = $GLOBALS['egw']->accounts->id2name($event['owner'],'account_email');
$mailtoOrganizer = $mailtoOrganizer ? 'MAILTO:'.$mailtoOrganizer : '';
$organizerURL = $GLOBALS['egw']->accounts->id2name($event['owner'],'account_email');
$organizerURL = $organizerURL ? 'MAILTO:'.$organizerURL : '';
$organizerCN = '"' . trim($GLOBALS['egw']->accounts->id2name($event['owner'],'account_firstname')
. ' ' . $GLOBALS['egw']->accounts->id2name($event['owner'],'account_lastname')) . '"';
. ' ' . $GLOBALS['egw']->accounts->id2name($event['owner'],'account_lastname')) . '"';
$organizerUID = $event['owner'];
if (!isset($event['participants'][$event['owner']]))
{
$attributes['ATTENDEE'][] = $organizerURL;
$parameters['ATTENDEE'][] = array(
'CN' => $organizerCN,
'ROLE' => 'CHAIR',
'PARTSTAT' => 'DELEGATED',
'CUTYPE' => 'INDIVIDUAL',
'RSVP' => 'FALSE',
'X-EGROUPWARE-UID' => $event['owner'],
);
}
}
if ($organizerCN)
if ($this->productManufacturer != 'groupdav'
|| !$this->check_perms(EGW_ACL_EDIT,$event['id']))
{
$attributes['ORGANIZER'] = $mailtoOrganizer;
$attributes['ORGANIZER'] = $organizerURL;
$parameters['ORGANIZER']['CN'] = $organizerCN;
$parameters['ORGANIZER']['X-EGROUPWARE-UID'] = $organizerUID;
}
break;
@ -950,13 +978,15 @@ class calendar_ical extends calendar_boupdate
* @param boolean $merge=false merge data with existing entry
* @param int $recur_date=0 if set, import the recurrence at this timestamp,
* default 0 => import whole series (or events, if not recurring)
* @param string $principalURL='' Used for CalDAV imports
* @param int $user=null account_id of owner, default null
* @return int|boolean cal_id > 0 on success, false on failure or 0 for a failed etag
*/
function importVCal($_vcalData, $cal_id=-1, $etag=null, $merge=false, $recur_date=0)
function importVCal($_vcalData, $cal_id=-1, $etag=null, $merge=false, $recur_date=0, $principalURL='', $user=null)
{
if (!is_array($this->supportedFields)) $this->setSupportedFields();
if (!($events = $this->icaltoegw($_vcalData)))
if (!($events = $this->icaltoegw($_vcalData, $principalURL)))
{
return false;
}
@ -1001,8 +1031,9 @@ class calendar_ical extends calendar_boupdate
}
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()\n" .
array2string($event)."\n",3,$this->logfile);
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
."($cal_id, $etag, $recur_date, $principalURL, $user)\n"
. array2string($event)."\n",3,$this->logfile);
}
/*
@ -1090,7 +1121,7 @@ class calendar_ical extends calendar_boupdate
else
{
// no merge
if(!isset($this->supportedFields['category']))
if(!isset($this->supportedFields['category']) || !isset($event['category']))
{
$event['category'] = $event_info['stored_event']['category'];
}
@ -1192,9 +1223,20 @@ class calendar_ical extends calendar_boupdate
$event['non_blocking'] = 1;
}
if (!is_null($user))
{
if ($this->check_perms(EGW_ACL_ADD, 0, $user))
{
$event['owner'] = $user;
}
else
{
return false; // no permission
}
}
// check if an owner is set and the current user has add rights
// for that owners calendar; if not set the current user
if (!isset($event['owner'])
elseif (!isset($event['owner'])
|| !$this->check_perms(EGW_ACL_ADD, 0, $event['owner']))
{
$event['owner'] = $this->user;
@ -1945,11 +1987,18 @@ class calendar_ical extends calendar_boupdate
// __FILE__, __LINE__, PEAR_LOG_DEBUG);
}
function icaltoegw($_vcalData)
/**
* Convert vCalendar data in EGw events
*
* @param string $_vcalData
* @param string $principalURL='' Used for CalDAV imports
* @return array|boolean events on success, false on failure
*/
function icaltoegw($_vcalData, $principalURL='')
{
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()\n" .
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."($principalURL)\n" .
array2string($_vcalData)."\n",3,$this->logfile);
}
@ -1987,7 +2036,7 @@ class calendar_ical extends calendar_boupdate
{
if (is_a($component, 'Horde_iCalendar_vevent'))
{
if (($event = $this->vevent2egw($component, $version, $this->supportedFields)))
if (($event = $this->vevent2egw($component, $version, $this->supportedFields, $principalURL)))
{
//common adjustments
if ($this->productManufacturer == '' && $this->productName == ''
@ -2020,6 +2069,14 @@ class calendar_ical extends calendar_boupdate
$events[] = $event;
}
}
else
{
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.'()' .
get_class($component)." found\n",3,$this->logfile);
}
}
}
date_default_timezone_set($GLOBALS['egw_info']['server']['server_timezone']);
@ -2033,12 +2090,21 @@ class calendar_ical extends calendar_boupdate
* @param array $component VEVENT
* @param string $version vCal version (1.0/2.0)
* @param array $supportedFields supported fields of the device
* @param string $principalURL='' Used for CalDAV imports
*
* @return array|boolean event on success, false on failure
*/
function vevent2egw(&$component, $version, $supportedFields)
function vevent2egw(&$component, $version, $supportedFields, $principalURL='')
{
if (!is_a($component, 'Horde_iCalendar_vevent')) return false;
if (!is_a($component, 'Horde_iCalendar_vevent'))
{
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.'()' .
get_class($component)." found\n",3,$this->logfile);
}
return false;
}
if (isset($GLOBALS['egw_info']['user']['preferences']['syncml']['minimum_uid_length'])) {
$minimum_uid_length = $GLOBALS['egw_info']['user']['preferences']['syncml']['minimum_uid_length'];
@ -2106,8 +2172,15 @@ class calendar_ical extends calendar_boupdate
$vcardData['end'] = $dtend_ts;
}
}
if (!isset($vcardData['start'])) return false; // not a valid entry
if (!isset($vcardData['start']))
{
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
. "() DTSTART missing!\n",3,$this->logfile);
}
return false; // not a valid entry
}
// lets see what we can get from the vcard
foreach ($component->_attributes as $attributes)
{
@ -2436,7 +2509,7 @@ class calendar_ical extends calendar_boupdate
}
break;
case 'ATTENDEE':
case 'ORGANIZER': // will be written direct to the event
case 'ORGANIZER':
if (isset($attributes['params']['PARTSTAT']))
{
$attributes['params']['STATUS'] = $attributes['params']['PARTSTAT'];
@ -2450,14 +2523,64 @@ class calendar_ical extends calendar_boupdate
{
$status = 'X'; // client did not return the status
}
$cn = '';
if (preg_match('/MAILTO:([@.a-z0-9_-]+)|MAILTO:"?([.a-z0-9_ -]*)"?[ ]*<([@.a-z0-9_-]*)>/i',
$uid = $email = $cn = '';
$quantity = 1;
$role = 'REQ-PARTICIPANT';
if (!empty($attributes['params']['ROLE']))
{
$role = $attributes['params']['ROLE'];
}
// try pricipal url from CalDAV
if (strpos($attributes['value'], 'http') === 0)
{
if (!empty($principalURL) && strstr($attributes['value'], $principalURL) !== false)
{
$uid = $this->user;
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
. "(): Found myself: '$uid'\n",3,$this->logfile);
}
}
else
{
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
. '(): Unknown URI: ' . $attributes['value']
. "\n",3,$this->logfile);
}
$attributes['value'] = '';
}
}
// try X-EGROUPWARE-UID
if (!$uid && !empty($attributes['params']['X-EGROUPWARE-UID']))
{
$uid = $attributes['params']['X-EGROUPWARE-UID'];
if (!empty($attributes['params']['X-EGROUPWARE-QUANTITY']))
{
$quantity = $attributes['params']['X-EGROUPWARE-QUANTITY'];
}
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
. "(): Found X-EGROUPWARE-UID: '$uid'\n",3,$this->logfile);
}
}
elseif ($attributes['value'] == 'Unknown')
{
// we use the current user
$uid = $this->user;
}
// try to find an email address
elseif (preg_match('/MAILTO:([@.a-z0-9_-]+)|MAILTO:"?([.a-z0-9_ -]*)"?[ ]*<([@.a-z0-9_-]*)>/i',
$attributes['value'],$matches))
{
$email = $matches[1] ? $matches[1] : $matches[3];
$cn = isset($matches[2]) ? $matches[2]: '';
}
elseif (preg_match('/"?([.a-z0-9_ -]*)"?[ ]*<([@.a-z0-9_-]*)>/i',
elseif (!empty($attributes['value']) &&
preg_match('/"?([.a-z0-9_ -]*)"?[ ]*<([@.a-z0-9_-]*)>/i',
$attributes['value'],$matches))
{
$cn = $matches[1];
@ -2467,82 +2590,92 @@ class calendar_ical extends calendar_boupdate
{
$email = $attributes['value'];
}
else
if (!$uid && $email && ($uid = $GLOBALS['egw']->accounts->name2id($email, 'account_email')))
{
$email = false; // no email given
}
$searcharray = array();
if ($email)
{
$searcharray = array('email' => $email, 'email_home' => $email);
}
if (isset($attributes['params']['CN']) && $attributes['params']['CN'])
{
if ($attributes['params']['CN'][0] == '"'
&& substr($attributes['params']['CN'],-1) == '"')
// we use the account we found
if ($this->log)
{
$attributes['params']['CN'] = substr($attributes['params']['CN'],1,-1);
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
. "() Found account: '$uid', '$cn', '$email'\n",3,$this->logfile);
}
$searcharray['n_fn'] = $attributes['params']['CN'];
}
elseif ($cn)
if (!$uid)
{
$searcharray['n_fn'] = $cn;
}
if (($uid = $attributes['params']['X-EGROUPWARE-UID'])
&& ($info = $this->resource_info($uid))
&& (!$email || $info['email'] == $email))
{
// we use the (checked) X-EGROUPWARE-UID
}
//elseif (//$attributes['params']['CUTYPE'] == 'GROUP'
elseif (preg_match('/(.*) Group/', $searcharray['n_fn'], $matches))
{
if (($uid = $GLOBALS['egw']->accounts->name2id($matches[1], 'account_lid', 'g')))
$searcharray = array();
// search for provided email address ...
if ($email)
{
//Horde::logMessage("vevent2egw: group participant $uid",
// __FILE__, __LINE__, PEAR_LOG_DEBUG);
if ($status != 'X' && $status != 'U')
$searcharray = array('email' => $email, 'email_home' => $email);
}
// ... and for provided CN
if (!empty($attributes['params']['CN']))
{
if ($attributes['params']['CN'][0] == '"'
&& substr($attributes['params']['CN'],-1) == '"')
{
// User tries to reply to the group invitiation
$members = $GLOBALS['egw']->accounts->members($uid, true);
if (in_array($this->user, $members))
$cn = substr($attributes['params']['CN'],1,-1);
}
$searcharray['n_fn'] = $cn;
}
elseif ($cn)
{
$searcharray['n_fn'] = $cn;
}
//elseif (//$attributes['params']['CUTYPE'] == 'GROUP'
if (preg_match('/(.*) Group/', $cn, $matches))
{
if (($uid = $GLOBALS['egw']->accounts->name2id($matches[1], 'account_lid', 'g')))
{
//Horde::logMessage("vevent2egw: group participant $uid",
// __FILE__, __LINE__, PEAR_LOG_DEBUG);
if (!isset($vcardData['participants'][$this->user]) &&
$status != 'X' && $status != 'U')
{
//Horde::logMessage("vevent2egw: set status to " . $status,
// __FILE__, __LINE__, PEAR_LOG_DEBUG);
$vcardData['participants'][$this->user] =
calendar_so::combine_status($status);
// User tries to reply to the group invitiation
$members = $GLOBALS['egw']->accounts->members($uid, true);
if (in_array($this->user, $members))
{
//Horde::logMessage("vevent2egw: set status to " . $status,
// __FILE__, __LINE__, PEAR_LOG_DEBUG);
$vcardData['participants'][$this->user] =
calendar_so::combine_status($status,$quantity,$role);
}
}
$status = 'U'; // keep the group
}
else continue; // can't find this group
}
else continue; // can't find this group
}
elseif ($attributes['value'] == 'Unknown')
{
$uid = $this->user;
}
elseif ($email && ($uid = $GLOBALS['egw']->accounts->name2id($email,'account_email')))
{
// we use the account we found
}
elseif (!$searcharray)
{
continue; // participants without email AND CN --> ignore it
}
elseif ((list($data) = ExecMethod2('addressbook.addressbook_bo.search',$searcharray,
array('id','egw_addressbook.account_id as account_id','n_fn'),'egw_addressbook.account_id IS NOT NULL DESC, n_fn IS NOT NULL DESC','','',false,'OR')))
{
$uid = $data['account_id'] ? (int)$data['account_id'] : 'c'.$data['id'];
}
else
{
if (!$email)
elseif (empty($searcharray))
{
$email = 'no-email@egroupware.org'; // set dummy email to store the CN
continue; // participants without email AND CN --> ignore it
}
elseif ((list($data) = $this->addressbook->search($searcharray,
array('id','egw_addressbook.account_id as account_id','n_fn'),
'egw_addressbook.account_id IS NOT NULL DESC, n_fn IS NOT NULL DESC',
'','',false,'OR')))
{
// found an addressbook entry
$uid = $data['account_id'] ? (int)$data['account_id'] : 'c'.$data['id'];
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
. "() Found addressbook entry: '$uid', '$cn', '$email'\n",3,$this->logfile);
}
}
else
{
if (!$email)
{
$email = 'no-email@egroupware.org'; // set dummy email to store the CN
}
$uid = 'e'. ($cn ? '"' . $cn . '" <' . $email . '>' : $email);
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
. "() Not Found, create dummy: '$uid', '$cn', '$email'\n",3,$this->logfile);
}
}
$uid = 'e'.($attributes['params']['CN'] ? $attributes['params']['CN'].' <'.$email.'>' : $email);
}
switch($attributes['name'])
{
@ -2558,9 +2691,15 @@ class calendar_ical extends calendar_boupdate
// for multiple entries the ACCEPT wins
// add quantity and role
$vcardData['participants'][$uid] =
calendar_so::combine_status($status,
$attributes['params']['X-EGROUPWARE-QUANTITY'],
$attributes['params']['ROLE']);
calendar_so::combine_status($status, $quantity, $role);
if (!$this->calendarOwner && is_numeric($uid) &&
$role == 'CHAIR' &&
is_a($component->getAttribute('ORGANIZER'), 'PEAR_Error'))
{
// we can store the ORGANIZER as event owner
$event['owner'] = $uid;
}
}
break;
@ -2572,7 +2711,7 @@ class calendar_ical extends calendar_boupdate
$vcardData['participants'][$uid] =
calendar_so::combine_status($status, $quantity, 'CHAIR');
}
if (is_numeric($uid) && ($uid == $this->calendarOwner || !$this->calendarOwner))
if (!$this->calendarOwner && is_numeric($uid))
{
// we can store the ORGANIZER as event owner
$event['owner'] = $uid;
@ -2585,7 +2724,7 @@ class calendar_ical extends calendar_boupdate
{
// save the ORGANIZER as event CHAIR
$vcardData['participants'][$uid] =
calendar_so::combine_status('U', 1, 'CHAIR');
calendar_so::combine_status('D', 1, 'CHAIR');
}
}
}
@ -2694,9 +2833,14 @@ class calendar_ical extends calendar_boupdate
{
$filter = $relax ? 'relax' : 'check';
$event = array_shift($events);
$eventId = -1;
if ($this->so->isWholeDay($event)) $event['whole_day'] = true;
if ($contentID)
{
$parts = preg_split('/:/', $contentID);
$event['id'] = $eventId = $parts[0];
}
$event['category'] = $this->find_or_add_categories($event['category'], $eventId);
if ($contentID) $event['id'] = $contentID;
return $this->find_event($event, $filter);
}
if ($this->log)

View File

@ -758,15 +758,35 @@ ORDER BY cal_user_type, cal_usre_id
array('cal_id' => $event['cal_id']),__LINE__,__FILE__,'calendar');
}
// write information about recuring event, if recur_type is present in the array
if (isset($event['recur_type']))
if ($event['recur_type'] != MCAL_RECUR_NONE)
{
// fetch information about the currently saved (old) event
$old_min = (int) $this->db->select($this->dates_table,'MIN(cal_start)',array('cal_id'=>$cal_id),__LINE__,__FILE__,false,'','calendar')->fetchColumn();
$old_duration = (int) $this->db->select($this->dates_table,'MIN(cal_end)',array('cal_id'=>$cal_id),__LINE__,__FILE__,false,'','calendar')->fetchColumn() - $old_min;
$old_repeats = $this->db->select($this->repeats_table,'*',array('cal_id' => $cal_id),__LINE__,__FILE__,false,'','calendar')->fetch();
$old_exceptions = $old_repeats['recur_exception'] ? explode(',',$old_repeats['recur_exception']) : array();
if (!empty($old_exceptions))
{
sort($old_exceptions);
if ($old_min > $old_exceptions[0]) $old_min = $old_exceptions[0];
}
$event['recur_exception'] = is_array($event['recur_exception']) ? $event['recur_exception'] : array();
if (!empty($event['recur_exception']))
{
sort($event['recur_exception']);
}
$where = array('cal_id' => $cal_id,
'cal_recur_date' => 0);
$old_participants = array();
foreach ($this->db->select($this->user_table,'cal_user_type,cal_user_id,cal_status,cal_quantity,cal_role', $where,
__LINE__,__FILE__,false,'','calendar') as $row)
{
$uid = self::combine_user($row['cal_user_type'], $row['cal_user_id']);
$status = self::combine_status($row['cal_status'], $row['cal_quantity'], $row['cal_role']);
$old_participants[$uid] = $status;
}
// re-check: did so much recurrence data change that we have to rebuild it from scratch?
if (!$set_recurrences)
@ -795,18 +815,20 @@ ORDER BY cal_user_type, cal_usre_id
{
// we adjust some possibly changed recurrences manually
// deleted exceptions: re-insert recurrences into the user and dates table
if(count($deleted_exceptions = array_diff($old_exceptions,$event['recur_exception'])))
if (count($deleted_exceptions = array_diff($old_exceptions,$event['recur_exception'])))
{
if (isset($event['cal_participants']))
{
$participants = $event['cal_participants'];
}
else
{
// use old default
$participants = $old_participants;
}
foreach($deleted_exceptions as $id => $deleted_exception)
{
// rebuild participants for the re-inserted recurrence
$participants = array();
$participants_only = $this->get_participants($cal_id); // participants without states
foreach($participants_only as $id => $participant_only)
{
$states = $this->get_recurrences($cal_id, $participant_only['uid']);
$participants[$participant_only['uid']] = $states[0]; // insert main status as default
}
$this->recurrence($cal_id, $deleted_exception, $deleted_exception + $old_duration, $participants);
}
}
@ -1058,11 +1080,12 @@ ORDER BY cal_user_type, cal_usre_id
/**
* updates the participants of an event, taken into account the evtl. recurrences of the event(!)
* this method just adds new participants or removes not longer set participants
* this method does never overwrite existing entries (except for delete)
* this method does never overwrite existing entries (except the 0-recurrence and for delete)
*
* @param int $cal_id
* @param array $participants uid => status pairs
* @param int|boolean $change_since=0, false=new entry, 0=all, > 0 time from which on the repetitions should be changed
* @param int|boolean $change_since=0, false=new event,
* 0=all, > 0 time from which on the repetitions should be changed
* @param boolean $add_only=false
* false = add AND delete participants if needed (full list of participants required in $participants)
* true = only add participants if needed, no participant will be deleted (participants to check/add required in $participants)
@ -1148,7 +1171,7 @@ ORDER BY cal_user_type, cal_usre_id
// update participants
foreach($participants as $uid => $status)
{
$id = null;
$type = $id = $quantity = $role = null;
self::split_user($uid,$type,$id);
self::split_status($status,$quantity,$role);
$set = array(

View File

@ -30,30 +30,25 @@ class infolog_bo
var $vfs_basedir='/infolog';
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
*
* var int
* @var int
*/
var $user_time_now;
var $now;
/**
* name of timestamps in an InfoLog entry
*
@ -288,7 +283,7 @@ class infolog_bo
/**
* check's if user has the requiered rights on entry $info_id
*
* @param int/array $info data or info_id of infolog entry to check
* @param int|array $info data or info_id of infolog entry to check
* @param int $required_rights EGW_ACL_{READ|EDIT|ADD|DELETE}
* @return boolean
*/
@ -514,13 +509,13 @@ class infolog_bo
/**
* Read an infolog entry specified by $info_id
*
* @param int/array $info_id integer id or array with key 'info_id' of the entry to read
* @param int|array $info_id integer id or array with key 'info_id' of the entry to read
* @param boolean $run_link_id2from=true should link_id2from run, default yes,
* need to be set to false if called from link-title to prevent an infinit recursion
* @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
* @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,$date_format='ts')
{
@ -558,7 +553,7 @@ class infolog_bo
{
$time = new egw_time($data['info_enddate'], egw_time::$server_timezone);
// Set due date to 00:00
$time->setTime(0, 0,0 );
$time->setTime(0, 0, 0);
$data['info_enddate'] = egw_time::to($time,'ts');
}
@ -568,9 +563,9 @@ class infolog_bo
/**
* Delete an infolog entry, evtl. incl. it's children / subs
*
* @param int/array $info_id int id
* @param int|array $info_id int id
* @param boolean $delete_children should the children be deleted
* @param int/boolean $new_parent parent to use for not deleted children if > 0
* @param int|boolean $new_parent parent to use for not deleted children if > 0
* @return boolean True if delete was successful, False otherwise ($info_id does not exist or no rights)
*/
function delete($info_id,$delete_children=False,$new_parent=False)
@ -691,7 +686,7 @@ 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];
}
@ -790,7 +785,7 @@ class infolog_bo
}
else
{
$time = new egw_time($values['info_enddate'], egw_time::$user_timezone);
$time = new egw_time($values['info_enddate'], egw_time::$server_timezone);
// Set due date to 00:00
$time->setTime(0, 0, 0);
$to_write['info_enddate'] = egw_time::to($time,'ts');
@ -821,13 +816,13 @@ class infolog_bo
{
$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(
@ -910,11 +905,12 @@ class infolog_bo
{
$query['start'] = egw_time::user2server($query['start'],'ts');
}
$ret = $this->so->search($query);
if (is_array($ret))
{
foreach($ret as $id => &$data)
foreach ($ret as $id => &$data)
{
if (!$this->check_access($data,EGW_ACL_READ))
{
@ -922,7 +918,7 @@ class infolog_bo
continue;
}
// convert system- to user-time
foreach($this->timestamps as $key)
foreach ($this->timestamps as $key)
{
if ($data[$key])
{
@ -1043,8 +1039,8 @@ class infolog_bo
*
* Is called as hook to participate in the linking
*
* @param int/array $info int info_id or array with infolog entry
* @return string/boolean string with the title, null if $info not found, false if no perms to view
* @param int|array $info int info_id or array with infolog entry
* @return string|boolean string with the title, null if $info not found, false if no perms to view
*/
function link_title($info)
{
@ -1068,13 +1064,13 @@ class infolog_bo
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
}
@ -1103,7 +1099,7 @@ class infolog_bo
$content = array();
if (is_array($ids))
{
foreach($ids as $id => $info )
foreach ($ids as $id => $info )
{
$content[$id] = $this->link_title($id);
}
@ -1173,7 +1169,7 @@ class infolog_bo
}
while ($infos = $this->search($query))
{
foreach($infos as $info)
foreach ($infos as $info)
{
$start = new egw_time($info['info_startdate'],egw_time::$user_timezone);
$time = (int) $start->format('Hi');
@ -1189,7 +1185,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)
@ -1228,7 +1224,7 @@ class infolog_bo
if (isset($args['infolog']) && count($args['infolog']))
{
$icons = $this->so->get_status($args['infolog']);
foreach((array) $icons as $id => $status)
foreach ((array) $icons as $id => $status)
{
if ($status && substr($status,-1) != '%')
{
@ -1262,11 +1258,11 @@ class infolog_bo
// preserve categories without users read access
$old_infolog = $this->read($info_id);
$old_categories = explode(',',$old_infolog['info_cat']);
if(is_array($old_categories) && count($old_categories) > 0)
if (is_array($old_categories) && count($old_categories) > 0)
{
foreach($old_categories as $cat_id)
foreach ($old_categories as $cat_id)
{
if($cat_id && !$this->categories->check_perms(EGW_ACL_READ, $cat_id))
if ($cat_id && !$this->categories->check_perms(EGW_ACL_READ, $cat_id))
{
$old_cats_preserve[] = $cat_id;
}
@ -1275,7 +1271,7 @@ class infolog_bo
}
$cat_id_list = array();
foreach ($catname_list as $cat_name)
foreach ((array)$catname_list as $cat_name)
{
$cat_name = trim($cat_name);
$cat_id = $this->categories->name2id($cat_name, 'X-');
@ -1550,7 +1546,7 @@ class infolog_bo
&& ($egwData = $this->read($infoData['info_id'], true, 'server')))
{
// we only do a simple consistency check
if (strpos($egwData['info_subject'], $infoData['info_subject']) === 0)
if (!$relax || strpos($egwData['info_subject'], $infoData['info_subject']) === 0)
{
return array($egwData['info_id']);
}

View File

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

View File

@ -111,14 +111,21 @@ class infolog_ical extends infolog_bo
/**
* Exports one InfoLog tast to an iCalendar VTODO
*
* @param int $_taskID info_id
* @param int|array $task infolog_id or infolog-tasks data
* @param string $_version='2.0' could be '1.0' too
* @param string $_method='PUBLISH'
* @return string/boolean string with vCal or false on error (eg. no permission to read the event)
*/
function exportVTODO($_taskID, $_version='2.0',$_method='PUBLISH')
function exportVTODO($task, $_version='2.0',$_method='PUBLISH')
{
$taskData = $this->read($_taskID, true, 'server');
if (is_array($task))
{
$taskData = $task;
}
else
{
if (!($taskData = $this->read($task, true, 'server'))) return false;
}
if ($taskData['info_id_parent'])
{
@ -151,6 +158,12 @@ 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);
@ -407,9 +420,10 @@ class infolog_ical extends infolog_bo
* @param string $_vcalData
* @param int $_taskID=-1 info_id, default -1 = new entry
* @param boolean $merge=false merge data with existing entry
* @param int $user=null delegate new task to this account_id, default null
* @return int|boolean integer info_id or false on error
*/
function importVTODO(&$_vcalData, $_taskID=-1, $merge=false)
function importVTODO(&$_vcalData, $_taskID=-1, $merge=false, $user=null)
{
if ($this->tzid)
@ -438,6 +452,11 @@ class infolog_ical extends infolog_bo
$taskData['info_datecompleted'] = 0;
}
if (!is_null($user))
{
$taskData['info_owner'] = $user;
}
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()\n" .
@ -615,7 +634,7 @@ class infolog_ical extends infolog_bo
// 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;
if ($attr['name'] == 'X-INFOLOG-STATUS') break;
}
$taskData['info_status'] = $this->vtodo2status($attribute['value'],
$attr['name'] == 'X-INFOLOG-STATUS' ? $attr['value'] : null);
@ -668,7 +687,8 @@ class infolog_ical extends infolog_bo
*/
function exportVNOTE($_noteID, $_type)
{
$note = $this->read($_noteID, true, 'server');
if(!($note = $this->read($_noteID, true, 'server'))) return false;
$note = $GLOBALS['egw']->translation->convert($note,
$GLOBALS['egw']->translation->charset(), 'UTF-8');

View File

@ -121,7 +121,7 @@ class infolog_sif extends infolog_bo
*
* @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)
{
@ -142,12 +142,14 @@ class infolog_sif extends infolog_bo
{
$arr = egw_time::to($time, 'array');
$time = new egw_time($arr, self::$tz_cache[$tzid]);
$value = $time->format('Y-m-d');
}
else
{
$time->setTimezone(self::$tz_cache[$tzid]);
$value = $time->format('Ymd\THis');
}
return $time->format('Ymd\THis');
return $value;
}
function startElement($_parser, $_tag, $_attributes)
@ -509,12 +511,12 @@ class infolog_sif extends infolog_bo
case 'DueDate':
case 'StartDate':
$sifTask .= '<$sifField>';
$sifTask .= "<$sifField>";
if (!empty($value))
{
$sifTask .= $this->getDateTime($value, $this->tzid);
}
$sifTask .= '</$sifField>';
$sifTask .= "</$sifField>";
break;
case 'Importance':

View File

@ -36,6 +36,10 @@ require_once('HTTP/WebDAV/Server.php');
*/
class groupdav extends HTTP_WebDAV_Server
{
/**
* DAV namespace
*/
const DAV = 'DAV:';
/**
* GroupDAV namespace
*/
@ -70,7 +74,7 @@ class groupdav extends HTTP_WebDAV_Server
'component-set' => array(self::GROUPDAV => 'VCARD'),
),
'infolog' => array(
'resourcetype' => array(self::GROUPDAV => 'vtodo-collection'),
'resourcetype' => array(self::GROUPDAV => 'vtodo-collection', self::CALDAV => 'calendar'),
'component-set' => array(self::GROUPDAV => 'VTODO'),
),
);
@ -81,7 +85,7 @@ class groupdav extends HTTP_WebDAV_Server
*
* @var integer
*/
var $debug = 0;
var $debug = 3;
/**
* eGW's charset
@ -101,6 +105,19 @@ class groupdav extends HTTP_WebDAV_Server
* @var groupdav_handler
*/
var $handler;
/**
* principal URL
*
* @var string
*/
var $principalURL;
/**
* Reference to the accounts class
*
* @var accounts
*/
var $accounts;
function __construct()
{
@ -115,11 +132,24 @@ class groupdav extends HTTP_WebDAV_Server
case 'davkit': // iCal app in OS X 10.6 created wrong request, if full url given
$this->client_require_href_as_url = false;
break;
case 'cfnetwork':
$this->crrnd = true; // Apple Addressbook.app does not cope with namespace redundancy
}
parent::HTTP_WebDAV_Server();
$this->translation =& $GLOBALS['egw']->translation;
$this->egw_charset = $this->translation->charset();
if (strpos($this->base_uri, 'http') === 0)
{
$this->principalURL = $this->_slashify($this->base_uri);
}
else
{
$this->principalURL = (@$_SERVER["HTTPS"] === "on" ? "https:" : "http:") .
'//' . $_SERVER['HTTP_HOST'] . $_SERVER['SCRIPT_NAME'] . '/';
}
$this->principalURL .= 'principals/users/'.$GLOBALS['egw_info']['user']['account_lid'].'/';
$this->accounts = $GLOBALS['egw']->accounts;
}
/**
@ -130,7 +160,7 @@ class groupdav extends HTTP_WebDAV_Server
*/
function app_handler($app)
{
return groupdav_handler::app_handler($app,$this->debug,$this->base_uri);
return groupdav_handler::app_handler($app,$this->debug,$this->base_uri,$this->principalURL);
}
/**
@ -146,11 +176,24 @@ class groupdav extends HTTP_WebDAV_Server
switch($app)
{
case 'calendar':
$dav[] = 2;
$dav[] = 'access-control';
$dav[] = 'calendar-access';
//$dav[] = 'calendar-schedule';
//$dav[] = 'calendar-proxy';
//$dav[] = 'calendar-avialibility';
//$dav[] = 'calendarserver-private-events';
break;
case 'addressbook':
$dav[] = 'addressbook';
$dav[] = 2;
$dav[] = 3;
$dav[] = 'access-control';
$dav[] = 'addressbook-access';
break;
default:
$dav[] = 2;
$dav[] = 'access-control';
$dav[] = 'calendar-access';
}
// not yet implemented: $dav[] = 'access-control';
}
@ -162,62 +205,101 @@ class groupdav extends HTTP_WebDAV_Server
* @param array return array for file properties
* @return bool true on success
*/
function PROPFIND(&$options, &$files,$method='PROPFIND')
function PROPFIND(&$options, &$files, $method='PROPFIND')
{
if ($this->debug) error_log(__CLASS__."::$method(".array2string($options,true).')');
if (groupdav_handler::get_agent() == 'cfnetwork' && // Apple Addressbook
$options['root']['name'] == 'propfind')
{
foreach ($options['props'] as $props)
{
if ($props['name'] == 'current-user-privilege-set')
{
if ($this->debug > 2) error_log(__CLASS__."::$method: current-user-privilege-set not implemented!");
return '501 Not Implemented';
}
}
}
// parse path in form [/account_lid]/app[/more]
if (!self::_parse_path($options['path'],$id,$app,$user,$user_prefix) && $app && !$user)
{
if ($this->debug > 1) error_log(__CLASS__."::$method: user=$user, app=$app, id=$id: 404 not found!");
if ($this->debug > 1) error_log(__CLASS__."::$method: user='$user', app='$app', id='$id': 404 not found!");
return '404 Not Found';
}
if ($this->debug > 1) error_log(__CLASS__."::$method: user=$user, app='$app', id=$id");
if ($this->debug > 1) error_log(__CLASS__."::$method: user='$user', app='$app', id='$id'");
if ($user)
{
$account_lid = $this->accounts->id2name($user);
}
else
{
$account_lid = $GLOBALS['egw_info']['user']['account_lid'];
}
$account = $this->accounts->read($account_lid);
$displayname = $GLOBALS['egw']->translation->convert($account['account_fullname'],
$GLOBALS['egw']->translation->charset(),'utf-8');
$files = array('files' => array());
$path = $user_prefix = $this->_slashify($user_prefix);
if (!$app) // root folder containing apps
if (!$app) // user root folder containing apps
{
if (empty($user_prefix))
{
$user_prefix = '/'.$GLOBALS['egw_info']['user']['account_lid'].'/';
}
if ($options['depth'])
{
$displayname = 'EGroupware (Cal|Card|Group)DAV server';
}
// self url
$files['files'][] = array(
'path' => $user_prefix.'/',
'props' => array(
self::mkprop('displayname','EGroupware (Cal|Card|Group)DAV server'),
self::mkprop('resourcetype','collection'),
$props = array(
self::mkprop('displayname',$displayname),
self::mkprop('resourcetype',array(self::mkprop('collection',''))),
// adding the calendar extra property (calendar-home-set, etc.) here, allows apple iCal to "autodetect" the URL
self::mkprop(groupdav::CALDAV,'calendar-home-set',$this->base_uri.'/calendar/'),
self::mkprop('current-user-principal',array(self::mkprop('href',$this->base_uri.'/principals/'.$GLOBALS['egw_info']['user']['account_lid'].'/'))),
),
self::mkprop(groupdav::CALDAV,'calendar-home-set',array(
self::mkprop('href',$this->base_uri.$user_prefix.'calendar/'))),
self::mkprop(groupdav::CARDDAV,'addressbook-home-set',array(
self::mkprop('href',$this->base_uri.$user_prefix))),
self::mkprop('current-user-principal',array(self::mkprop('href',$this->principalURL))),
self::mkprop(groupdav::CALDAV,'calendar-user-address-set',array(
self::mkprop('href','MAILTO:'.$GLOBALS['egw_info']['user']['email']))),
//self::mkprop('principal-URL',array(self::mkprop('href',$this->principalURL))),
//self::mkprop('principal-collection-set',array(self::mkprop('href',$this->base_uri.'/principals/'))),
);
//$props = self::current_user_privilege_set($props);
$files['files'][] = array(
'path' => $path,
'props' => $props,
);
if ($options['depth'])
{
if (empty($user_prefix))
if (strlen($path) == 1) // GroupDAV Root
{
// principals collection
$files['files'][] = array(
'path' => '/principals/',
'props' => array(
self::mkprop('displayname',lang('Accounts')),
self::mkprop('resourcetype','collection'),
self::mkprop('current-user-principal',array(self::mkprop('href',$this->base_uri.'/principals/'.$GLOBALS['egw_info']['user']['account_lid'].'/'))),
),
);
// groups collection
$files['files'][] = array(
'path' => '/groups/',
'props' => array(
self::mkprop('displayname',lang('Groups')),
self::mkprop('resourcetype','collection'),
self::mkprop('current-user-principal',array(self::mkprop('href',$this->base_uri.'/principals/'.$GLOBALS['egw_info']['user']['account_lid'].'/'))),
),
);
self::mkprop('displayname',lang('Accounts')),
self::mkprop('resourcetype',array(self::mkprop('collection',''))),
self::mkprop('current-user-principal',array(self::mkprop('href',$this->principalURL))),
self::mkprop(groupdav::CALDAV,'calendar-home-set',array(
self::mkprop('href',$this->base_uri.$user_prefix.'calendar/'))),
self::mkprop(groupdav::CARDDAV,'addressbook-home-set',array(
self::mkprop('href',$this->base_uri.'/'))),
self::mkprop('principal-URL',array(self::mkprop('href',$this->principalURL))),
),
);
}
foreach($this->root as $app => $data)
{
if (!$GLOBALS['egw_info']['user']['apps'][$app]) continue; // no rights for the given app
$files['files'][] = array(
'path' => $user_prefix.'/'.$app.'/',
'path' => $path.$app.'/',
'props' => $this->_properties($app,false,$user),
);
}
@ -234,12 +316,12 @@ class groupdav extends HTTP_WebDAV_Server
if ($method != 'REPORT' && !$id) // no self URL for REPORT requests (only PROPFIND) or propfinds on an id
{
$files['files'][0] = array(
'path' => '/'.$app.'/',
'path' => $path.$app.'/',
// KAddressbook doubles the folder, if the self URL contains the GroupDAV/CalDAV resourcetypes
'props' => $this->_properties($app,$app=='addressbook'&&strpos($_SERVER['HTTP_USER_AGENT'],'KHTML') !== false),
'props' => $this->_properties($app,$app=='addressbook'&&strpos($_SERVER['HTTP_USER_AGENT'],'KHTML') !== false,$user),
);
}
if (!$options['depth'] && !$id)
if (isset($options['depth']) && !$options['depth'] && !$id)
{
// add ctag if handler implements it (only for depth 0)
if (method_exists($handler,'getctag'))
@ -249,7 +331,7 @@ class groupdav extends HTTP_WebDAV_Server
}
return true; // depth 0 --> show only the self url
}
return $handler->propfind($options['path'],$options,$files,$user,$id);
return $handler->propfind($this->_slashify($options['path']),$options,$files,$user,$id);
}
return '501 Not Implemented';
}
@ -264,12 +346,54 @@ class groupdav extends HTTP_WebDAV_Server
*/
function _properties($app,$no_extra_types=false,$user=null)
{
if (!$user) $user = $GLOBALS['egw_info']['user']['account_fullname'];
if ($this->debug) error_log(__CLASS__."::$method: user='$user', app='$app'");
if ($user)
{
$account_lid = $this->accounts->id2name($user);
}
else
{
$account_lid = $GLOBALS['egw_info']['user']['account_lid'];
}
$account = $this->accounts->read($account_lid);
$displayname = $GLOBALS['egw']->translation->convert($account['account_fullname'],
$GLOBALS['egw']->translation->charset(),'utf-8');
$props = array(
self::mkprop('displayname',$this->translation->convert(lang($app).' '.common::grab_owner_name($user),$this->egw_charset,'utf-8')),
self::mkprop('current-user-principal',array(self::mkprop('href',$this->base_uri.'/principals/'.$GLOBALS['egw_info']['user']['account_lid'].'/'))),
);
self::mkprop('current-user-principal',array(self::mkprop('href',$this->principalURL))),
self::mkprop('owner',$displayname),
self::mkprop('principal-URL',array(self::mkprop('href',$this->principalURL))),
self::mkprop('alternate-URI-set',array(
self::mkprop('href','MAILTO:'.$GLOBALS['egw_info']['user']['email']))),
self::mkprop(groupdav::CALDAV,'calendar-user-address-set',array(
self::mkprop('href','MAILTO:'.$GLOBALS['egw_info']['user']['email']))),
self::mkprop('principal-collection-set',array(
self::mkprop('href',$this->base_uri.'/principals/users/'),
self::mkprop('href',$this->base_uri.'/principals/groups/'),
)),
);
switch ($app)
{
case 'calendar':
$props[] = self::mkprop(groupdav::CALDAV,'calendar-home-set',array(
self::mkprop('href',$this->base_uri.'/'.$account_lid.'/calendar/')));
break;
case 'infolog':
$props[] = self::mkprop(groupdav::CALDAV,'calendar-home-set',array(
self::mkprop('href',$this->base_uri.'/'.$account_lid.'/infolog/')));
$displayname = $this->translation->convert(lang($app).' '.
common::grab_owner_name($user),$this->egw_charset,'utf-8');
break;
default:
$props[] = self::mkprop(groupdav::CALDAV,'calendar-home-set',array(
self::mkprop('href',$this->base_uri.'/'.$account_lid.'/calendar/')));
$displayname = $this->translation->convert(lang($app).' '.
common::grab_owner_name($user),$this->egw_charset,'utf-8');
}
$props[] = self::mkprop(groupdav::CARDDAV,'addressbook-home-set',array(
self::mkprop('href',$this->base_uri.'/'.$account_lid.'/')));
$props[] = self::mkprop('displayname',$displayname);
foreach((array)$this->root[$app] as $prop => $values)
{
if ($prop == 'resourcetype')
@ -296,7 +420,9 @@ class groupdav extends HTTP_WebDAV_Server
}
if (method_exists($app.'_groupdav','extra_properties'))
{
$props = ExecMethod($app.'_groupdav::extra_properties',$props);
$displayname = $GLOBALS['egw']->translation->convert($account['account_fullname'],
$GLOBALS['egw']->translation->charset(),'utf-8');
$props = ExecMethod2($app.'_groupdav::extra_properties',$props,$displayname,$this->base_uri);
}
return $props;
}
@ -401,8 +527,7 @@ class groupdav extends HTTP_WebDAV_Server
$props = $this->props2array($file['props']);
//echo $file['path']; _debug_array($props);
$class = $class == 'row_on' ? 'row_off' : 'row_on';
$name = $this->_slashify(basename($this->_unslashify($file['path'])));
/*
if (substr($file['path'],-1) == '/')
{
$name = basename(substr($file['path'],0,-1)).'/';
@ -411,7 +536,7 @@ class groupdav extends HTTP_WebDAV_Server
{
$name = basename($file['path']);
}
*/
echo "\t<tr class='$class'>\n\t\t<td>$n</td>\n\t\t<td>".html::a_href(htmlspecialchars($name),'/groupdav.php'.$file['path'])."</td>\n";
echo "\t\t<td>".$props['DAV:getcontentlength']."</td>\n";
echo "\t\t<td>".(!empty($props['DAV:getlastmodified']) ? date('Y-m-d H:i:s',$props['DAV:getlastmodified']) : '')."</td>\n";
@ -664,6 +789,34 @@ class groupdav extends HTTP_WebDAV_Server
return egw_vfs::checkLock($path);
}
/**
* ACL method handler
*
* @param array general parameter passing array
* @return string HTTP status
*/
function ACL(&$options)
{
self::_parse_path($options['path'],$id,$app,$user);
if ($this->debug) error_log(__METHOD__.'('.array2string($options).") path=$path");
$options['errors'] = array();
switch ($app)
{
case 'calendar':
case 'addressbook':
case 'infolog':
$status = '200 OK'; // grant all
break;
default:
$options['errors'][] = 'no-inherited-ace-conflict';
$status = '403 Forbidden';
}
return $status;
}
/**
* Parse a path into it's id, app and user parts
*
@ -686,7 +839,7 @@ class groupdav extends HTTP_WebDAV_Server
}
$parts = explode('/', $this->_unslashify($path));
if ($GLOBALS['egw']->accounts->name2id($parts[0]))
if ($this->accounts->name2id($parts[0]))
{
// /$user/$app/...
$user = array_shift($parts);
@ -697,7 +850,7 @@ class groupdav extends HTTP_WebDAV_Server
if ($user)
{
$user_prefix = '/'.$user;
$user = $GLOBALS['egw']->accounts->name2id($user,'account_lid',$app != 'addressbook' ? 'u' : null);
$user = $this->accounts->name2id($user,'account_lid',$app != 'addressbook' ? 'u' : null);
}
else
{
@ -710,13 +863,39 @@ class groupdav extends HTTP_WebDAV_Server
list($id) = explode('.',$id); // remove evtl. .ics extension
}
if (!($ok = $id && in_array($app,array('addressbook','calendar','infolog','principals','groups')) && $user))
$ok = $id && $user && in_array($app,array('addressbook','calendar','infolog','principals','groups'));
if ($this->debug)
{
if ($this->debug)
{
error_log(__METHOD__."('$path') returning false: id=$id, app='$app', user=$user");
}
error_log(__METHOD__."('$path') returning " . ($ok ? 'true' : 'false') . ": id='$id', app='$app', user='$user', user_prefix='$user_prefix'");
}
return $ok;
}
/**
* Add the privileges of the current user
*
* @param array $props=array() regular props by the groupdav handler
* @return array
*/
static function current_user_privilege_set(array $props=array())
{
$props[] = HTTP_WebDAV_Server::mkprop('current-user-privilege-set',
array(HTTP_WebDAV_Server::mkprop('privilege',
array(//HTTP_WebDAV_Server::mkprop('all',''),
HTTP_WebDAV_Server::mkprop('read',''),
HTTP_WebDAV_Server::mkprop('read-free-busy',''),
//HTTP_WebDAV_Server::mkprop('read-current-user-privilege-set',''),
HTTP_WebDAV_Server::mkprop('bind',''),
HTTP_WebDAV_Server::mkprop('unbind',''),
HTTP_WebDAV_Server::mkprop('schedule-post',''),
HTTP_WebDAV_Server::mkprop('schedule-post-vevent',''),
HTTP_WebDAV_Server::mkprop('schedule-respond',''),
HTTP_WebDAV_Server::mkprop('schedule-respond-vevent',''),
HTTP_WebDAV_Server::mkprop('schedule-deliver',''),
HTTP_WebDAV_Server::mkprop('schedule-deliver-vevent',''),
HTTP_WebDAV_Server::mkprop('write',''),
HTTP_WebDAV_Server::mkprop('write-properties',''),
HTTP_WebDAV_Server::mkprop('write-content',''),
))));
return $props;
}
}

View File

@ -37,6 +37,12 @@ abstract class groupdav_handler
* @var translation
*/
var $translation;
/**
* Reference to the accounts class
*
* @var accounts
*/
var $accounts;
/**
* Translates method names into ACL bits
*
@ -59,6 +65,12 @@ abstract class groupdav_handler
* @var string
*/
var $base_uri;
/**
* principal URL
*
* @var string
*/
var $principalURL;
/**
* HTTP_IF_MATCH / etag of current request / last call to _common_get_put_delete() method
*
@ -78,17 +90,29 @@ abstract class groupdav_handler
* @param string $app 'calendar', 'addressbook' or 'infolog'
* @param int $debug=null debug-level to set
* @param string $base_uri=null base url of handler
* @param string $principalURL=null pricipal url of handler
*/
function __construct($app,$debug=null,$base_uri=null)
function __construct($app,$debug=null,$base_uri=null,$principalURL=null)
{
//error_log(__METHOD__." called");
$this->app = $app;
#if (!is_null($debug)) $this->debug = $debug = 3;
if (!is_null($debug)) $this->debug = $debug;
$this->base_uri = is_null($base_uri) ? $base_uri : $_SERVER['SCRIPT_NAME'];
if (is_null($principalURL))
{
$this->principalURL = (@$_SERVER["HTTPS"] === "on" ? "https:" : "http:") .
'//'.$_SERVER['HTTP_HOST'] . $_SERVER['SCRIPT_NAME'] . '/';
}
else
{
$this->principalURL = $principalURL.'principals/users/'.
$GLOBALS['egw_info']['user']['account_lid'].'/';
}
$this->agent = self::get_agent();
$this->translation =& $GLOBALS['egw']->translation;
$this->egw_charset = $this->translation->charset();
$this->accounts = $GLOBALS['egw']->accounts;
}
/**
@ -105,12 +129,13 @@ abstract class groupdav_handler
/**
* Propfind callback, if interator is used
*
* @param string $path
* @param array $filter
* @param array|boolean $start false=return all or array(start,num)
* @param int &$total
* @return array with "files" array with values for keys path and props
*/
function &propfind_callback(array $filter,$start,&$total) { }
function &propfind_callback($path, array $filter,$start,&$total) { }
/**
* Handle get request for an applications entry
@ -161,9 +186,11 @@ abstract class groupdav_handler
* Add extra properties for collections
*
* @param array $props=array() regular props by the groupdav handler
* @param string $displayname
* @param string $base_uri=null base url of handler
* @return array
*/
static function extra_properties(array $props=array())
static function extra_properties(array $props=array(), $displayname, $base_uri=null)
{
return $props;
}
@ -185,7 +212,7 @@ abstract class groupdav_handler
// error_log(__METHOD__."(".array2string($entry).") Cant create etag!");
return false;
}
return '"'.$entry['id'].':'.(isset($entry['etag']) ? $entry['etag'] : $entry['modified']).'"';
return 'EGw-'.$entry['id'].':'.(isset($entry['etag']) ? $entry['etag'] : $entry['modified']).'-wGE';
}
/**
@ -196,7 +223,7 @@ abstract class groupdav_handler
*/
static function etag2value($etag)
{
list(,$val) = explode(':',substr($etag,1,-1),2);
list(,$val) = explode(':',substr($etag,4,-4),2);
return $val;
}
@ -211,7 +238,7 @@ abstract class groupdav_handler
* @param array &$options
* @param int $id
* @param boolean &$return_no_access=false if set to true on call, instead of '403 Forbidden' the entry is returned and $return_no_access===false
* @return array/string entry on success, string with http-error-code on failure, null for PUT on an unknown id
* @return array|string entry on success, string with http-error-code on failure, null for PUT on an unknown id
*/
function _common_get_put_delete($method,&$options,$id,&$return_no_access=false)
{
@ -260,11 +287,13 @@ abstract class groupdav_handler
*
* @static
* @param string $app 'calendar', 'addressbook' or 'infolog'
* @param int $user=null owner of the collection, default current user
* @param int $debug=null debug-level to set
* @param string $base_uri=null base url of handler
* @param string $principalURL=null pricipal url of handler
* @return groupdav_handler
*/
static function &app_handler($app,$debug=null,$base_uri=null)
static function &app_handler($app,$debug=null,$base_uri=null,$principalURL=null)
{
static $handler_cache = array();
@ -273,8 +302,14 @@ abstract class groupdav_handler
$class = $app.'_groupdav';
if (!class_exists($class) && !class_exists($class = 'groupdav_'.$app)) return null;
$handler_cache[$app] = new $class($app,$debug,$base_uri);
$handler_cache[$app] = new $class($app,$debug,$base_uri,$principalURL);
}
$handler_cache[$app]->$debug = $debug;
$handler_cache[$app]->$base_uri = $base_uri;
$handler_cache[$app]->$principalURL = $principalURL;
if ($debug) error_log(__METHOD__."('$app', '$base_uri', '$principalURL')");
return $handler_cache[$app];
}
@ -294,6 +329,7 @@ abstract class groupdav_handler
$user_agent = strtolower($_SERVER['HTTP_USER_AGENT']);
foreach(array(
'davkit' => 'davkit', // Apple iCal
'cfnetwork' => 'cfnetwork', // Apple Addressbook
'bionicmessage.net' => 'funambol', // funambol GroupDAV connector from bionicmessage.net
'zideone' => 'zideone', // zideone outlook plugin
'lightning' => 'lightning', // Lighting (SOGo connector for addressbook)
@ -333,6 +369,13 @@ abstract class groupdav_handler
*/
class groupdav_propfind_iterator implements Iterator
{
/**
* current path
*
* @var string
*/
protected $path;
/**
* Handler to call for entries
*
@ -352,8 +395,16 @@ class groupdav_propfind_iterator implements Iterator
*
* @var array
*/
protected $common_files;
/**
* current chunk
*
* @var array
*/
protected $files;
/**
* Start value for callback
*
@ -374,6 +425,8 @@ class groupdav_propfind_iterator implements Iterator
*/
public $debug = false;
/**
/**
* Constructor
*
@ -381,13 +434,14 @@ class groupdav_propfind_iterator implements Iterator
* @param array $filter filter for propfind call
* @param array $files=null extra files/responses to return too
*/
public function __construct(groupdav_handler $handler,array $filter,array &$files=null)
public function __construct(groupdav_handler $handler, $path, array $filter,array &$files=null)
{
if ($this->debug) error_log(__METHOD__."(,".array2string($filter).",)");
if ($this->debug) error_log(__METHOD__."('$path', ".array2string($filter).",)");
$this->path = $path;
$this->handler = $handler;
$this->filter = $filter;
$this->files = $files;
$this->common_files = $files;
reset($this->files);
}
@ -399,7 +453,6 @@ class groupdav_propfind_iterator implements Iterator
public function current()
{
if ($this->debug) error_log(__METHOD__."() returning ".array2string(current($this->files)));
return current($this->files);
}
@ -410,7 +463,7 @@ class groupdav_propfind_iterator implements Iterator
*/
public function key()
{
$current = $this->current();
$current = current($this->files);
if ($this->debug) error_log(__METHOD__."() returning ".array2string($current['path']));
return $current['path']; // we return path as key
@ -424,21 +477,20 @@ class groupdav_propfind_iterator implements Iterator
if (next($this->files) !== false)
{
if ($this->debug) error_log(__METHOD__."() returning TRUE");
return true;
}
if (is_array($this->files) && count($this->files) < self::CHUNK_SIZE) // less entries then asked --> no further available
if (is_array($this->files) && count($this->files) < self::CHUNK_SIZE) // less entries then asked --> no further available
{
if ($this->debug) error_log(__METHOD__."() returning FALSE (no more entries)");
return false; // no further entries
}
// try query further files via propfind callback of handler and store result in $this->files
$this->files = $this->handler->propfind_callback($this->filter,array($this->start,self::CHUNK_SIZE));
$this->files = $this->handler->propfind_callback($this->path,$this->filter,array($this->start,self::CHUNK_SIZE));
$this->start += self::CHUNK_SIZE;
reset($this->files);
if ($this->debug) error_log(__METHOD__."() returning ".array2string(current($this->files) !== false));
return current($this->files) !== false;
}
@ -451,7 +503,8 @@ class groupdav_propfind_iterator implements Iterator
// query first set of files via propfind callback of handler and store result in $this->files
$this->start = 0;
$this->files = $this->handler->propfind_callback($this->filter,array($this->start,self::CHUNK_SIZE));
$files = $this->handler->propfind_callback($this->path,$this->filter,array($this->start,self::CHUNK_SIZE));
$this->files = $this->common_files + $files;
$this->start += self::CHUNK_SIZE;
reset($this->files);
}

View File

@ -7,7 +7,7 @@
* @package api
* @subpackage groupdav
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2008 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2008-10 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @version $Id$
*/
@ -16,12 +16,6 @@
*/
class groupdav_principals extends groupdav_handler
{
/**
* Reference to the accounts class
*
* @var accounts
*/
var $accounts;
/**
* Constructor
@ -29,12 +23,11 @@ class groupdav_principals extends groupdav_handler
* @param string $app 'calendar', 'addressbook' or 'infolog'
* @param int $debug=null debug-level to set
* @param string $base_uri=null base url of handler
* @param string $principalURL=null pricipal url of handler
*/
function __construct($app,$debug=null,$base_uri=null)
function __construct($app,$debug=null,$base_uri=null,$principalURL=null)
{
parent::__construct($app,$debug,$base_uri);
$this->accounts = $GLOBALS['egw']->accounts;
parent::__construct($app,$debug,$base_uri,$principalURL);
}
/**
@ -48,6 +41,40 @@ class groupdav_principals extends groupdav_handler
*/
function propfind($path,$options,&$files,$user)
{
list(,$principals,$type,$name,$rest) = explode('/',$path,5);
// /principals/users/$name/
// /users/$name/calendar-proxy-read/
// /users/$name/calendar-proxy-write/
// /groups/$name/
// /resources/$resource/
// /__uids__/$uid/.../
switch($type)
{
case 'users':
$files['files'] = $this->propfind_users($name,$rest,$options);
break;
case 'groups':
$files['files'] = $this->propfind_groups($name,$rest,$options);
break;
case 'resources':
$files['files'] = $this->propfind_resources($name,$rest,$options);
break;
case '__uids__':
$files['files'] = $this->propfind_uids($name,$rest,$options);
break;
case '':
$files['files'] = $this->propfind_principals($options);
break;
default:
return '404 Not Found';
}
if (!is_array($files['files']))
{
return $files['files'];
}
return true;
list(,,$id) = explode('/',$path);
if ($id && !($id = $this->accounts->id2name($id)))
{
@ -55,18 +82,20 @@ class groupdav_principals extends groupdav_handler
}
foreach($id ? array($this->accounts->read($id)) : $this->accounts->search(array('type' => 'accounts')) as $account)
{
$props = array(
HTTP_WebDAV_Server::mkprop('displayname',trim($account['account_firstname'].' '.$account['account_lastname'])),
$displayname = $this->translation->convert($account['account_fullname'],
$this->translation->charset(),'utf-8');
$props = array(
HTTP_WebDAV_Server::mkprop('displayname',$displayname),
HTTP_WebDAV_Server::mkprop('getetag',$this->get_etag($account)),
HTTP_WebDAV_Server::mkprop('resourcetype','principal'),
HTTP_WebDAV_Server::mkprop('alternate-URI-set',''),
HTTP_WebDAV_Server::mkprop('principal-URL',$_SERVER['SCRIPT_NAME'].'/principals/'.$account['account_lid']),
HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-home-set',$_SERVER['SCRIPT_NAME'].'/'),
HTTP_WebDAV_Server::mkprop('principal-URL',$this->base_uri.'/principals/'.$account['account_lid']),
HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-home-set',$this->base_uri.$account['account_lid'].'/calendar/'),
HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-user-address-set','MAILTO:'.$account['account_email']),
);
foreach($this->accounts->memberships($account['account_id']) as $gid => $group)
{
$props[] = HTTP_WebDAV_Server::mkprop('group-membership',$_SERVER['SCRIPT_NAME'].'/groups/'.$group);
$props[] = HTTP_WebDAV_Server::mkprop('group-membership',$this->base_uri.'/groups/'.$group);
}
$files['files'][] = array(
'path' => '/principals/'.$account['account_lid'],
@ -74,7 +103,251 @@ class groupdav_principals extends groupdav_handler
);
if ($this->debug > 1) error_log(__METHOD__."($path) path=/principals/".$account['account_lid'].', props='.array2string($props));
}
return true;
return files;
}
/**
* Do propfind in /pricipals/users
*
* @param string $name name of account or empty
* @param string $rest rest of path behind account-name
* @param array $options
* @return array|string array with files or HTTP error code
*/
protected function propfind_users($name,$rest,array $options)
{
//echo "<p>".__METHOD__."($name,$rest,".array2string($options).")</p>\n";
if (empty($name))
{
$files = array();
// add /pricipals/users/ entry
$files[] = $this->add_collection('/principals/users/',array(
HTTP_WebDAV_Server::mkprop('current-user-principal',array(
HTTP_WebDAV_Server::mkprop('href',$this->base_uri.'/principals/'.$GLOBALS['egw_info']['user']['account_lid'].'/'))),
));
if ($options['depth'])
{
// add all users
foreach($this->accounts->search(array('type' => 'accounts')) as $account)
{
$files[] = $this->add_account($account);
}
}
}
else
{
if (!($id = $this->accounts->name2id($name,'account_lid','u')) ||
!($account = $this->accounts->read($id)))
{
return '404 Not Found';
}
switch((string)$rest)
{
case '':
$files[] = $this->add_account($account);
$files[] = $this->add_collection('/principals/users/'.$account['account_lid'].'/calendar-proxy-read');
$files[] = $this->add_collection('/principals/users/'.$account['account_lid'].'/calendar-proxy-write');
break;
case 'calendar-proxy-read':
case 'calendar-proxy-write':
$files = array();
$files[] = $this->add_collection('/principals/users/'.$account['account_lid'].'/'.$rest);
// add proxys
break;
default:
return '404 Not Found';
}
}
return $files;
}
/**
* Do propfind in /pricipals/groups
*
* @param string $name name of group or empty
* @param string $rest rest of path behind account-name
* @param array $options
* @return array|string array with files or HTTP error code
*/
protected function propfind_groups($name,$rest,array $options)
{
//echo "<p>".__METHOD__."($name,$rest,".array2string($options).")</p>\n";
if (empty($name))
{
$files = array();
// add /pricipals/users/ entry
$files[] = $this->add_collection('/principals/groups/',array(
HTTP_WebDAV_Server::mkprop('current-user-principal',array(
HTTP_WebDAV_Server::mkprop('href',$this->base_uri.'/principals/'.$GLOBALS['egw_info']['user']['account_lid'].'/'))),
));
if ($options['depth'])
{
// add all users
foreach($this->accounts->search(array('type' => 'groups')) as $account)
{
$files[] = $this->add_group($account);
}
}
}
else
{
if (!($id = $this->accounts->name2id($name,'account_lid','g')) ||
!($account = $this->accounts->read($id)))
{
return '404 Not Found';
}
switch((string)$rest)
{
case '':
$files[] = $this->add_group($account);
$files[] = $this->add_collection('/principals/groups/'.$account['account_lid'].'/calendar-proxy-read');
$files[] = $this->add_collection('/principals/groups/'.$account['account_lid'].'/calendar-proxy-write');
break;
case 'calendar-proxy-read':
case 'calendar-proxy-write':
$files = array();
$files[] = $this->add_collection('/principals/groups/'.$account['account_lid'].'/'.$rest);
// add proxys
break;
default:
return '404 Not Found';
}
}
return $files;
}
/**
* Add collection of a single account to a collection
*
* @param array $account
* @return array with values for keys 'path' and 'props'
*/
protected function add_account(array $account)
{
//echo "<p>".__METHOD__."(".array2string($account).")</p>\n";
$displayname = $this->translation->convert($account['account_fullname'],
$this->translation->charset(),'utf-8');
$memberships = array();
foreach($this->accounts->memberships($account['account_id']) as $gid => $group)
{
if ($group)
{
$memberships[] = HTTP_WebDAV_Server::mkprop('href',
$this->base_uri.'/principals/groups/'.$group);
}
}
$props = array(
HTTP_WebDAV_Server::mkprop('displayname',$displayname),
HTTP_WebDAV_Server::mkprop('getetag',$this->get_etag($account)),
HTTP_WebDAV_Server::mkprop('resourcetype','principal'),
HTTP_WebDAV_Server::mkprop('alternate-URI-set',array(
HTTP_WebDAV_Server::mkprop('href','MAILTO:'.$account['account_email']))),
HTTP_WebDAV_Server::mkprop('principal-URL',$this->base_uri.'/principals/users/'.$account['account_lid'].'/'),
HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-home-set',array(
HTTP_WebDAV_Server::mkprop('href',$this->base_uri.'/'.$account['account_lid'].'/calendar/'))),
HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-user-address-set',array(
HTTP_WebDAV_Server::mkprop('href','MAILTO:'.$account['account_email']))),
HTTP_WebDAV_Server::mkprop(groupdav::CARDDAV,'addressbook-home-set',array(
HTTP_WebDAV_Server::mkprop('href',$this->base_uri.'/'.$account['account_lid'].'/'))),
HTTP_WebDAV_Server::mkprop('group-member-ship', $memberships),
HTTP_WebDAV_Server::mkprop('principal-URL',array(HTTP_WebDAV_Server::mkprop('href',$this->principalURL))),
);
if ($this->debug > 1) error_log(__METHOD__."($path) path=/principals/users/".$account['account_lid'].', props='.array2string($props));
return array(
'path' => '/principals/users/'.$account['account_lid'].'/',
'props' => $props,
);
}
/**
* Add collection of a single group to a collection
*
* @param array $account
* @return array with values for keys 'path' and 'props'
*/
protected function add_group(array $account)
{
$displayname = $this->translation->convert(lang('Group').' '.$account['account_lid'],
$this->translation->charset(),'utf-8');
$members = array();
foreach($this->accounts->members($account['account_id']) as $gid => $user)
{
if ($user)
{
$members[] = HTTP_WebDAV_Server::mkprop('href',
$this->base_uri.'/principals/users/'.$user);
}
}
$props = array(
HTTP_WebDAV_Server::mkprop('displayname',$displayname),
HTTP_WebDAV_Server::mkprop('getetag',$this->get_etag($account)),
HTTP_WebDAV_Server::mkprop('resourcetype','principal'),
HTTP_WebDAV_Server::mkprop('alternate-URI-set',''),
HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-home-set',array(
HTTP_WebDAV_Server::mkprop('href',$this->base_uri.'/'.$account['account_lid'].'/calendar/'))),
HTTP_WebDAV_Server::mkprop(groupdav::CARDDAV,'addressbook-home-set',array(
HTTP_WebDAV_Server::mkprop('href',$this->base_uri.'/'.$account['account_lid'].'/'))),
HTTP_WebDAV_Server::mkprop('group-member-set', $members),
//HTTP_WebDAV_Server::mkprop('principal-URL',array(self::mkprop('href',$this->principalURL))),
);
$files['files'][] = array(
'path' => '/principals/groups/'.$account['account_lid'].'/',
'props' => $props,
);
if ($this->debug > 1) error_log(__METHOD__."($path) path=/principals/groups/".$account['account_lid'].', props='.array2string($props));
return array(
'path' => '/principals/groups/'.$account['account_lid'].'/',
'props' => $props,
);
}
/**
* Add a collection
*
* @param string $path
* @param array $props=array() extra properties 'resourcetype' is added anyway
* @return array
*/
protected function add_collection($path,$props=array())
{
//echo "<p>".__METHOD__."($path,".array($props).")</p>\n";
$props[] = HTTP_WebDAV_Server::mkprop('resourcetype',array(
HTTP_WebDAV_Server::mkprop('collection',''),
HTTP_WebDAV_Server::mkprop('resourcetype','principal'),
));
return array(
'path' => $path,
'props' => $props,
);
}
/**
* Do propfind of /pricipals
*
* @param string $name name of group or empty
* @param string $rest name of rest of path behind group-name
* @param array $options
* @return array|string array with files or HTTP error code
*/
protected function propfind_principals(array $options)
{
//echo "<p>".__METHOD__."(".array($options).")</p>\n";
$files = array();
$files[] = $this->add_collection('/principals/',array(
HTTP_WebDAV_Server::mkprop('current-user-principal',array(
HTTP_WebDAV_Server::mkprop('href',$this->base_uri.'/principals/users/'.$GLOBALS['egw_info']['user']['account_lid'].'/'))),
));
if ($options['depth'])
{
$options['depth'] = 0;
$files = array_merge($files,$this->propfind_users('','',$options));
$files = array_merge($files,$this->propfind_groups('','',$options));
//$files = array_merge($this->propfind_resources('','',$options));
//$files = array_merge($this->propfind_uids('','',$options));
}
return $files;
}
/**
@ -90,9 +363,12 @@ class groupdav_principals extends groupdav_handler
{
return $account;
}
$name = $GLOBALS['egw']->translation->convert(
trim($account['account_firstname'].' '.$account['account_lastname']),
$GLOBALS['egw']->translation->charset(),'utf-8');
$options['data'] = 'Principal: '.$account['account_lid'].
"\nURL: ".$_SERVER['SCRIPT_NAME'].$options['path'].
"\nName: ".$account['account_firstname'].' '.$account['account_lastname'].
"\nURL: ".$this->base_uri.$options['path'].
"\nName: ".$name.
"\nEmail: ".$account['account_email'].
"\nMemberships: ".implode(', ',$this->accounts->memberships($id))."\n";
$options['mimetype'] = 'text/plain; charset=utf-8';
@ -134,7 +410,8 @@ class groupdav_principals extends groupdav_handler
*/
function read($id)
{
return $this->accounts->read($id);
return false;
//return $this->accounts->read($id);
}
/**
@ -169,6 +446,6 @@ class groupdav_principals extends groupdav_handler
{
$account = $this->read($account);
}
return '"'.$account['account_id'].':'.md5(serialize($account)).'"';
return 'EGw-'.$account['account_id'].':'.md5(serialize($account)).'-wGE';
}
}

View File

@ -263,7 +263,8 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem
// type and size (caller already made sure that path exists)
if (is_dir($fspath)) {
// directory (WebDAV collection)
$info['props'][] = HTTP_WebDAV_Server::mkprop ('resourcetype', HTTP_WebDAV_Server::mkprop('collection',''));
$info['props'][] = HTTP_WebDAV_Server::mkprop ('resourcetype', array(
HTTP_WebDAV_Server::mkprop('collection', '')));
$info['props'][] = HTTP_WebDAV_Server::mkprop ('getcontenttype', 'httpd/unix-directory');
} else {
// plain file (WebDAV resource)
@ -471,4 +472,4 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem
{
return egw_vfs::checkLock($path);
}
}
}