mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-11-28 10:53:39 +01:00
got POST, PUT and DELETE request to add, update and delete contacts working
added JSON exception handler with nicer JsCalendar parse errors
This commit is contained in:
parent
655f52a876
commit
3bc015a90d
@ -599,8 +599,10 @@ class addressbook_groupdav extends Api\CalDAV\Handler
|
||||
// jsContact or vCard
|
||||
if (Api\CalDAV::isJSON())
|
||||
{
|
||||
$options['data'] = $contact['list_id'] ? JsContact::getJsCardGroup($contact) : JsContact::getJsCard($contact);
|
||||
$options['mimetype'] = $contact['list_id'] ? JsContact::MIME_TYPE_JSCARDGROUP : JsContact::MIME_TYPE_JSCARD;
|
||||
$options['data'] = $contact['list_id'] ? JsContact::getJsCardGroup($contact) :
|
||||
JsContact::getJsCard($contact);
|
||||
$options['mimetype'] = ($contact['list_id'] ? JsContact::MIME_TYPE_JSCARDGROUP :
|
||||
JsContact::MIME_TYPE_JSCARD).';charset=utf-8';
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -638,10 +640,12 @@ class addressbook_groupdav extends Api\CalDAV\Handler
|
||||
if (Api\CalDAV::isJSON())
|
||||
{
|
||||
$contact = JsContact::parseJsCard($options['content']);
|
||||
// just output it again for now
|
||||
|
||||
/* uncomment to return parsed data for testing
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($contact, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
|
||||
return "200 Ok";
|
||||
*/
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -751,7 +755,8 @@ class addressbook_groupdav extends Api\CalDAV\Handler
|
||||
}
|
||||
if ($this->http_if_match) $contact['etag'] = self::etag2value($this->http_if_match);
|
||||
|
||||
$contact['photo_unchanged'] = false; // photo needs saving
|
||||
// ignore photo for JSON/REST, it's not yet supported
|
||||
$contact['photo_unchanged'] = Api\CalDAV::isJSON(); //false; // photo needs saving
|
||||
if (!($save_ok = $is_group ? $this->save_group($contact, $oldContact) : $this->bo->save($contact)))
|
||||
{
|
||||
if ($this->debug) error_log(__METHOD__."(,$id) save(".array2string($contact).") failed, Ok=$save_ok");
|
||||
@ -1051,7 +1056,17 @@ class addressbook_groupdav extends Api\CalDAV\Handler
|
||||
unset($tids[Api\Contacts::DELETED_TYPE]);
|
||||
$non_deleted_tids = array_keys($tids);
|
||||
}
|
||||
$contact = $this->bo->read(array(self::$path_attr => $id, 'tid' => $non_deleted_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)
|
||||
{
|
||||
$keys['id'] = $id;
|
||||
}
|
||||
else
|
||||
{
|
||||
$keys[self::$path_attr] = $id;
|
||||
}
|
||||
$contact = $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'))))
|
||||
|
@ -19,16 +19,16 @@ use EGroupware\Api\CalDAV\Principals;
|
||||
// explicit import non-namespaced classes
|
||||
require_once(__DIR__.'/WebDAV/Server.php');
|
||||
|
||||
use EGroupware\Api\Contacts\JsContact;
|
||||
use EGroupware\Api\Contacts\JsContactParseException;
|
||||
use HTTP_WebDAV_Server;
|
||||
use calendar_hooks;
|
||||
|
||||
/**
|
||||
* EGroupware: GroupDAV access
|
||||
* EGroupware: CalDAV/CardDAV server
|
||||
*
|
||||
* Using a modified PEAR HTTP/WebDAV/Server class from API!
|
||||
*
|
||||
* One can use the following url's releative (!) to http://domain.com/egroupware/groupdav.php
|
||||
* One can use the following URLs relative (!) to https://example.org/egroupware/groupdav.php
|
||||
*
|
||||
* - / base of Cal|Card|GroupDAV tree, only certain clients (KDE, Apple) can autodetect folders from here
|
||||
* - /principals/ principal-collection-set for WebDAV ACL
|
||||
@ -51,10 +51,25 @@ use calendar_hooks;
|
||||
* - /(resources|locations)/<resource-name>/calendar calendar of a resource/location, if user has rights to view
|
||||
* - /<current-username>/(resource|location)-<resource-name> shared calendar from a resource/location
|
||||
*
|
||||
* Shared addressbooks or calendars are only shown in in users home-set, if he subscribed to it via his CalDAV preferences!
|
||||
* Shared addressbooks or calendars are only shown in the users home-set, if he subscribed to it via his CalDAV preferences!
|
||||
*
|
||||
* Calling one of the above collections with a GET request / regular browser generates an automatic index
|
||||
* from the data of a allprop PROPFIND, allow to browse CalDAV/CardDAV/GroupDAV tree with a regular browser.
|
||||
* from the data of a allprop PROPFIND, allow browsing CalDAV/CardDAV tree with a regular browser.
|
||||
*
|
||||
* Using EGroupware CalDAV/CardDAV as REST API: currently only for contacts
|
||||
* ===========================================
|
||||
* GET requests to collections with an "Accept: application/json" header return a JSON response similar to a PROPFIND
|
||||
* following GET parameters are supported to customize the returned properties:
|
||||
* - props[]=<DAV-prop-name> eg. props[]=getetag to return only the ETAG (multiple DAV properties can be specified)
|
||||
* Default for addressbook collections is to only return address-data (JsContact), other collections return all props.
|
||||
* - sync-token=<token> to only request change since last sync-token, like rfc6578 sync-collection REPORT
|
||||
* - nresults=N limit number of responses (only for sync-collection / given sync-token parameter!)
|
||||
* this will return a "more-results"=true attribute and a new "sync-token" attribute to query for the next chunk
|
||||
* POST requests to collection with a "Content-Type: application/json" header add new entries in addressbook or calendar collections
|
||||
* (Location header in response gives URL of new resource)
|
||||
* GET requests with an "Accept: application/json" header can be used to retrieve single resources / JsContact or JsCalendar schema
|
||||
* PUT requests with a "Content-Type: application/json" header allow modifying single resources
|
||||
* DELETE requests delete single resources
|
||||
*
|
||||
* Permanent error_log() calls should use groupdav->log($str) instead, to be send to PHP error_log()
|
||||
* and our request-log (prefixed with "### " after request and response, like exceptions).
|
||||
@ -1403,7 +1418,8 @@ class CalDAV extends HTTP_WebDAV_Server
|
||||
{
|
||||
// for some reason OS X Addressbook (CFNetwork user-agent) uses now (DAV:add-member given with collection URL+"?add-member")
|
||||
// POST to the collection URL plus a UID like name component (like for regular PUT) to create new entrys
|
||||
if (isset($_GET['add-member']) || Handler::get_agent() == 'cfnetwork')
|
||||
if (isset($_GET['add-member']) || Handler::get_agent() == 'cfnetwork' ||
|
||||
substr($options['path'], -1) === '/' && self::isJSON())
|
||||
{
|
||||
$_GET['add-member'] = ''; // otherwise we give no Location header
|
||||
return $this->PUT($options);
|
||||
@ -2421,16 +2437,37 @@ class CalDAV extends HTTP_WebDAV_Server
|
||||
$headline = null;
|
||||
_egw_log_exception($e,$headline);
|
||||
|
||||
// exception handler sending message back to the client as basic auth message
|
||||
$error = str_replace(array("\r", "\n"), array('', ' | '), $e->getMessage());
|
||||
header('WWW-Authenticate: Basic realm="'.$headline.': '.$error.'"');
|
||||
header('HTTP/1.1 401 Unauthorized');
|
||||
header('X-WebDAV-Status: 401 Unauthorized', true);
|
||||
|
||||
if (self::isJSON())
|
||||
{
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
if (is_a($e, JsContactParseException::class))
|
||||
{
|
||||
$status = '422 Unprocessable Entity';
|
||||
}
|
||||
else
|
||||
{
|
||||
$status = '500 Internal Server Error';
|
||||
}
|
||||
http_response_code((int)$status);
|
||||
echo self::json_encode([
|
||||
'error' => $e->getCode() ?: (int)$status,
|
||||
'message' => $e->getMessage(),
|
||||
]+($e->getPrevious() ? [
|
||||
'original' => get_class($e->getPrevious()).': '.$e->getPrevious()->getMessage(),
|
||||
] : []), JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
else
|
||||
{
|
||||
// exception handler sending message back to the client as basic auth message
|
||||
$error = str_replace(array("\r", "\n"), array('', ' | '), $e->getMessage());
|
||||
header('WWW-Authenticate: Basic realm="' . $headline . ': ' . $error . '"');
|
||||
header('HTTP/1.1 401 Unauthorized');
|
||||
header('X-WebDAV-Status: 401 Unauthorized', true);
|
||||
}
|
||||
// if our own logging is active, log the request plus a trace, if enabled in server-config
|
||||
if (self::$request_starttime && isset(self::$instance))
|
||||
{
|
||||
self::$instance->_http_status = '401 Unauthorized'; // to correctly log it
|
||||
self::$instance->_http_status = self::isJSON() ? $status : '401 Unauthorized'; // to correctly log it
|
||||
if ($GLOBALS['egw_info']['server']['exception_show_trace'])
|
||||
{
|
||||
self::$instance->log_request("\n".$e->getTraceAsString()."\n");
|
||||
|
@ -53,8 +53,15 @@ class JsContact
|
||||
'organizations' => array_filter(['org' => self::organization($contact)]),
|
||||
'titles' => self::titles($contact),
|
||||
'emails' => array_filter([
|
||||
'work' => empty($contact['email']) ? null : ['email' => $contact['email']],
|
||||
'private' => empty($contact['email_home']) ? null : ['email' => $contact['email_home']],
|
||||
'work' => empty($contact['email']) ? null : [
|
||||
'email' => $contact['email'],
|
||||
'contexts' => ['work' => true],
|
||||
'pref' => 1, // as it's the more prominent in our UI
|
||||
],
|
||||
'private' => empty($contact['email_home']) ? null : [
|
||||
'email' => $contact['email_home'],
|
||||
'contexts' => ['private' => true],
|
||||
],
|
||||
]),
|
||||
'phones' => self::phones($contact),
|
||||
'online' => array_filter([
|
||||
@ -62,7 +69,7 @@ class JsContact
|
||||
'url_home' => !empty($contact['url_home']) ? ['resource' => $contact['url_home'], 'type' => 'uri', 'contexts' => ['private' => true]] : null,
|
||||
]),
|
||||
'addresses' => [
|
||||
'work' => self::address($contact, 'work', 1),
|
||||
'work' => self::address($contact, 'work', 1), // as it's the more prominent in our UI
|
||||
'home' => self::address($contact, 'home'),
|
||||
],
|
||||
'photos' => self::photos($contact),
|
||||
@ -75,7 +82,7 @@ class JsContact
|
||||
]);
|
||||
if ($encode)
|
||||
{
|
||||
return Api\CalDAV::json_encode($data);
|
||||
return Api\CalDAV::json_encode($data, self::JSON_OPTIONS_ERROR);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
@ -88,97 +95,126 @@ class JsContact
|
||||
*/
|
||||
public static function parseJsCard(string $json)
|
||||
{
|
||||
$data = json_decode($json, JSON_THROW_ON_ERROR);
|
||||
|
||||
if (!isset($data['uid'])) $data['uid'] = null; // to fail below, if it does not exist
|
||||
|
||||
$contact = [];
|
||||
foreach($data as $name => $value)
|
||||
try
|
||||
{
|
||||
switch($name)
|
||||
$data = json_decode($json, true, 10, JSON_THROW_ON_ERROR);
|
||||
|
||||
if (!isset($data['uid'])) $data['uid'] = null; // to fail below, if it does not exist
|
||||
|
||||
$contact = [];
|
||||
foreach ($data as $name => $value)
|
||||
{
|
||||
case 'uid':
|
||||
if (!is_string($value) || empty($value))
|
||||
{
|
||||
throw new \InvalidArgumentException("Invalid uid value!");
|
||||
}
|
||||
$contact['uid'] = $value;
|
||||
break;
|
||||
switch ($name)
|
||||
{
|
||||
case 'uid':
|
||||
if (!is_string($value) || empty($value))
|
||||
{
|
||||
throw new \InvalidArgumentException("Missing or invalid uid value!");
|
||||
}
|
||||
$contact['uid'] = $value;
|
||||
break;
|
||||
|
||||
case 'name':
|
||||
$contact += self::parseNameComponents($value);
|
||||
break;
|
||||
case 'name':
|
||||
$contact += self::parseNameComponents($value);
|
||||
break;
|
||||
|
||||
case 'fullName':
|
||||
$contact['n_fn'] = self::parseLocalizedString($value);
|
||||
break;
|
||||
case 'fullName':
|
||||
$contact['n_fn'] = self::parseLocalizedString($value);
|
||||
break;
|
||||
|
||||
case 'organizations':
|
||||
$contact += self::parseOrganizations($value);
|
||||
break;
|
||||
case 'organizations':
|
||||
$contact += self::parseOrganizations($value);
|
||||
break;
|
||||
|
||||
case 'titles':
|
||||
$contact += self::parseTitles($value);
|
||||
break;
|
||||
case 'titles':
|
||||
$contact += self::parseTitles($value);
|
||||
break;
|
||||
|
||||
case 'emails':
|
||||
$contact += self::parseEmails($value);
|
||||
break;
|
||||
case 'emails':
|
||||
$contact += self::parseEmails($value);
|
||||
break;
|
||||
|
||||
case 'phones':
|
||||
$contact += self::parsePhones($value);
|
||||
break;
|
||||
case 'phones':
|
||||
$contact += self::parsePhones($value);
|
||||
break;
|
||||
|
||||
case 'online':
|
||||
$contact += self::parseOnline($value);
|
||||
break;
|
||||
case 'online':
|
||||
$contact += self::parseOnline($value);
|
||||
break;
|
||||
|
||||
case 'addresses':
|
||||
$contact += self::parseAddresses($value);
|
||||
break;
|
||||
case 'addresses':
|
||||
$contact += self::parseAddresses($value);
|
||||
break;
|
||||
|
||||
case 'photos':
|
||||
$contact += self::parsePhotos($value);
|
||||
break;
|
||||
case 'photos':
|
||||
$contact += self::parsePhotos($value);
|
||||
break;
|
||||
|
||||
case 'anniversaries':
|
||||
$contact += self::parseAnniversaries($value);
|
||||
break;
|
||||
case 'anniversaries':
|
||||
$contact += self::parseAnniversaries($value);
|
||||
break;
|
||||
|
||||
case 'notes':
|
||||
$contact['note'] = implode("\n", array_map(static function($note)
|
||||
{
|
||||
return self::parseLocalizedString($note);
|
||||
}, $value));
|
||||
break;
|
||||
case 'notes':
|
||||
$contact['note'] = implode("\n", array_map(static function ($note) {
|
||||
return self::parseLocalizedString($note);
|
||||
}, $value));
|
||||
break;
|
||||
|
||||
case 'categories':
|
||||
$contact['cat_id'] = self::parseCategories($value);
|
||||
break;
|
||||
case 'categories':
|
||||
$contact['cat_id'] = self::parseCategories($value);
|
||||
break;
|
||||
|
||||
case 'egroupware.org/customfields':
|
||||
$contact += self::parseCustomfields($value);
|
||||
break;
|
||||
case 'egroupware.org/customfields':
|
||||
$contact += self::parseCustomfields($value);
|
||||
break;
|
||||
|
||||
case 'egroupware.org/assistant':
|
||||
$contact['assistent'] = $value;
|
||||
break;
|
||||
case 'egroupware.org/assistant':
|
||||
$contact['assistent'] = $value;
|
||||
break;
|
||||
|
||||
case 'egroupware.org/fileAs':
|
||||
$contact['fileas'] = $value;
|
||||
break;
|
||||
case 'egroupware.org/fileAs':
|
||||
$contact['fileas'] = $value;
|
||||
break;
|
||||
|
||||
case 'prodId': case 'created': case 'updated': case 'kind':
|
||||
break;
|
||||
case 'prodId':
|
||||
case 'created':
|
||||
case 'updated':
|
||||
case 'kind':
|
||||
break;
|
||||
|
||||
default:
|
||||
error_log(__METHOD__."() $name=".json_encode($value).' --> ignored');
|
||||
break;
|
||||
default:
|
||||
error_log(__METHOD__ . "() $name=" . json_encode($value, self::JSON_OPTIONS_ERROR) . ' --> ignored');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (\JsonException $e) {
|
||||
throw new JsContactParseException("Error parsing JSON: ".$e->getMessage(), 422, $e);
|
||||
}
|
||||
catch (\InvalidArgumentException $e) {
|
||||
throw new JsContactParseException("Error parsing JsContact field '$name': ".
|
||||
str_replace('"', "'", $e->getMessage()), 422);
|
||||
}
|
||||
catch (\TypeError $e) {
|
||||
$message = $e->getMessage();
|
||||
if (preg_match('/must be of the type ([^ ]+), ([^ ]+) given/', $message, $matches))
|
||||
{
|
||||
$message = "$matches[1] expected, but got $matches[2]: ".
|
||||
str_replace('"', "'", json_encode($value, self::JSON_OPTIONS_ERROR));
|
||||
}
|
||||
throw new JsContactParseException("Error parsing JsContact field '$name': $message", 422, $e);
|
||||
}
|
||||
catch (\Throwable $e) {
|
||||
throw new JsContactParseException("Error parsing JsContact field '$name': ". $e->getMessage(), 422, $e);
|
||||
}
|
||||
return $contact;
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON options for errors thrown as exceptions
|
||||
*/
|
||||
const JSON_OPTIONS_ERROR = JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE;
|
||||
|
||||
/**
|
||||
* Return organisation
|
||||
*
|
||||
@ -317,6 +353,7 @@ class JsContact
|
||||
* Parse custom fields
|
||||
*
|
||||
* Not defined custom fields are ignored!
|
||||
* Not send custom fields are set to null!
|
||||
*
|
||||
* @param array $cfs name => object with attribute data and optional type, label, values
|
||||
* @return array
|
||||
@ -324,17 +361,18 @@ class JsContact
|
||||
protected static function parseCustomfields(array $cfs)
|
||||
{
|
||||
$contact = [];
|
||||
$definition = Api\Storage\Customfields::get('addressbook');
|
||||
$definitions = Api\Storage\Customfields::get('addressbook');
|
||||
|
||||
foreach($cfs as $name => $data)
|
||||
foreach($definitions as $name => $definition)
|
||||
{
|
||||
if (!is_array($data) || !array_key_exists('value', $data))
|
||||
$data = $cfs[$name];
|
||||
if (isset($data[$name]))
|
||||
{
|
||||
throw new \InvalidArgumentException("Invalid customfield object $name: ".json_encode($data));
|
||||
}
|
||||
if (isset($definition[$name]))
|
||||
{
|
||||
switch($definition[$name]['type'])
|
||||
if (!is_array($data) || !array_key_exists('value', $data))
|
||||
{
|
||||
throw new \InvalidArgumentException("Invalid customfield object $name: ".json_encode($data, self::JSON_OPTIONS_ERROR));
|
||||
}
|
||||
switch($definition['type'])
|
||||
{
|
||||
case 'date-time':
|
||||
$data['value'] = Api\DateTime::to($data['value'], 'object');
|
||||
@ -347,12 +385,22 @@ class JsContact
|
||||
break;
|
||||
case 'select':
|
||||
if (is_scalar($data['value'])) $data['value'] = explode(',', $data['value']);
|
||||
$data['value'] = array_intersect(array_keys($definition[$name]['values']), $data['value']);
|
||||
$data['value'] = array_intersect(array_keys($definition['values']), $data['value']);
|
||||
$data['value'] = $data['value'] ? implode(',', (array)$data['value']) : null;
|
||||
break;
|
||||
}
|
||||
$contact['#'.$name] = $data['value'];
|
||||
}
|
||||
// set not return cfs to null
|
||||
else
|
||||
{
|
||||
$contact['#'.$name] = null;
|
||||
}
|
||||
}
|
||||
// report not existing cfs to log
|
||||
if (($not_existing=array_diff(array_keys($cfs), array_keys($definitions))))
|
||||
{
|
||||
error_log(__METHOD__."() not existing/ignored custom fields: ".implode(', ', $not_existing));
|
||||
}
|
||||
return $contact;
|
||||
}
|
||||
@ -454,7 +502,7 @@ class JsContact
|
||||
$contact += ($values=self::parseAddress($address, $id, $last_type));
|
||||
if (++$n > 2)
|
||||
{
|
||||
error_log(__METHOD__."() Ignoring $n. address id=$id: ".json_encode($address));
|
||||
error_log(__METHOD__."() Ignoring $n. address id=$id: ".json_encode($address, self::JSON_OPTIONS_ERROR));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -481,7 +529,8 @@ class JsContact
|
||||
*/
|
||||
protected static function parseAddress(array $address, string $id, string &$last_type=null)
|
||||
{
|
||||
$type = !isset($last_type) && (empty($address['context']['private']) || $id !== 'private') || $last_type === 'home' ? 'work' : 'home';
|
||||
$type = !isset($last_type) && (empty($address['contexts']['private']) || $id === 'work') ||
|
||||
$last_type === 'home' ? 'work' : 'home';
|
||||
$last_type = $type;
|
||||
$prefix = $type === 'work' ? 'adr_one_' : 'adr_two_';
|
||||
|
||||
@ -547,7 +596,7 @@ class JsContact
|
||||
{
|
||||
if (!is_array($component) || !is_string($component['value']))
|
||||
{
|
||||
throw new \InvalidArgumentException("Invalid streetComponents!");
|
||||
throw new \InvalidArgumentException("Invalid street-component: ".json_encode($component, self::JSON_OPTIONS_ERROR));
|
||||
}
|
||||
if ($street && $last_type !== 'separator') // if we have no separator, we add a space
|
||||
{
|
||||
@ -614,7 +663,7 @@ class JsContact
|
||||
{
|
||||
if (!is_array($phone) || !is_string($phone['phone']))
|
||||
{
|
||||
throw new \InvalidArgumentException("Invalid phone: " . json_encode($phone));
|
||||
throw new \InvalidArgumentException("Invalid phone: " . json_encode($phone, self::JSON_OPTIONS_ERROR));
|
||||
}
|
||||
// first check for "our" id's
|
||||
if (isset(self::$phone2jscard[$id]) && !isset($contact[$id]))
|
||||
@ -687,7 +736,7 @@ class JsContact
|
||||
{
|
||||
if (!is_array($value) || !is_string($value['resource']))
|
||||
{
|
||||
throw new \InvalidArgumentException("Invalid online resource with id '$id': ".json_encode($value));
|
||||
throw new \InvalidArgumentException("Invalid online resource with id '$id': ".json_encode($value, self::JSON_OPTIONS_ERROR));
|
||||
}
|
||||
// check for "our" id's
|
||||
if (in_array($id, ['url', 'url_home']))
|
||||
@ -717,6 +766,7 @@ class JsContact
|
||||
/**
|
||||
* Parse emails object
|
||||
*
|
||||
* @link https://datatracker.ietf.org/doc/html/draft-ietf-jmap-jscontact-07#section-2.3.1
|
||||
* @param array $emails id => object with attribute "email" and optional "context"
|
||||
* @return array
|
||||
*/
|
||||
@ -727,7 +777,7 @@ class JsContact
|
||||
{
|
||||
if (!is_array($value) || !is_string($value['email']))
|
||||
{
|
||||
throw new \InvalidArgumentException("Invalid email object: ".json_encode($value));
|
||||
throw new \InvalidArgumentException("Invalid email object (requires email attribute): ".json_encode($value, self::JSON_OPTIONS_ERROR));
|
||||
}
|
||||
if (!isset($contact['email']) && $id !== 'private' && empty($value['context']['private']))
|
||||
{
|
||||
@ -802,21 +852,21 @@ class JsContact
|
||||
/**
|
||||
* parse nameComponents
|
||||
*
|
||||
* @param array $values
|
||||
* @param array $components
|
||||
* @return array
|
||||
*/
|
||||
protected static function parseNameComponents(array $values)
|
||||
protected static function parseNameComponents(array $components)
|
||||
{
|
||||
$contact = array_combine(array_values(self::$nameType2attribute),
|
||||
array_fill(0, count(self::$nameType2attribute), null));
|
||||
|
||||
foreach($values as $value)
|
||||
foreach($components as $component)
|
||||
{
|
||||
if (empty($value['type']) || isset($value) && !is_string($value['value']))
|
||||
if (empty($component['type']) || isset($component) && !is_string($component['value']))
|
||||
{
|
||||
throw new \InvalidArgumentException("Invalid nameComponent!");
|
||||
throw new \InvalidArgumentException("Invalid name-component (must have type and value attributes): ".json_encode($component, self::JSON_OPTIONS_ERROR));
|
||||
}
|
||||
$contact[self::$nameType2attribute[$value['type']]] = $value['value'];
|
||||
$contact[self::$nameType2attribute[$component['type']]] = $component['value'];
|
||||
}
|
||||
return $contact;
|
||||
}
|
||||
@ -854,7 +904,7 @@ class JsContact
|
||||
(!list($year, $month, $day) = explode('-', $anniversary['date'])) ||
|
||||
!(1 <= $month && $month <= 12 && 1 <= $day && $day <= 31))
|
||||
{
|
||||
throw new \InvalidArgumentException("Invalid anniversary object with id '$id': ".json_encode($anniversary));
|
||||
throw new \InvalidArgumentException("Invalid anniversary object with id '$id': ".json_encode($anniversary, self::JSON_OPTIONS_ERROR));
|
||||
}
|
||||
if (!isset($contact['bday']) && ($id === 'bday' || $anniversary['type'] === 'birth'))
|
||||
{
|
||||
@ -862,7 +912,7 @@ class JsContact
|
||||
}
|
||||
else
|
||||
{
|
||||
error_log(__METHOD__."() only one birtday is supported, ignoring aniversary: ".json_encode($anniversary));
|
||||
error_log(__METHOD__."() only one birtday is supported, ignoring aniversary: ".json_encode($anniversary, self::JSON_OPTIONS_ERROR));
|
||||
}
|
||||
}
|
||||
return $contact;
|
||||
@ -902,7 +952,7 @@ class JsContact
|
||||
{
|
||||
if (!is_string($value['value']))
|
||||
{
|
||||
throw new \InvalidArgumentException("Invalid localizedString: ".json_encode($value));
|
||||
throw new \InvalidArgumentException("Invalid localizedString: ".json_encode($value, self::JSON_OPTIONS_ERROR));
|
||||
}
|
||||
return $value['value'];
|
||||
}
|
||||
@ -960,7 +1010,7 @@ class JsContact
|
||||
$vCard->setAttribute('UID',$list['list_uid']);
|
||||
*/
|
||||
|
||||
return Api\CalDAV::json_encode($group);
|
||||
return Api\CalDAV::json_encode($group, self::JSON_OPTIONS_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -975,4 +1025,4 @@ class JsContact
|
||||
}
|
||||
return $contacts;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
27
api/src/Contacts/JsContactParseException.php
Normal file
27
api/src/Contacts/JsContactParseException.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/**
|
||||
* EGroupware API - JsContact
|
||||
*
|
||||
* @link https://www.egroupware.org
|
||||
* @author Ralf Becker <rb@egroupware.org>
|
||||
* @package addressbook
|
||||
* @copyright (c) 2021 by Ralf Becker <rb@egroupware.org>
|
||||
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
||||
*/
|
||||
|
||||
namespace EGroupware\Api\Contacts;
|
||||
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Error parsing JsContact format
|
||||
*
|
||||
* @link https://datatracker.ietf.org/doc/html/draft-ietf-jmap-jscontact-07
|
||||
*/
|
||||
class JsContactParseException extends \InvalidArgumentException
|
||||
{
|
||||
public function __construct($message = "", $code = 422, Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code ?: 422, $previous);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user