Use iterator approach in GroupDAV; fix various issues

This commit is contained in:
Jörg Lehrke 2010-03-15 11:35:07 +00:00
parent cd7ae8c813
commit b407ea4c8e
7 changed files with 310 additions and 103 deletions

View File

@ -111,7 +111,6 @@ class addressbook_groupdav extends groupdav_handler
}
// return iterator, calling ourself to return result in chunks
$files['files'] = new groupdav_propfind_iterator($this,$path,$filter,$files['files']);
return true;
}

View File

@ -102,11 +102,10 @@ class calendar_groupdav extends groupdav_handler
if ($this->debug)
{
error_log(__METHOD__."($path,".array2string($options).",,$user,$id)");
$starttime = microtime(true);
}
// ToDo: add parameter to only return id & etag
$cal_filters = array(
$filter = array(
'users' => $user,
'start' => time()-100*24*3600, // default one month back -30 breaks all sync recurrences
'end' => time()+365*24*3600, // default one year into the future +365
@ -115,38 +114,66 @@ class calendar_groupdav extends groupdav_handler
'date_format' => 'server',
);
if ($path == '/calendar/') $cal_filters['show_rejected'] = false;
/*
if ($this->client_shared_uid_exceptions)
if ($path == '/calendar/')
{
$cal_filters['query']['cal_reference'] = 0;
$filter['show_rejected'] = true;
}
*/
else
{
$filter['show_rejected'] = false;
}
// process REPORT filters or multiget href's
if (($id || $options['root']['name'] != 'propfind') && !$this->_report_filters($options,$cal_filters,$id))
if (($id || $options['root']['name'] != 'propfind') && !$this->_report_filters($options,$filter,$id))
{
return false;
}
if ($this->debug > 1)
{
error_log(__METHOD__."($path,,,$user,$id) cal_filters=".
array2string($cal_filters));
error_log(__METHOD__."($path,,,$user,$id) filter=".
array2string($filter));
}
// 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']))
if (!($filter['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;
$filter['calendar_data'] = true;
break;
}
}
}
$events =& $this->bo->search($cal_filters);
// return iterator, calling ourself to return result in chunks
$files['files'] = new groupdav_propfind_iterator($this,$path,$filter,$files['files']);
return true;
}
/**
* 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($path,array $filter,$start=false)
{
if ($this->debug) $starttime = microtime(true);
$calendar_data = $filter['calendar_data'];
unset($filter['calendar_data']);
$files = array();
if (is_array($start))
{
$filter['offset'] = $start[0];
$filter['num_rows'] = $start[1];
}
$events =& $this->bo->search($filter);
if ($events)
{
// get all max user modified times at once
@ -178,8 +205,6 @@ class calendar_groupdav extends groupdav_handler
HTTP_WebDAV_Server::mkprop('getlastmodified', $event['modified']),
HTTP_WebDAV_Server::mkprop('resourcetype',''), // DAVKit requires that attribute!
);
//$props[] = HTTP_WebDAV_Server::mkprop('current-user-principal',array(self::mkprop('href','MAILTO:'.$GLOBALS['egw_info']['user']['email'])));
//$props = self::current_user_privilege_set($props);
//error_log(__FILE__ . __METHOD__ . "Calendar Data : $calendar_data");
if ($calendar_data)
{
@ -191,7 +216,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(
$files[] = array(
'path' => $path.self::get_path($event),
'props' => $props,
);
@ -200,9 +225,9 @@ class calendar_groupdav extends groupdav_handler
if ($this->debug)
{
error_log(__METHOD__."($path) took ".(microtime(true) - $starttime).
' to return '.count($files['files']).' items');
' to return '.count($files).' items');
}
return true;
return $files;
}
/**

View File

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

View File

@ -287,14 +287,22 @@ 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}
* @param int $other uid to check (if info==0) or 0 to check against $this->user
* @return boolean
*/
function check_access( $info,$required_rights )
function check_access($info,$required_rights,$other=0)
{
static $cache = array();
if (!$info)
{
$owner = $other ? $other : $this->user;
$grants = $this->grants[$owner];
return $grants & $required_rights;
}
$info_id = is_array($info) ? $info['info_id'] : $info;
if (isset($cache[$info_id][$required_rights]))
@ -485,9 +493,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)
@ -573,7 +581,10 @@ class infolog_bo
function write(&$values, $check_defaults=true, $touch_modified=true, $user2server=true)
{
//echo "boinfolog::write()values="; _debug_array($values);
if ($status_only = $values['info_id'] && !$this->check_access($values['info_id'],EGW_ACL_EDIT))
if (!$values['info_id'] && !$this->check_access(0,EGW_ACL_EDIT,$values['info_owner'])
&& !$this->check_access(0,EGW_ACL_ADD,$values['info_owner'])) return false;
if (($status_only = $values['info_id']) && !$this->check_access($values['info_id'],EGW_ACL_EDIT))
{
if (!isset($values['info_responsible']))
{
@ -596,7 +607,7 @@ class infolog_bo
if ($values['info_id'] && !$this->check_access($values['info_id'],EGW_ACL_EDIT) && !$status_only ||
!$values['info_id'] && $values['info_id_parent'] && !$this->check_access($values['info_id_parent'],EGW_ACL_ADD))
{
return False;
return false;
}
if ($status_only && !$undelete) // make sure only status gets writen
{
@ -634,7 +645,7 @@ class infolog_bo
}
if ($forcestatus && !in_array($values['info_status'],array('done','billed','cancelled'))) $values['info_status'] = $status;
}
$check_defaults = False;
$check_defaults = false;
}
if ($check_defaults)
{
@ -845,7 +856,6 @@ class infolog_bo
$ret = $this->so->search($query);
if (is_array($ret))
{
foreach ($ret as $id => &$data)
@ -985,8 +995,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)
{
@ -1166,7 +1176,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) != '%')
{
@ -1202,7 +1212,7 @@ class infolog_bo
$old_categories = explode(',',$old_infolog['info_cat']);
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))
{

View File

@ -23,6 +23,17 @@ class infolog_groupdav extends groupdav_handler
*/
var $bo;
var $filter_prop2infolog = array(
'SUMMARY' => 'info_subject',
'UID' => 'info_uid',
'DTSTART' => 'info_startdate',
'DUE' => 'info_enddate',
'DESCRIPTION' => 'info_des',
'STATUS' => 'info_status',
'PRIORITY' => 'info_priority',
'LOCATION' => 'info_location',
'COMPLETED' => 'info_datecompleted',
);
/**
* Constructor
*
@ -71,73 +82,102 @@ class infolog_groupdav extends groupdav_handler
*/
function propfind($path,$options,&$files,$user,$id='')
{
$starttime = microtime(true);
$myself = ($user == $GLOBALS['egw_info']['user']['account_id']);
if ($path == '/infolog/')
{
$task_filter= 'open';
$task_filter= 'own';
}
else
{
$task_filter= 'own' . ($myself?'':'-open');
}
if ($options['filters'])
{
foreach($options['filters'] as $filter)
if ($myself)
{
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;
}
}
$task_filter = 'open';
}
}
// 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)
else
{
if ($prop['name'] == 'calendar-data')
{
$calendar_data = true;
break;
}
$task_filter = 'user' . $user. '-open';
}
}
// todo add a filter to limit how far back entries from the past get synced
$filter = array(
'info_type' => 'task',
'filter' => $task_filter,
);
//if (!$myself) $filter['info_owner'] = $user;
// process REPORT filters or multiget href's
if (($id || $options['root']['name'] != 'propfind') && !$this->_report_filters($options,$filter,$id))
{
return false;
}
if ($this->debug > 1)
{
error_log(__METHOD__."($path,,,$user,$id) filter=".
array2string($filter));
}
if ($id) $filter['info_id'] = $id; // propfind on a single id
// check if we have to return the full calendar data or just the etag's
if (!($filter['calendar_data'] = $options['props'] == 'all' &&
$options['root']['ns'] == groupdav::CALDAV) && is_array($options['props']))
{
foreach($options['props'] as $prop)
{
if ($prop['name'] == 'calendar-data')
{
$filter['calendar_data'] = true;
break;
}
}
}
// ToDo: add parameter to only return id & etag
if (($tasks =& $this->bo->search($params=array(
'order' => 'info_datemodified',
'sort' => 'DESC',
'filter' => $task_filter, // filter my: entries user is responsible for,
// filter own: entries the user own or is responsible for
'date_format' => 'server',
// return iterator, calling ourself to return result in chunks
$files['files'] = new groupdav_propfind_iterator($this,$path,$filter,$files['files']);
return true;
}
/**
* 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($path,array $filter,$start=false)
{
if ($this->debug) $starttime = microtime(true);
if (($calendar_data = $filter['calendar_data']))
{
$handler = self::_get_handler();
}
unset($filter['calendar_data']);
$task_filter = $filter['filter'];
unset($filter['filter']);
$query = array(
'order' => 'info_datemodified',
'sort' => 'DESC',
'filter' => $task_filter,
'date_format' => 'server',
'col_filter' => $filter,
))))
);
if (is_array($start))
{
$query['start'] = $offset = $start[0];
$query['num_rows'] = $start[1];
}
else
{
$offset = 0;
}
$files = array();
// ToDo: add parameter to only return id & etag
$tasks =& $this->bo->search($query);
if ($tasks && $offset == $query['start'])
{
foreach($tasks as &$task)
{
@ -152,7 +192,6 @@ class infolog_groupdav extends groupdav_handler
);
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);
@ -161,16 +200,111 @@ class infolog_groupdav extends groupdav_handler
{
$props[] = HTTP_WebDAV_Server::mkprop('getcontentlength', ''); // expensive to calculate and no CalDAV client uses it
}
$files['files'][] = array(
$files[] = array(
'path' => $path.self::get_path($task),
'props' => $props,
);
}
}
if ($this->debug) error_log(__METHOD__."($path) took ".(microtime(true) - $starttime).' to return '.count($files['files']).' items');
if ($this->debug) error_log(__METHOD__."($path) took ".(microtime(true) - $starttime).' to return '.count($files).' items');
return $files;
}
/**
* Process the filters from the CalDAV REPORT request
*
* @param array $options
* @param array &$cal_filters
* @param string $id
* @return boolean true if filter could be processed, false for requesting not here supported VTODO items
*/
function _report_filters($options,&$cal_filters,$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 'VTODO':
//case 'VCALENDAR':
break;
default:
return false;
}
break;
case 'prop-filter':
if ($this->debug > 1) error_log(__METHOD__."($options[path],...) prop-filter='{$filter['attrs']['name']}'");
$prop_filter = $filter['attrs']['name'];
break;
case 'text-match':
if ($this->debug > 1) error_log(__METHOD__."($options[path],...) text-match: $prop_filter='{$filter['data']}'");
if (!isset($this->filter_prop2infolog[strtoupper($prop_filter)]))
{
if ($this->debug) error_log(__METHOD__."($options[path],".array2string($options).",...) unknown property '$prop_filter' --> ignored");
}
else
{
$cal_filters[$this->filter_prop2infolog[strtoupper($prop_filter)]] = $filter['data'];
}
unset($prop_filter);
break;
case 'param-filter':
if ($this->debug) error_log(__METHOD__."($options[path],...) param-filter='{$filter['attrs']['name']}' not (yet) implemented!");
break;
case 'time-range':
if ($this->debug > 1) error_log(__FILE__ . __METHOD__."($options[path],...) time-range={$filter['attrs']['start']}-{$filter['attrs']['end']}");
$cal_filters[] = 'info_startdate >= ' . $filter['attrs']['start'];
$cal_filters[] = 'info_startdate <= ' . $filter['attrs']['end'];
break;
default:
if ($this->debug) error_log(__METHOD__."($options[path],".array2string($options).",...) unknown filter --> ignored");
break;
}
}
}
// multiget or propfind on a given id
//error_log(__FILE__ . __METHOD__ . "multiget of propfind:");
if ($options['root']['name'] == 'calendar-multiget' || $id)
{
$ids = array();
if ($id)
{
if (is_numeric($id))
{
$cal_filters['info_id'] = $id;
}
else
{
$cal_filters['info_uid'] = basename($id,'.ics');
}
}
else // fetch all given url's
{
foreach($options['other'] as $option)
{
if ($option['name'] == 'href')
{
$parts = explode('/',$option['data']);
if (is_numeric($id = basename(array_pop($parts),'.ics'))) $ids[] = $id;
}
}
if ($ids)
{
$cal_filters[] = 'info_id IN ('.implode(',',array_map(create_function('$n','return (int)$n;'),$ids)).')';
}
}
if ($this->debug > 1) error_log(__METHOD__ ."($options[path],...,$id) calendar-multiget: ids=".implode(',',$ids));
}
return true;
}
/**
* Handle get request for a task / infolog entry
*
@ -231,14 +365,14 @@ class infolog_groupdav extends groupdav_handler
else
{
// to be safe
$taskId = -1;
$taskId = 0;
$retval = '201 Created';
}
}
else
{
// new entry
$taskId = -1;
$taskId = 0;
$retval = '201 Created';
}
}
@ -249,7 +383,42 @@ class infolog_groupdav extends groupdav_handler
return '403 Forbidden';
}
if ($infoId != $taskId) $retval = '201 Created';
/*
if (strstr($option['path'], '/infolog/') === 0)
{
$task_filter= 'own';
}
else
{
if ($myself)
{
$task_filter = 'open';
}
else
{
$task_filter = 'user' . $user. '-open';
}
}
$query = array(
'order' => 'info_datemodified',
'sort' => 'DESC',
'filter' => $task_filter,
'date_format' => 'server',
'col_filter' => array('info_id' => $infoId),
);
if (!$this->bo->search($query))
{
$retval = '410 Gone';
}
else
*/
if ($infoId != $taskId)
{
$retval = '201 Created';
}
header('ETag: '.$this->get_etag($infoId));
if ($retval !== true)
@ -285,7 +454,8 @@ class infolog_groupdav extends groupdav_handler
*/
function read($id)
{
return $this->bo->read($id,false,'server');
if (is_numeric($id)) return $this->bo->read($id,false,'server');
return null;
}
/**
@ -297,6 +467,7 @@ class infolog_groupdav extends groupdav_handler
*/
function check_access($acl,$task)
{
if (is_null($task)) return true;
return $this->bo->check_access($task,$acl);
}

View File

@ -129,8 +129,8 @@ class infolog_so
// ACL only on public entrys || $owner granted _PRIVATE
(!!($this->grants[$owner] & $required_rights) ||
$this->is_responsible($info) && // implicite rights for responsible user(s) and his memberships
($required_rights == EGW_ACL_READ || $required_rights == EGW_ACL_ADD || $implicit_edit && $required_rights == EGW_ACL_EDIT)) &&
($info['info_access'] == 'public' || !!($this->grants[$owner] & EGW_ACL_PRIVATE));
($required_rights == EGW_ACL_READ || $required_rights == EGW_ACL_ADD || $implicit_edit && $required_rights == EGW_ACL_EDIT)); // &&
//($info['info_access'] == 'public' || !!($this->grants[$owner] & EGW_ACL_PRIVATE));
//echo "<p align=right>check_access(info_id=$info_id,requited=$required_rights,implicit_edit=$implicit_edit) owner=$owner, responsible=(".implode(',',$info['info_responsible'])."): access".($access_ok?"Ok":"Denied")."</p>\n";
return $access_ok;
@ -151,9 +151,9 @@ class infolog_so
$responsible = array();
foreach((array)$users as $user)
{
$responsible = array_merge($responsible,
$user > 0 ? $GLOBALS['egw']->accounts->memberships($user,true) :
$GLOBALS['egw']->accounts->members($user,true));
$responsible = array_merge($responsible,(array)
($user > 0 ? $GLOBALS['egw']->accounts->memberships($user,true) :
$GLOBALS['egw']->accounts->members($user,true)));
$responsible[] = $user;
}
if (is_array($users))
@ -225,7 +225,7 @@ class infolog_so
}
$public_access = $this->db->expression($this->info_table,array('info_owner' => $public_user_list));
// implicit read-rights for responsible user
$filtermethod .= " OR (".$this->responsible_filter($this->user)." AND info_access='public')";
$filtermethod .= " OR (".$this->responsible_filter($this->user).')';
// private: own entries plus the one user is responsible for
if ($filter == 'private' || $filter == 'privat' || $filter == 'own')
@ -526,11 +526,11 @@ class infolog_so
*/
function write($values,$check_modified=0) // did _not_ ensure ACL
{
if (isset($GLOBALS['egw_info']['user']['preferences']['syncml']['minimum_uid_length']))
if (isset($GLOBALS['egw_info']['user']['preferences']['syncml']['minimum_uid_length']))
{
$minimum_uid_length = $GLOBALS['egw_info']['user']['preferences']['syncml']['minimum_uid_length'];
}
else
}
else
{
$minimum_uid_length = 8;
}

View File

@ -479,14 +479,14 @@ class groupdav_propfind_iterator implements Iterator
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
// try query further files via propfind callback of handler and store result in $this->files
$this->files = $this->handler->propfind_callback($this->path,$this->filter,array($this->start,self::CHUNK_SIZE));
if (!is_array($this->files) || !($entries = count($this->files)))
{
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->path,$this->filter,array($this->start,self::CHUNK_SIZE));
$this->start += self::CHUNK_SIZE;
$this->start += $entries;
reset($this->files);
if ($this->debug) error_log(__METHOD__."() returning ".array2string(current($this->files) !== false));
@ -504,7 +504,7 @@ 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;
$files = $this->handler->propfind_callback($this->path,$this->filter,array($this->start,self::CHUNK_SIZE));
$this->files = $this->common_files + $files;
$this->files = array_merge($this->common_files, $files);
$this->start += self::CHUNK_SIZE;
reset($this->files);
}