mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-12-27 00:58:56 +01:00
implement and document PATCH
This commit is contained in:
parent
392b8036f4
commit
3e035a70a4
@ -629,13 +629,15 @@ class addressbook_groupdav extends Api\CalDAV\Handler
|
||||
* @param int $id
|
||||
* @param int $user =null account_id of owner, default null
|
||||
* @param string $prefix =null user prefix from path (eg. /ralf from /ralf/addressbook)
|
||||
* @param string $method='PUT' also called for POST and PATCH
|
||||
* @param string $content_type=null
|
||||
* @return mixed boolean true on success, false on failure or string with http status (eg. '404 Not Found')
|
||||
*/
|
||||
function put(&$options,$id,$user=null,$prefix=null)
|
||||
function put(&$options, $id, $user=null, $prefix=null, string $method='PUT', string $content_type=null)
|
||||
{
|
||||
if ($this->debug) error_log(__METHOD__.'('.array2string($options).",$id,$user)");
|
||||
|
||||
$oldContact = $this->_common_get_put_delete('PUT',$options,$id);
|
||||
$oldContact = $this->_common_get_put_delete($method,$options,$id);
|
||||
if (!is_null($oldContact) && !is_array($oldContact))
|
||||
{
|
||||
if ($this->debug) error_log(__METHOD__."(,'$id', $user, '$prefix') returning ".array2string($oldContact));
|
||||
@ -658,7 +660,8 @@ class addressbook_groupdav extends Api\CalDAV\Handler
|
||||
}
|
||||
}
|
||||
$contact = $type === JsContact::MIME_TYPE_JSCARD ?
|
||||
JsContact::parseJsCard($options['content'], $oldContact ?: []) : JsContact::parseJsCardGroup($options['content']);
|
||||
JsContact::parseJsCard($options['content'], $oldContact ?: [], $content_type, $method) :
|
||||
JsContact::parseJsCardGroup($options['content']);
|
||||
|
||||
if (!empty($id) && strpos($id, self::JS_CARDGROUP_ID_PREFIX) === 0)
|
||||
{
|
||||
|
@ -993,6 +993,34 @@ class CalDAV extends HTTP_WebDAV_Server
|
||||
parent::http_PROPFIND('REPORT');
|
||||
}
|
||||
|
||||
/**
|
||||
* REST API PATCH handler
|
||||
*
|
||||
* Currently, only implemented for REST not CalDAV/CardDAV
|
||||
*
|
||||
* @param $options
|
||||
* @param $files
|
||||
* @return string|void
|
||||
*/
|
||||
function PATCH(array &$options)
|
||||
{
|
||||
if (!preg_match('#^application/([^; +]+\+)?json#', $_SERVER['HTTP_CONTENT_TYPE']))
|
||||
{
|
||||
return '501 Not implemented';
|
||||
}
|
||||
return $this->PUT($options, 'PATCH');
|
||||
}
|
||||
|
||||
/**
|
||||
* REST API PATCH handler
|
||||
*
|
||||
* Just calls http_PUT()
|
||||
*/
|
||||
function http_PATCH()
|
||||
{
|
||||
return parent::http_PUT('PATCH');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if client want or sends JSON
|
||||
*
|
||||
@ -1003,7 +1031,7 @@ class CalDAV extends HTTP_WebDAV_Server
|
||||
{
|
||||
if (!isset($type))
|
||||
{
|
||||
$type = in_array($_SERVER['REQUEST_METHOD'], ['PUT', 'POST', 'PROPPATCH']) ?
|
||||
$type = in_array($_SERVER['REQUEST_METHOD'], ['PUT', 'POST', 'PATCH', 'PROPPATCH']) ?
|
||||
$_SERVER['HTTP_CONTENT_TYPE'] : $_SERVER['HTTP_ACCEPT'];
|
||||
}
|
||||
return preg_match('#application/(([^+ ;]+)\+)?json#', $type, $matches) ?
|
||||
@ -1427,7 +1455,7 @@ class CalDAV extends HTTP_WebDAV_Server
|
||||
substr($options['path'], -1) === '/' && self::isJSON())
|
||||
{
|
||||
$_GET['add-member'] = ''; // otherwise we give no Location header
|
||||
return $this->PUT($options);
|
||||
return $this->PUT($options, 'POST');
|
||||
}
|
||||
if ($this->debug) error_log(__METHOD__.'('.array2string($options).')');
|
||||
|
||||
@ -1915,7 +1943,7 @@ class CalDAV extends HTTP_WebDAV_Server
|
||||
* @param array parameter passing array
|
||||
* @return bool true on success
|
||||
*/
|
||||
function PUT(&$options)
|
||||
function PUT(&$options, $method='PUT')
|
||||
{
|
||||
// read the content in a string, if a stream is given
|
||||
if (isset($options['stream']))
|
||||
@ -1934,9 +1962,14 @@ class CalDAV extends HTTP_WebDAV_Server
|
||||
{
|
||||
return '404 Not Found';
|
||||
}
|
||||
// REST API & PATCH only implemented for addressbook currently
|
||||
if ($app !== 'addressbook' && $method === 'PATCH')
|
||||
{
|
||||
return '501 Not implemented';
|
||||
}
|
||||
if (($handler = self::app_handler($app)))
|
||||
{
|
||||
$status = $handler->put($options,$id,$user,$prefix);
|
||||
$status = $handler->put($options, $id, $user, $prefix, $method, $_SERVER['HTTP_CONTENT_TYPE']);
|
||||
|
||||
// set default stati: true --> 204 No Content, false --> should be already handled
|
||||
if (is_bool($status)) $status = $status ? '204 No Content' : '400 Something went wrong';
|
||||
|
@ -60,6 +60,7 @@ abstract class Handler
|
||||
var $method2acl = array(
|
||||
'GET' => Api\Acl::READ,
|
||||
'PUT' => Api\Acl::EDIT,
|
||||
'PATCH' => Api\Acl::EDIT,
|
||||
'DELETE' => Api\Acl::DELETE,
|
||||
);
|
||||
/**
|
||||
|
@ -78,26 +78,32 @@ class JsContact
|
||||
/**
|
||||
* Parse JsCard
|
||||
*
|
||||
* We use strict parsing for "application/jscontact+json" content-type, not for "application/json".
|
||||
* Strict parsing checks objects for proper @type attributes and value attributes, non-strict allows scalar values.
|
||||
*
|
||||
* Non-strict parsing also automatic detects patch for POST requests.
|
||||
*
|
||||
* @param string $json
|
||||
* @param array $old=[] existing contact
|
||||
* @param bool $strict true: check if objects have their proper @type attribute
|
||||
* @param array $old=[] existing contact for patch
|
||||
* @param ?string $content_type=null application/json no strict parsing and automatic patch detection, if method not 'PATCH' or 'PUT'
|
||||
* @param string $method='PUT' 'PUT', 'POST' or 'PATCH'
|
||||
* @return array
|
||||
*/
|
||||
public static function parseJsCard(string $json, array $old=[], bool $strict=true)
|
||||
public static function parseJsCard(string $json, array $old=[], string $content_type=null, $method='PUT')
|
||||
{
|
||||
try
|
||||
{
|
||||
$strict = !isset($content_type) || !preg_match('#^application/json#', $content_type);
|
||||
$data = json_decode($json, true, 10, JSON_THROW_ON_ERROR);
|
||||
|
||||
// check if we have a patch: keys contain slashes
|
||||
if (array_filter(array_keys($data), static function ($key)
|
||||
// check if we use patch: method is PATCH or method is POST AND keys contain slashes
|
||||
if ($method === 'PATCH' || !$strict && $method === 'POST' && array_filter(array_keys($data), static function ($key)
|
||||
{
|
||||
return strpos($key, '/') !== false;
|
||||
}))
|
||||
{
|
||||
// apply patch on JsCard of contact
|
||||
$data = self::patch($data, $old ? self::getJsCard($old, false) : [], !$old);
|
||||
$strict = false;
|
||||
}
|
||||
|
||||
if (!isset($data['uid'])) $data['uid'] = null; // to fail below, if it does not exist
|
||||
@ -951,7 +957,7 @@ class JsContact
|
||||
{
|
||||
throw new \InvalidArgumentException("Invalid email object (requires email attribute): ".json_encode($value, self::JSON_OPTIONS_ERROR));
|
||||
}
|
||||
if (!isset($contact['email']) && $id === 'work' && empty($value['context']['private']))
|
||||
if (!isset($contact['email']) && ($id === 'work' || empty($value['contexts']['private']) || isset($contact['email_home'])))
|
||||
{
|
||||
$contact['email'] = $value['email'];
|
||||
}
|
||||
|
@ -1701,10 +1701,10 @@ class HTTP_WebDAV_Server
|
||||
/**
|
||||
* PUT method handler
|
||||
*
|
||||
* @param void
|
||||
* @param string $method='PUT'
|
||||
* @return void
|
||||
*/
|
||||
function http_PUT()
|
||||
function http_PUT(string $method='PUT')
|
||||
{
|
||||
if ($this->_check_lock_status($this->path)) {
|
||||
$options = Array();
|
||||
@ -1839,7 +1839,7 @@ class HTTP_WebDAV_Server
|
||||
}
|
||||
}
|
||||
|
||||
$stat = $this->PUT($options);
|
||||
$stat = $this->$method($options);
|
||||
|
||||
if ($stat === false) {
|
||||
$stat = "403 Forbidden";
|
||||
|
@ -276,7 +276,37 @@ Location: https://example.org/egroupware/groupdav.php/<username>/addressbook/123
|
||||
```
|
||||
</details>
|
||||
|
||||
* **PUT** requests with a ```Content-Type: application/json``` header allow modifying single resources
|
||||
* **PUT** requests with a ```Content-Type: application/json``` header allow modifying single resources (requires to specify all attributes!)
|
||||
|
||||
* **PATCH** request with a ```Content-Type: application/json``` header allow to modify a single resource by only specifying changed attributes as a [PatchObject](https://www.rfc-editor.org/rfc/rfc8984.html#type-PatchObject)
|
||||
|
||||
<details>
|
||||
<summary>Example: PATCH request to modify a contact with partial data</summary>
|
||||
|
||||
```
|
||||
cat <<EOF | curl -i 'https://example.org/egroupware/groupdav.php/<username>/addressbook/1234' -X PATCH -d @- -H "Content-Type: application/json" --user <username>
|
||||
{
|
||||
"name": [
|
||||
{
|
||||
"@type": "NameComponent",
|
||||
"type": "personal",
|
||||
"value": "Testfirst"
|
||||
},
|
||||
{
|
||||
"@type": "NameComponent",
|
||||
"type": "surname",
|
||||
"value": "Username"
|
||||
}
|
||||
],
|
||||
"fullName": "Testfirst Username",
|
||||
"organizations/org/name": "Test-User.org",
|
||||
"emails/work/email": "test.user@test-user.org"
|
||||
}
|
||||
EOF
|
||||
|
||||
HTTP/1.1 204 No content
|
||||
```
|
||||
</details>
|
||||
|
||||
* **DELETE** requests delete single resources
|
||||
|
||||
@ -289,3 +319,11 @@ use ```<domain-name>:<name>``` like in JsCalendar
|
||||
* top-level objects need a ```@type``` attribute with one of the following values:
|
||||
```NameComponent```, ```Organization```, ```Title```, ```Phone```, ```Resource```, ```File```, ```ContactLanguage```,
|
||||
```Address```, ```StreetComponent```, ```Anniversary```, ```PersonalInformation```
|
||||
|
||||
### ToDos
|
||||
- [x] Addressbook
|
||||
- [ ] update of photos, keys, attachments
|
||||
- [ ] InfoLog
|
||||
- [ ] Calendar
|
||||
- [ ] relatedTo / links
|
||||
- [ ] storing not native supported attributes eg. localization
|
Loading…
Reference in New Issue
Block a user