mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-12-26 16:48:49 +01:00
implement and document PATCH
This commit is contained in:
parent
1280de46d6
commit
e640873fc0
@ -629,13 +629,15 @@ class addressbook_groupdav extends Api\CalDAV\Handler
|
|||||||
* @param int $id
|
* @param int $id
|
||||||
* @param int $user =null account_id of owner, default null
|
* @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 $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')
|
* @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)");
|
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 (!is_null($oldContact) && !is_array($oldContact))
|
||||||
{
|
{
|
||||||
if ($this->debug) error_log(__METHOD__."(,'$id', $user, '$prefix') returning ".array2string($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 ?
|
$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)
|
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');
|
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
|
* Check if client want or sends JSON
|
||||||
*
|
*
|
||||||
@ -1003,7 +1031,7 @@ class CalDAV extends HTTP_WebDAV_Server
|
|||||||
{
|
{
|
||||||
if (!isset($type))
|
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'];
|
$_SERVER['HTTP_CONTENT_TYPE'] : $_SERVER['HTTP_ACCEPT'];
|
||||||
}
|
}
|
||||||
return preg_match('#application/(([^+ ;]+)\+)?json#', $type, $matches) ?
|
return preg_match('#application/(([^+ ;]+)\+)?json#', $type, $matches) ?
|
||||||
@ -1427,7 +1455,7 @@ class CalDAV extends HTTP_WebDAV_Server
|
|||||||
substr($options['path'], -1) === '/' && self::isJSON())
|
substr($options['path'], -1) === '/' && self::isJSON())
|
||||||
{
|
{
|
||||||
$_GET['add-member'] = ''; // otherwise we give no Location header
|
$_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).')');
|
if ($this->debug) error_log(__METHOD__.'('.array2string($options).')');
|
||||||
|
|
||||||
@ -1915,7 +1943,7 @@ class CalDAV extends HTTP_WebDAV_Server
|
|||||||
* @param array parameter passing array
|
* @param array parameter passing array
|
||||||
* @return bool true on success
|
* @return bool true on success
|
||||||
*/
|
*/
|
||||||
function PUT(&$options)
|
function PUT(&$options, $method='PUT')
|
||||||
{
|
{
|
||||||
// read the content in a string, if a stream is given
|
// read the content in a string, if a stream is given
|
||||||
if (isset($options['stream']))
|
if (isset($options['stream']))
|
||||||
@ -1934,9 +1962,14 @@ class CalDAV extends HTTP_WebDAV_Server
|
|||||||
{
|
{
|
||||||
return '404 Not Found';
|
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)))
|
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
|
// 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';
|
if (is_bool($status)) $status = $status ? '204 No Content' : '400 Something went wrong';
|
||||||
|
@ -60,6 +60,7 @@ abstract class Handler
|
|||||||
var $method2acl = array(
|
var $method2acl = array(
|
||||||
'GET' => Api\Acl::READ,
|
'GET' => Api\Acl::READ,
|
||||||
'PUT' => Api\Acl::EDIT,
|
'PUT' => Api\Acl::EDIT,
|
||||||
|
'PATCH' => Api\Acl::EDIT,
|
||||||
'DELETE' => Api\Acl::DELETE,
|
'DELETE' => Api\Acl::DELETE,
|
||||||
);
|
);
|
||||||
/**
|
/**
|
||||||
|
@ -78,26 +78,32 @@ class JsContact
|
|||||||
/**
|
/**
|
||||||
* Parse JsCard
|
* 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 string $json
|
||||||
* @param array $old=[] existing contact
|
* @param array $old=[] existing contact for patch
|
||||||
* @param bool $strict true: check if objects have their proper @type attribute
|
* @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
|
* @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
|
try
|
||||||
{
|
{
|
||||||
|
$strict = !isset($content_type) || !preg_match('#^application/json#', $content_type);
|
||||||
$data = json_decode($json, true, 10, JSON_THROW_ON_ERROR);
|
$data = json_decode($json, true, 10, JSON_THROW_ON_ERROR);
|
||||||
|
|
||||||
// check if we have a patch: keys contain slashes
|
// check if we use patch: method is PATCH or method is POST AND keys contain slashes
|
||||||
if (array_filter(array_keys($data), static function ($key)
|
if ($method === 'PATCH' || !$strict && $method === 'POST' && array_filter(array_keys($data), static function ($key)
|
||||||
{
|
{
|
||||||
return strpos($key, '/') !== false;
|
return strpos($key, '/') !== false;
|
||||||
}))
|
}))
|
||||||
{
|
{
|
||||||
// apply patch on JsCard of contact
|
// apply patch on JsCard of contact
|
||||||
$data = self::patch($data, $old ? self::getJsCard($old, false) : [], !$old);
|
$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
|
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));
|
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'];
|
$contact['email'] = $value['email'];
|
||||||
}
|
}
|
||||||
|
@ -1701,10 +1701,10 @@ class HTTP_WebDAV_Server
|
|||||||
/**
|
/**
|
||||||
* PUT method handler
|
* PUT method handler
|
||||||
*
|
*
|
||||||
* @param void
|
* @param string $method='PUT'
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
function http_PUT()
|
function http_PUT(string $method='PUT')
|
||||||
{
|
{
|
||||||
if ($this->_check_lock_status($this->path)) {
|
if ($this->_check_lock_status($this->path)) {
|
||||||
$options = Array();
|
$options = Array();
|
||||||
@ -1839,7 +1839,7 @@ class HTTP_WebDAV_Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$stat = $this->PUT($options);
|
$stat = $this->$method($options);
|
||||||
|
|
||||||
if ($stat === false) {
|
if ($stat === false) {
|
||||||
$stat = "403 Forbidden";
|
$stat = "403 Forbidden";
|
||||||
|
@ -276,7 +276,37 @@ Location: https://example.org/egroupware/groupdav.php/<username>/addressbook/123
|
|||||||
```
|
```
|
||||||
</details>
|
</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
|
* **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:
|
* top-level objects need a ```@type``` attribute with one of the following values:
|
||||||
```NameComponent```, ```Organization```, ```Title```, ```Phone```, ```Resource```, ```File```, ```ContactLanguage```,
|
```NameComponent```, ```Organization```, ```Title```, ```Phone```, ```Resource```, ```File```, ```ContactLanguage```,
|
||||||
```Address```, ```StreetComponent```, ```Anniversary```, ```PersonalInformation```
|
```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