From b407ea4c8ebadc5da6e6ba99b13e2d6eb1713186 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Lehrke?= Date: Mon, 15 Mar 2010 11:35:07 +0000 Subject: [PATCH] Use iterator approach in GroupDAV; fix various issues --- .../inc/class.addressbook_groupdav.inc.php | 1 - calendar/inc/class.calendar_groupdav.inc.php | 63 ++-- infolog/inc/class.boinfolog.inc.php | 10 +- infolog/inc/class.infolog_bo.inc.php | 34 ++- infolog/inc/class.infolog_groupdav.inc.php | 277 ++++++++++++++---- infolog/inc/class.infolog_so.inc.php | 18 +- phpgwapi/inc/class.groupdav_handler.inc.php | 10 +- 7 files changed, 310 insertions(+), 103 deletions(-) diff --git a/addressbook/inc/class.addressbook_groupdav.inc.php b/addressbook/inc/class.addressbook_groupdav.inc.php index b67377c448..46e8bf54cf 100644 --- a/addressbook/inc/class.addressbook_groupdav.inc.php +++ b/addressbook/inc/class.addressbook_groupdav.inc.php @@ -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; } diff --git a/calendar/inc/class.calendar_groupdav.inc.php b/calendar/inc/class.calendar_groupdav.inc.php index 9b33b1076d..d7803ddc07 100644 --- a/calendar/inc/class.calendar_groupdav.inc.php +++ b/calendar/inc/class.calendar_groupdav.inc.php @@ -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; } /** diff --git a/infolog/inc/class.boinfolog.inc.php b/infolog/inc/class.boinfolog.inc.php index de789586fa..08678c5117 100644 --- a/infolog/inc/class.boinfolog.inc.php +++ b/infolog/inc/class.boinfolog.inc.php @@ -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) { diff --git a/infolog/inc/class.infolog_bo.inc.php b/infolog/inc/class.infolog_bo.inc.php index 88a207af60..73f8b72983 100644 --- a/infolog/inc/class.infolog_bo.inc.php +++ b/infolog/inc/class.infolog_bo.inc.php @@ -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)) { diff --git a/infolog/inc/class.infolog_groupdav.inc.php b/infolog/inc/class.infolog_groupdav.inc.php index 04dd27909f..195f3bf037 100644 --- a/infolog/inc/class.infolog_groupdav.inc.php +++ b/infolog/inc/class.infolog_groupdav.inc.php @@ -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); } diff --git a/infolog/inc/class.infolog_so.inc.php b/infolog/inc/class.infolog_so.inc.php index 95684c8e20..7208a19faa 100644 --- a/infolog/inc/class.infolog_so.inc.php +++ b/infolog/inc/class.infolog_so.inc.php @@ -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 "

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

\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; } diff --git a/phpgwapi/inc/class.groupdav_handler.inc.php b/phpgwapi/inc/class.groupdav_handler.inc.php index d5bfde9eb2..252e8b1af4 100644 --- a/phpgwapi/inc/class.groupdav_handler.inc.php +++ b/phpgwapi/inc/class.groupdav_handler.inc.php @@ -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); }