forked from extern/egroupware
finished REST API for contacts modulo docu and bugs ;)
- JsCardGroup now used for distribution lists - responses are not in "responses" attribute (no longer in root of object) - fix sometimes empty / different members between PROPFIND/REPORT/JSON-GET and GET of group (caused by wrongly implemented limit to given AB) - JSON pretty-print only if requested by Accept: application/pretty+json - fix invalid JSON for errors (caused by opening {"responses": already sent
This commit is contained in:
parent
3a88aedce1
commit
e9998161a5
@ -350,7 +350,7 @@ class addressbook_groupdav extends Api\CalDAV\Handler
|
||||
{
|
||||
foreach($lists as $list)
|
||||
{
|
||||
$list[self::$path_attr] = $list['list_carddav_name'];
|
||||
$list[self::$path_attr] = $is_jscontact ? 'list-'.$list['list_id'] : $list['list_carddav_name'];
|
||||
$etag = $list['list_id'].':'.$list['list_etag'];
|
||||
// for all-in-one addressbook, add selected ABs to etag
|
||||
if (isset($filter['owner']) && is_array($filter['owner']))
|
||||
@ -365,7 +365,7 @@ class addressbook_groupdav extends Api\CalDAV\Handler
|
||||
);
|
||||
if ($address_data)
|
||||
{
|
||||
$content = $is_jscontact ? JsContact::getJsCardGroup($list) : $handler->getGroupVCard($list);
|
||||
$content = $is_jscontact ? JsContact::getJsCardGroup($list, false) : $handler->getGroupVCard($list);
|
||||
$props['getcontentlength'] = bytes($content);
|
||||
$props['address-data'] = Api\CalDAV::mkprop(Api\CalDAV::CARDDAV, 'address-data', $content);
|
||||
}
|
||||
@ -597,10 +597,10 @@ class addressbook_groupdav extends Api\CalDAV\Handler
|
||||
return $contact;
|
||||
}
|
||||
// jsContact or vCard
|
||||
if (Api\CalDAV::isJSON())
|
||||
if (($type=Api\CalDAV::isJSON()))
|
||||
{
|
||||
$options['data'] = $contact['list_id'] ? JsContact::getJsCardGroup($contact) :
|
||||
JsContact::getJsCard($contact);
|
||||
$options['data'] = $contact['list_id'] ? JsContact::getJsCardGroup($contact, $type) :
|
||||
JsContact::getJsCard($contact, $type);
|
||||
$options['mimetype'] = ($contact['list_id'] ? JsContact::MIME_TYPE_JSCARDGROUP :
|
||||
JsContact::MIME_TYPE_JSCARD).';charset=utf-8';
|
||||
}
|
||||
@ -1057,16 +1057,24 @@ class addressbook_groupdav extends Api\CalDAV\Handler
|
||||
$non_deleted_tids = array_keys($tids);
|
||||
}
|
||||
$keys = ['tid' => $non_deleted_tids];
|
||||
|
||||
// with REST/JSON we only use our id, but DELETE request has neither Accept nor Content-Type header to detect JSON request
|
||||
if ((string)$id === (string)(int)$id)
|
||||
if (preg_match('/^(list-)?(\d+)$/', $id, $matches))
|
||||
{
|
||||
$keys['id'] = $id;
|
||||
if (!empty($matches[1]))
|
||||
{
|
||||
$keys = ['list_id' => $matches[2]];
|
||||
}
|
||||
else
|
||||
{
|
||||
$keys['id'] = $id;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$keys[self::$path_attr] = $id;
|
||||
}
|
||||
$contact = $this->bo->read($keys);
|
||||
$contact = isset($keys['list_id']) ? false: $this->bo->read($keys);
|
||||
|
||||
// if contact not found and accounts stored NOT like contacts, try reading it without path-extension as id
|
||||
if (is_null($contact) && $this->bo->so_accounts && ($c = $this->bo->read($test=basename($id, '.vcf'))))
|
||||
@ -1086,12 +1094,13 @@ class addressbook_groupdav extends Api\CalDAV\Handler
|
||||
$limit_in_ab[] = $GLOBALS['egw_info']['user']['account_id'];
|
||||
}
|
||||
/* we are currently not syncing distribution-lists/groups to /addressbook/ as
|
||||
* Apple clients use that only as directory gateway
|
||||
elseif ($account_lid == 'addressbook') // /addressbook/ contains all readably contacts
|
||||
* Apple clients use that only as directory gateway*/
|
||||
elseif (Api\CalDAV::isJSON() && $account_lid == 'addressbook') // /addressbook/ contains all readably contacts
|
||||
{
|
||||
$limit_in_ab = array_keys($this->bo->grants);
|
||||
}*/
|
||||
if (!$contact && ($contact = $this->bo->read_lists(array('list_'.self::$path_attr => $id),'contact_uid',$limit_in_ab)))
|
||||
}
|
||||
if (!$contact && ($contact = $this->bo->read_lists(isset($keys['list_id']) ? $keys :
|
||||
['list_'.self::$path_attr => $id],'contact_uid',$limit_in_ab)))
|
||||
{
|
||||
$contact = array_shift($contact);
|
||||
$contact['n_fn'] = $contact['n_family'] = $contact['list_name'];
|
||||
|
@ -994,9 +994,9 @@ class CalDAV extends HTTP_WebDAV_Server
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if clients want's or sends JSON
|
||||
* Check if client want or sends JSON
|
||||
*
|
||||
* @return bool
|
||||
* @return bool|string false: no json, true: application/json, string: application/(string)+json
|
||||
*/
|
||||
public static function isJSON(string $type=null)
|
||||
{
|
||||
@ -1005,7 +1005,8 @@ class CalDAV extends HTTP_WebDAV_Server
|
||||
$type = in_array($_SERVER['REQUEST_METHOD'], ['PUT', 'POST', 'PROPPATCH']) ?
|
||||
$_SERVER['HTTP_CONTENT_TYPE'] : $_SERVER['HTTP_ACCEPT'];
|
||||
}
|
||||
return (bool)preg_match('#application/([^+ ;]+\+)?json#', $type);
|
||||
return preg_match('#application/(([^+ ;]+)\+)?json#', $type, $matches) ?
|
||||
(empty($matches[1]) ? true : $matches[2]) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1021,9 +1022,9 @@ class CalDAV extends HTTP_WebDAV_Server
|
||||
$id = $app = $user = null;
|
||||
if (!$this->_parse_path($options['path'],$id,$app,$user) || $app == 'principals')
|
||||
{
|
||||
if (self::isJSON())
|
||||
if (($json = self::isJSON()))
|
||||
{
|
||||
return $this->jsonIndex($options);
|
||||
return $this->jsonIndex($options, $json === 'pretty');
|
||||
}
|
||||
return $this->autoindex($options);
|
||||
}
|
||||
@ -1048,7 +1049,7 @@ class CalDAV extends HTTP_WebDAV_Server
|
||||
{
|
||||
if (!$pretty)
|
||||
{
|
||||
return self::json_encode($data, self::JSON_OPTIONS);
|
||||
return json_encode($data, self::JSON_OPTIONS);
|
||||
}
|
||||
return preg_replace('/: {\n\s*(.*?)\n\s*(},?\n)/', ': { $1 $2',
|
||||
json_encode($data, self::JSON_OPTIONS_PRETTY));
|
||||
@ -1066,9 +1067,10 @@ class CalDAV extends HTTP_WebDAV_Server
|
||||
* }
|
||||
*
|
||||
* @param array $options
|
||||
* @param bool $pretty =false true: pretty-print JSON
|
||||
* @return bool|string|void
|
||||
*/
|
||||
protected function jsonIndex(array $options)
|
||||
protected function jsonIndex(array $options, bool $pretty)
|
||||
{
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
$is_addressbook = strpos($options['path'], '/addressbook') !== false;
|
||||
@ -1118,9 +1120,8 @@ class CalDAV extends HTTP_WebDAV_Server
|
||||
{
|
||||
return $ret; // no collection
|
||||
}
|
||||
|
||||
echo "{\n";
|
||||
$prefix = " ";
|
||||
// set start as prefix, to no have it in front of exceptions
|
||||
$prefix = "{\n\t\"responses\": {\n";
|
||||
foreach($files['files'] as $resource)
|
||||
{
|
||||
$path = $resource['path'];
|
||||
@ -1129,10 +1130,6 @@ class CalDAV extends HTTP_WebDAV_Server
|
||||
{
|
||||
echo 'null'; // deleted in sync-report
|
||||
}
|
||||
/*elseif (isset($resource['props']['address-data']))
|
||||
{
|
||||
echo $resource['props']['address-data']['val'];
|
||||
}*/
|
||||
else
|
||||
{
|
||||
$props = $propfind_options['props'] === 'all' ? $resource['props'] :
|
||||
@ -1146,17 +1143,24 @@ class CalDAV extends HTTP_WebDAV_Server
|
||||
{
|
||||
$props = current($props)['val'];
|
||||
}
|
||||
echo self::json_encode($props);
|
||||
echo self::json_encode($props, $pretty);
|
||||
}
|
||||
$prefix = ",\n ";
|
||||
$prefix = ",\n";
|
||||
}
|
||||
// add sync-token to response
|
||||
// happens with an empty response
|
||||
if ($prefix !== ",\n")
|
||||
{
|
||||
echo $prefix;
|
||||
$prefix = ",\n";
|
||||
}
|
||||
echo "\n\t}";
|
||||
// add sync-token and more-results to response
|
||||
if (isset($files['sync-token']))
|
||||
{
|
||||
echo $prefix.'"sync-token": '.json_encode(!is_callable($files['sync-token']) ? $files['sync-token'] :
|
||||
echo $prefix."\t".'"sync-token": '.json_encode(!is_callable($files['sync-token']) ? $files['sync-token'] :
|
||||
call_user_func_array($files['sync-token'], (array)$files['sync-token-params']), JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
echo "\n}\n";
|
||||
echo "\n}";
|
||||
|
||||
// exit now, so WebDAV::GET does NOT add Content-Type: application/octet-stream
|
||||
exit;
|
||||
|
@ -714,7 +714,7 @@ abstract class Handler
|
||||
{
|
||||
if (Api\CalDAV::isJSON())
|
||||
{
|
||||
$error = ",\n".' "more-results": true';
|
||||
$error = ",\n\t".'"more-results": true';
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -30,7 +30,7 @@ class JsContact
|
||||
* Get jsCard for given contact
|
||||
*
|
||||
* @param int|array $contact
|
||||
* @param bool $encode=true true: JSON encode, false: return raw data eg. from listing
|
||||
* @param bool|"pretty" $encode=true true: JSON encode, "pretty": JSON encode with pretty-print, false: return raw data eg. from listing
|
||||
* @return string|array
|
||||
* @throws Api\Exception\NotFound
|
||||
*/
|
||||
@ -68,13 +68,13 @@ class JsContact
|
||||
'url' => !empty($contact['url']) ? ['resource' => $contact['url'], 'type' => 'uri', 'contexts' => ['work' => true]] : null,
|
||||
'url_home' => !empty($contact['url_home']) ? ['resource' => $contact['url_home'], 'type' => 'uri', 'contexts' => ['private' => true]] : null,
|
||||
]),
|
||||
'addresses' => [
|
||||
'addresses' => array_filter([
|
||||
'work' => self::address($contact, 'work', 1), // as it's the more prominent in our UI
|
||||
'home' => self::address($contact, 'home'),
|
||||
],
|
||||
]),
|
||||
'photos' => self::photos($contact),
|
||||
'anniversaries' => self::anniversaries($contact),
|
||||
'notes' => [self::localizedString($contact['note'])],
|
||||
'notes' => empty($contact['note']) ? null : [self::localizedString($contact['note'])],
|
||||
'categories' => self::categories($contact['cat_id']),
|
||||
'egroupware.org/customfields' => self::customfields($contact),
|
||||
'egroupware.org/assistant' => $contact['assistent'],
|
||||
@ -82,7 +82,7 @@ class JsContact
|
||||
]);
|
||||
if ($encode)
|
||||
{
|
||||
return Api\CalDAV::json_encode($data, self::JSON_OPTIONS_ERROR);
|
||||
return Api\CalDAV::json_encode($data, $encode === "pretty");
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
@ -474,16 +474,17 @@ class JsContact
|
||||
$js2attr = self::$jsAddress2attr;
|
||||
if ($type === 'work') $js2attr += self::$jsAddress2workAttr;
|
||||
|
||||
$address = array_map(static function($attr) use ($contact, $prefix)
|
||||
$address = array_filter(array_map(static function($attr) use ($contact, $prefix)
|
||||
{
|
||||
return $contact[$prefix.$attr];
|
||||
}, $js2attr) + [
|
||||
'street' => self::streetComponents($contact[$prefix.'street'], $contact[$prefix.'street2']),
|
||||
]);
|
||||
// only add contexts and preference to non-empty address
|
||||
return !$address ? [] : $address+[
|
||||
'contexts' => [$type => true],
|
||||
'pref' => $preference,
|
||||
];
|
||||
|
||||
return array_filter($address);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -984,33 +985,35 @@ class JsContact
|
||||
* Get jsCardGroup for given group
|
||||
*
|
||||
* @param int|array $group
|
||||
* @return string
|
||||
* @param bool|"pretty" $encode=true true: JSON, "pretty": JSON pretty-print, false: array
|
||||
* @return array|string
|
||||
* @throws Api\Exception\NotFound
|
||||
*/
|
||||
public static function getJsCardGroup($group)
|
||||
public static function getJsCardGroup($group, $encode=true)
|
||||
{
|
||||
if (is_scalar($group) && !($group = self::getContacts()->read_lists($group)))
|
||||
{
|
||||
throw new Api\Exception\NotFound();
|
||||
}
|
||||
/*
|
||||
$vCard = new Horde_Icalendar_Vcard($version);
|
||||
$vCard->setAttribute('PRODID','-//EGroupware//NONSGML EGroupware Addressbook '.$GLOBALS['egw_info']['apps']['api']['version'].'//'.
|
||||
strtoupper($GLOBALS['egw_info']['user']['preferences']['common']['lang']));
|
||||
|
||||
$vCard->setAttribute('N',$list['list_name'],array(),true,array($list['list_name'],'','','',''));
|
||||
$vCard->setAttribute('FN',$list['list_name']);
|
||||
|
||||
$vCard->setAttribute('X-ADDRESSBOOKSERVER-KIND','group');
|
||||
foreach($list['members'] as $uid)
|
||||
$data = array_filter([
|
||||
'uid' => $group['list_uid'],
|
||||
'name' => $group['list_name'],
|
||||
'card' => self::getJsCard([
|
||||
'uid' => $group['list_uid'],
|
||||
'n_fn' => $group['list_name'], // --> fullName
|
||||
'modified' => $group['list_modified'], // no other way to send modification date
|
||||
], false),
|
||||
'members' => [],
|
||||
]);
|
||||
foreach($group['members'] as $uid)
|
||||
{
|
||||
$vCard->setAttribute('X-ADDRESSBOOKSERVER-MEMBER','urn:uuid:'.$uid);
|
||||
$data['members'][$uid] = true;
|
||||
}
|
||||
$vCard->setAttribute('REV',Api\DateTime::to($list['list_modified'],'Y-m-d\TH:i:s\Z'));
|
||||
$vCard->setAttribute('UID',$list['list_uid']);
|
||||
*/
|
||||
|
||||
return Api\CalDAV::json_encode($group, self::JSON_OPTIONS_ERROR);
|
||||
if ($encode)
|
||||
{
|
||||
$data = Api\CalDAV::json_encode($data, $encode === 'pretty');
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -810,14 +810,14 @@ class Sql extends Api\Storage
|
||||
{
|
||||
if ($limit_in_ab)
|
||||
{
|
||||
$in_ab_join = " JOIN $this->lists_table ON $this->lists_table.list_id=$this->ab2list_table.list_id AND $this->lists_table.";
|
||||
$in_ab_join = " JOIN $this->lists_table ON $this->lists_table.list_id=$this->ab2list_table.list_id AND ";
|
||||
if (!is_bool($limit_in_ab))
|
||||
{
|
||||
$in_ab_join .= $this->db->expression($this->lists_table, array('list_owner'=>$limit_in_ab));
|
||||
$in_ab_join .= $this->db->expression($this->table_name, $this->table_name.'.', ['contact_owner' => $limit_in_ab]);
|
||||
}
|
||||
else
|
||||
{
|
||||
$in_ab_join .= "list_owner=$this->table_name.contact_owner";
|
||||
$in_ab_join .= "$this->lists_table.list_owner=$this->table_name.contact_owner";
|
||||
}
|
||||
}
|
||||
foreach($this->db->select($this->ab2list_table,"$this->ab2list_table.list_id,$this->table_name.$member_attr",
|
||||
|
@ -1209,7 +1209,8 @@ class Storage
|
||||
*
|
||||
* @param array $keys column-name => value(s) pairs, eg. array('list_uid'=>$uid)
|
||||
* @param string $member_attr ='contact_uid' null: no members, 'contact_uid', 'contact_id', 'caldav_name' return members as that attribute
|
||||
* @param boolean $limit_in_ab =false if true only return members from the same owners addressbook
|
||||
* @param boolean|int|array $limit_in_ab =false if true only return members from the same owners addressbook,
|
||||
* if int|array only return members from the given owners addressbook(s)
|
||||
* @return array with list_id => array(list_id,list_name,list_owner,...) pairs
|
||||
*/
|
||||
function read_lists($keys,$member_attr=null,$limit_in_ab=false)
|
||||
|
Loading…
Reference in New Issue
Block a user