Improve Thunderbird sync deletes and gate CalDAV tracing by diagnostics
This commit is contained in:
parent
6354c8b4c5
commit
1b803f077b
|
|
@ -2014,12 +2014,20 @@ HTML
|
||||||
|
|
||||||
private function serveCalDavPath(string $path, string $method): void
|
private function serveCalDavPath(string $path, string $method): void
|
||||||
{
|
{
|
||||||
|
$userAgent = (string) ($_SERVER['HTTP_USER_AGENT'] ?? '');
|
||||||
$caldavUser = $this->resolveCalDavUserForRequest(null);
|
$caldavUser = $this->resolveCalDavUserForRequest(null);
|
||||||
|
$this->caldavTrace('request', [
|
||||||
|
'method' => $method,
|
||||||
|
'path' => $path,
|
||||||
|
'user_agent' => $userAgent,
|
||||||
|
'authorized' => $caldavUser !== null,
|
||||||
|
]);
|
||||||
if ($caldavUser === null) {
|
if ($caldavUser === null) {
|
||||||
http_response_code(401);
|
http_response_code(401);
|
||||||
header('WWW-Authenticate: Basic realm="Calendar CalDAV"');
|
header('WWW-Authenticate: Basic realm="Calendar CalDAV"');
|
||||||
header('Content-Type: application/xml; charset=utf-8');
|
header('Content-Type: application/xml; charset=utf-8');
|
||||||
echo '<error><message>auth required</message></error>';
|
echo '<error><message>auth required</message></error>';
|
||||||
|
$this->caldavTrace('response', ['method' => $method, 'path' => $path, 'status' => 401, 'reason' => 'auth_required']);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2046,6 +2054,7 @@ HTML
|
||||||
header('Allow: OPTIONS, PROPFIND, REPORT, GET, PUT, DELETE, HEAD');
|
header('Allow: OPTIONS, PROPFIND, REPORT, GET, PUT, DELETE, HEAD');
|
||||||
header('DAV: 1, calendar-access');
|
header('DAV: 1, calendar-access');
|
||||||
http_response_code(200);
|
http_response_code(200);
|
||||||
|
$this->caldavTrace('response', ['method' => $method, 'path' => $path, 'status' => 200, 'kind' => 'collection_head']);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (str_starts_with($path, $resourcePrefix) && str_ends_with($path, '.ics')) {
|
if (str_starts_with($path, $resourcePrefix) && str_ends_with($path, '.ics')) {
|
||||||
|
|
@ -2053,11 +2062,13 @@ HTML
|
||||||
$obj = $this->calDavService->getObject($resource);
|
$obj = $this->calDavService->getObject($resource);
|
||||||
if ($obj === null) {
|
if ($obj === null) {
|
||||||
http_response_code(404);
|
http_response_code(404);
|
||||||
|
$this->caldavTrace('response', ['method' => $method, 'path' => $path, 'status' => 404, 'resource' => $resource]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
header('Content-Type: text/calendar; charset=utf-8');
|
header('Content-Type: text/calendar; charset=utf-8');
|
||||||
header('ETag: ' . (string) ($obj['etag'] ?? ''));
|
header('ETag: ' . (string) ($obj['etag'] ?? ''));
|
||||||
http_response_code(200);
|
http_response_code(200);
|
||||||
|
$this->caldavTrace('response', ['method' => $method, 'path' => $path, 'status' => 200, 'resource' => $resource]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2078,6 +2089,7 @@ HTML
|
||||||
header('Allow: OPTIONS, PROPFIND, REPORT, GET, PUT, DELETE, HEAD');
|
header('Allow: OPTIONS, PROPFIND, REPORT, GET, PUT, DELETE, HEAD');
|
||||||
header('DAV: 1, calendar-access');
|
header('DAV: 1, calendar-access');
|
||||||
http_response_code(200);
|
http_response_code(200);
|
||||||
|
$this->caldavTrace('response', ['method' => $method, 'path' => $path, 'status' => 200, 'kind' => 'collection_get']);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2086,6 +2098,7 @@ HTML
|
||||||
header('Allow: OPTIONS, PROPFIND, REPORT, GET, PUT, DELETE, HEAD');
|
header('Allow: OPTIONS, PROPFIND, REPORT, GET, PUT, DELETE, HEAD');
|
||||||
header('DAV: 1, calendar-access');
|
header('DAV: 1, calendar-access');
|
||||||
http_response_code(200);
|
http_response_code(200);
|
||||||
|
$this->caldavTrace('response', ['method' => $method, 'path' => $path, 'status' => 200, 'kind' => 'options']);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2094,18 +2107,22 @@ HTML
|
||||||
http_response_code(207);
|
http_response_code(207);
|
||||||
if ($path === $root || $path === $root . '/') {
|
if ($path === $root || $path === $root . '/') {
|
||||||
echo $this->caldavPropfindRootXml($root, $principal, $calendarsRoot, $collection);
|
echo $this->caldavPropfindRootXml($root, $principal, $calendarsRoot, $collection);
|
||||||
|
$this->caldavTrace('response', ['method' => $method, 'path' => $path, 'status' => 207, 'kind' => 'propfind_root']);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($path === $principalCollection || $path === rtrim($principalCollection, '/')) {
|
if ($path === $principalCollection || $path === rtrim($principalCollection, '/')) {
|
||||||
echo $this->caldavPropfindPrincipalCollectionXml($principalCollection, $principal);
|
echo $this->caldavPropfindPrincipalCollectionXml($principalCollection, $principal);
|
||||||
|
$this->caldavTrace('response', ['method' => $method, 'path' => $path, 'status' => 207, 'kind' => 'propfind_principal_collection']);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($path === $principal || $path === rtrim($principal, '/')) {
|
if ($path === $principal || $path === rtrim($principal, '/')) {
|
||||||
echo $this->caldavPropfindPrincipalXml($principal, $calendarsRoot);
|
echo $this->caldavPropfindPrincipalXml($principal, $calendarsRoot);
|
||||||
|
$this->caldavTrace('response', ['method' => $method, 'path' => $path, 'status' => 207, 'kind' => 'propfind_principal']);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($path === $calendarsRoot || $path === rtrim($calendarsRoot, '/')) {
|
if ($path === $calendarsRoot || $path === rtrim($calendarsRoot, '/')) {
|
||||||
echo $this->caldavPropfindCalendarsRootXml($calendarsRoot, $collection);
|
echo $this->caldavPropfindCalendarsRootXml($calendarsRoot, $collection);
|
||||||
|
$this->caldavTrace('response', ['method' => $method, 'path' => $path, 'status' => 207, 'kind' => 'propfind_calendars_root']);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($path === rtrim($collection, '/')) {
|
if ($path === rtrim($collection, '/')) {
|
||||||
|
|
@ -2113,6 +2130,7 @@ HTML
|
||||||
}
|
}
|
||||||
if ($path === $collection) {
|
if ($path === $collection) {
|
||||||
echo $this->caldavPropfindCollectionXml($collection, $this->caldavSyncToken(), true);
|
echo $this->caldavPropfindCollectionXml($collection, $this->caldavSyncToken(), true);
|
||||||
|
$this->caldavTrace('response', ['method' => $method, 'path' => $path, 'status' => 207, 'kind' => 'propfind_collection']);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (str_starts_with($path, $resourcePrefix) && str_ends_with($path, '.ics')) {
|
if (str_starts_with($path, $resourcePrefix) && str_ends_with($path, '.ics')) {
|
||||||
|
|
@ -2121,21 +2139,33 @@ HTML
|
||||||
if ($obj === null) {
|
if ($obj === null) {
|
||||||
http_response_code(404);
|
http_response_code(404);
|
||||||
echo '<error><message>not found</message></error>';
|
echo '<error><message>not found</message></error>';
|
||||||
|
$this->caldavTrace('response', ['method' => $method, 'path' => $path, 'status' => 404, 'resource' => $resource, 'kind' => 'propfind_object']);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
echo $this->caldavPropfindObjectXml($collection . $resource, (string) ($obj['etag'] ?? ''));
|
echo $this->caldavPropfindObjectXml($collection . $resource, (string) ($obj['etag'] ?? ''));
|
||||||
|
$this->caldavTrace('response', ['method' => $method, 'path' => $path, 'status' => 207, 'resource' => $resource, 'kind' => 'propfind_object']);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
http_response_code(404);
|
http_response_code(404);
|
||||||
echo '<error><message>not found</message></error>';
|
echo '<error><message>not found</message></error>';
|
||||||
|
$this->caldavTrace('response', ['method' => $method, 'path' => $path, 'status' => 404, 'kind' => 'propfind_not_found']);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($method === 'REPORT' && ($path === $collection || $path === rtrim($collection, '/'))) {
|
if ($method === 'REPORT' && ($path === $collection || $path === rtrim($collection, '/'))) {
|
||||||
$body = (string) file_get_contents('php://input');
|
$body = (string) file_get_contents('php://input');
|
||||||
|
$reportType = $this->caldavReportType($body);
|
||||||
header('Content-Type: application/xml; charset=utf-8');
|
header('Content-Type: application/xml; charset=utf-8');
|
||||||
http_response_code(207);
|
http_response_code(207);
|
||||||
echo $this->caldavReportXml($collection, $body, $this->caldavSyncToken());
|
echo $this->caldavReportXml($collection, $body, $this->caldavSyncToken());
|
||||||
|
$this->caldavTrace('response', [
|
||||||
|
'method' => $method,
|
||||||
|
'path' => $path,
|
||||||
|
'status' => 207,
|
||||||
|
'kind' => 'report',
|
||||||
|
'report_type' => $reportType,
|
||||||
|
'body_bytes' => strlen($body),
|
||||||
|
]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2145,12 +2175,14 @@ HTML
|
||||||
$obj = $this->calDavService->getObject($resource);
|
$obj = $this->calDavService->getObject($resource);
|
||||||
if ($obj === null) {
|
if ($obj === null) {
|
||||||
http_response_code(404);
|
http_response_code(404);
|
||||||
|
$this->caldavTrace('response', ['method' => $method, 'path' => $path, 'status' => 404, 'resource' => $resource, 'kind' => 'object_get']);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
header('Content-Type: text/calendar; charset=utf-8');
|
header('Content-Type: text/calendar; charset=utf-8');
|
||||||
header('ETag: ' . (string) ($obj['etag'] ?? ''));
|
header('ETag: ' . (string) ($obj['etag'] ?? ''));
|
||||||
http_response_code(200);
|
http_response_code(200);
|
||||||
echo (string) ($obj['ics'] ?? '');
|
echo (string) ($obj['ics'] ?? '');
|
||||||
|
$this->caldavTrace('response', ['method' => $method, 'path' => $path, 'status' => 200, 'resource' => $resource, 'kind' => 'object_get']);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($method === 'PUT') {
|
if ($method === 'PUT') {
|
||||||
|
|
@ -2169,6 +2201,14 @@ HTML
|
||||||
http_response_code((int) ($error['status'] ?? 500));
|
http_response_code((int) ($error['status'] ?? 500));
|
||||||
header('Content-Type: application/xml; charset=utf-8');
|
header('Content-Type: application/xml; charset=utf-8');
|
||||||
echo '<error><message>' . esc_html((string) ($error['message'] ?? 'error')) . '</message></error>';
|
echo '<error><message>' . esc_html((string) ($error['message'] ?? 'error')) . '</message></error>';
|
||||||
|
$this->caldavTrace('response', [
|
||||||
|
'method' => $method,
|
||||||
|
'path' => $path,
|
||||||
|
'status' => (int) ($error['status'] ?? 500),
|
||||||
|
'resource' => $resource,
|
||||||
|
'kind' => 'object_put',
|
||||||
|
'body_bytes' => strlen($raw),
|
||||||
|
]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$status = (int) ($result['status'] ?? 204);
|
$status = (int) ($result['status'] ?? 204);
|
||||||
|
|
@ -2177,6 +2217,14 @@ HTML
|
||||||
header('ETag: ' . (string) $event['etag']);
|
header('ETag: ' . (string) $event['etag']);
|
||||||
}
|
}
|
||||||
http_response_code($status);
|
http_response_code($status);
|
||||||
|
$this->caldavTrace('response', [
|
||||||
|
'method' => $method,
|
||||||
|
'path' => $path,
|
||||||
|
'status' => $status,
|
||||||
|
'resource' => $resource,
|
||||||
|
'kind' => 'object_put',
|
||||||
|
'body_bytes' => strlen($raw),
|
||||||
|
]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($method === 'DELETE') {
|
if ($method === 'DELETE') {
|
||||||
|
|
@ -2184,15 +2232,24 @@ HTML
|
||||||
if (isset($result['error'])) {
|
if (isset($result['error'])) {
|
||||||
$error = (array) $result['error'];
|
$error = (array) $result['error'];
|
||||||
http_response_code((int) ($error['status'] ?? 500));
|
http_response_code((int) ($error['status'] ?? 500));
|
||||||
|
$this->caldavTrace('response', [
|
||||||
|
'method' => $method,
|
||||||
|
'path' => $path,
|
||||||
|
'status' => (int) ($error['status'] ?? 500),
|
||||||
|
'resource' => $resource,
|
||||||
|
'kind' => 'object_delete',
|
||||||
|
]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
http_response_code(204);
|
http_response_code(204);
|
||||||
|
$this->caldavTrace('response', ['method' => $method, 'path' => $path, 'status' => 204, 'resource' => $resource, 'kind' => 'object_delete']);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
http_response_code(405);
|
http_response_code(405);
|
||||||
header('Allow: OPTIONS, PROPFIND, REPORT, GET, PUT, DELETE, HEAD');
|
header('Allow: OPTIONS, PROPFIND, REPORT, GET, PUT, DELETE, HEAD');
|
||||||
|
$this->caldavTrace('response', ['method' => $method, 'path' => $path, 'status' => 405, 'reason' => 'method_not_allowed']);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function caldavPropfindRootXml(string $root, string $principal, string $calendarsRoot, string $collection): string
|
private function caldavPropfindRootXml(string $root, string $principal, string $calendarsRoot, string $collection): string
|
||||||
|
|
@ -2319,6 +2376,10 @@ HTML
|
||||||
$bodyLower = strtolower($xmlBody);
|
$bodyLower = strtolower($xmlBody);
|
||||||
$resources = array_map(static fn(array $r): string => (string) ($r['resource'] ?? ''), $this->calDavService->listResources());
|
$resources = array_map(static fn(array $r): string => (string) ($r['resource'] ?? ''), $this->calDavService->listResources());
|
||||||
$items = [];
|
$items = [];
|
||||||
|
$reportType = $this->caldavReportType($xmlBody);
|
||||||
|
$includeDeleted = false;
|
||||||
|
$deletedCount = 0;
|
||||||
|
$clientSyncToken = '';
|
||||||
|
|
||||||
if (str_contains($bodyLower, 'sync-collection')) {
|
if (str_contains($bodyLower, 'sync-collection')) {
|
||||||
$clientSyncToken = $this->extractSyncCollectionToken($xmlBody);
|
$clientSyncToken = $this->extractSyncCollectionToken($xmlBody);
|
||||||
|
|
@ -2326,9 +2387,18 @@ HTML
|
||||||
if ($clientSyncToken !== '' && $clientSyncToken === $syncToken) {
|
if ($clientSyncToken !== '' && $clientSyncToken === $syncToken) {
|
||||||
$items = [];
|
$items = [];
|
||||||
} else {
|
} else {
|
||||||
// Fallback implementation: emit current objects for initial/out-of-date tokens.
|
// Emit current objects for initial/out-of-date tokens.
|
||||||
// Avoid emitting historical tombstones because many clients treat large 404 sets as transient failures.
|
|
||||||
$items = $this->calDavService->multiget($resources);
|
$items = $this->calDavService->multiget($resources);
|
||||||
|
// For incremental syncs, include a bounded set of deleted hrefs so clients can remove local copies.
|
||||||
|
if ($clientSyncToken !== '') {
|
||||||
|
$includeDeleted = true;
|
||||||
|
// Keep incremental deletes bounded to avoid overwhelming strict clients.
|
||||||
|
$deletedResources = $this->calDavService->listDeletedResources(20);
|
||||||
|
$deletedCount = count($deletedResources);
|
||||||
|
foreach ($deletedResources as $deletedResource) {
|
||||||
|
$items[] = ['resource' => $deletedResource, 'status' => 404];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} elseif (str_contains($bodyLower, 'calendar-query')) {
|
} elseif (str_contains($bodyLower, 'calendar-query')) {
|
||||||
$items = $this->calDavService->multiget($resources);
|
$items = $this->calDavService->multiget($resources);
|
||||||
|
|
@ -2359,17 +2429,38 @@ HTML
|
||||||
}
|
}
|
||||||
|
|
||||||
$responses = '';
|
$responses = '';
|
||||||
|
$okCount = 0;
|
||||||
|
$notFoundCount = 0;
|
||||||
foreach ($items as $item) {
|
foreach ($items as $item) {
|
||||||
$status = (int) ($item['status'] ?? 404);
|
$status = (int) ($item['status'] ?? 404);
|
||||||
$resource = (string) ($item['resource'] ?? '');
|
$resource = (string) ($item['resource'] ?? '');
|
||||||
$responses .= '<D:response><D:href>' . htmlspecialchars($collection . $resource, ENT_XML1 | ENT_QUOTES, 'UTF-8') . '</D:href><D:propstat><D:prop>';
|
$responses .= '<D:response><D:href>' . htmlspecialchars($collection . $resource, ENT_XML1 | ENT_QUOTES, 'UTF-8') . '</D:href>';
|
||||||
if ($status === 200) {
|
if ($status === 200) {
|
||||||
|
$okCount++;
|
||||||
|
$responses .= '<D:propstat><D:prop>';
|
||||||
$responses .= '<D:getetag>' . htmlspecialchars((string) ($item['etag'] ?? ''), ENT_XML1 | ENT_QUOTES, 'UTF-8') . '</D:getetag>'
|
$responses .= '<D:getetag>' . htmlspecialchars((string) ($item['etag'] ?? ''), ENT_XML1 | ENT_QUOTES, 'UTF-8') . '</D:getetag>'
|
||||||
. '<C:calendar-data xmlns:C="urn:ietf:params:xml:ns:caldav">' . htmlspecialchars((string) ($item['ics'] ?? ''), ENT_XML1 | ENT_QUOTES, 'UTF-8') . '</C:calendar-data>';
|
. '<C:calendar-data xmlns:C="urn:ietf:params:xml:ns:caldav">' . htmlspecialchars((string) ($item['ics'] ?? ''), ENT_XML1 | ENT_QUOTES, 'UTF-8') . '</C:calendar-data>';
|
||||||
|
$responses .= '</D:prop><D:status>HTTP/1.1 200 OK</D:status></D:propstat>';
|
||||||
|
} else {
|
||||||
|
$notFoundCount++;
|
||||||
|
// For sync-collection deletions, clients expect bare status (no propstat block).
|
||||||
|
if ($reportType === 'sync-collection') {
|
||||||
|
$responses .= '<D:status>HTTP/1.1 404 Not Found</D:status>';
|
||||||
|
} else {
|
||||||
|
$responses .= '<D:propstat><D:prop></D:prop><D:status>HTTP/1.1 404 Not Found</D:status></D:propstat>';
|
||||||
}
|
}
|
||||||
$responses .= '</D:prop><D:status>HTTP/1.1 ' . $status . ($status === 200 ? ' OK' : ' Not Found')
|
|
||||||
. '</D:status></D:propstat></D:response>';
|
|
||||||
}
|
}
|
||||||
|
$responses .= '</D:response>';
|
||||||
|
}
|
||||||
|
$this->caldavTrace('report', [
|
||||||
|
'report_type' => $reportType,
|
||||||
|
'client_sync_token_present' => $clientSyncToken !== '',
|
||||||
|
'client_sync_token_matches' => $clientSyncToken !== '' && $clientSyncToken === $syncToken,
|
||||||
|
'include_deleted' => $includeDeleted,
|
||||||
|
'deleted_included_count' => $deletedCount,
|
||||||
|
'response_ok_count' => $okCount,
|
||||||
|
'response_not_found_count' => $notFoundCount,
|
||||||
|
]);
|
||||||
return '<?xml version="1.0" encoding="utf-8"?><D:multistatus xmlns:D="DAV:"><D:sync-token>'
|
return '<?xml version="1.0" encoding="utf-8"?><D:multistatus xmlns:D="DAV:"><D:sync-token>'
|
||||||
. htmlspecialchars($syncToken, ENT_XML1 | ENT_QUOTES, 'UTF-8')
|
. htmlspecialchars($syncToken, ENT_XML1 | ENT_QUOTES, 'UTF-8')
|
||||||
. '</D:sync-token>'
|
. '</D:sync-token>'
|
||||||
|
|
@ -2425,6 +2516,34 @@ HTML
|
||||||
return $token;
|
return $token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function caldavReportType(string $xmlBody): string
|
||||||
|
{
|
||||||
|
$bodyLower = strtolower($xmlBody);
|
||||||
|
if (str_contains($bodyLower, 'sync-collection')) {
|
||||||
|
return 'sync-collection';
|
||||||
|
}
|
||||||
|
if (str_contains($bodyLower, 'calendar-query')) {
|
||||||
|
return 'calendar-query';
|
||||||
|
}
|
||||||
|
if (str_contains($bodyLower, 'calendar-multiget')) {
|
||||||
|
return 'calendar-multiget';
|
||||||
|
}
|
||||||
|
return 'other';
|
||||||
|
}
|
||||||
|
|
||||||
|
private function caldavTrace(string $event, array $context = []): void
|
||||||
|
{
|
||||||
|
if (!$this->isDiagnosticsEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$payload = [
|
||||||
|
'event' => $event,
|
||||||
|
'at' => gmdate('c'),
|
||||||
|
'context' => $context,
|
||||||
|
];
|
||||||
|
error_log('[calendar-plugin][caldav] ' . wp_json_encode($payload));
|
||||||
|
}
|
||||||
|
|
||||||
private function unwrapServiceResult(array $result): array|\WP_Error
|
private function unwrapServiceResult(array $result): array|\WP_Error
|
||||||
{
|
{
|
||||||
if (isset($result['error']) && is_array($result['error'])) {
|
if (isset($result['error']) && is_array($result['error'])) {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,6 @@ e65577c707c5a66e2097faa7720170180c79a9d9df219f0e1061fdcec55be744 ./src/Infrastr
|
||||||
68c0ca15ad2c8b6363a2578b85f8daf0d3a094e612a120a2cdd2a2bfd8fe5e3c ./src/Infrastructure/WordPress/WordPressDatabaseAdapter.php
|
68c0ca15ad2c8b6363a2578b85f8daf0d3a094e612a120a2cdd2a2bfd8fe5e3c ./src/Infrastructure/WordPress/WordPressDatabaseAdapter.php
|
||||||
8da85db3c1e69c2c5f01aaa2f558aa8f0323446d8af0aec5b34d4607cd4afe1b ./src/Infrastructure/WordPress/WordPressHttpAdapter.php
|
8da85db3c1e69c2c5f01aaa2f558aa8f0323446d8af0aec5b34d4607cd4afe1b ./src/Infrastructure/WordPress/WordPressHttpAdapter.php
|
||||||
cf9fddcecb07af2c03ad2c0e448be12a6b45dd936efc8bc1fd46a52b4af864ea ./src/Infrastructure/WordPress/WordPressOptionsAdapter.php
|
cf9fddcecb07af2c03ad2c0e448be12a6b45dd936efc8bc1fd46a52b4af864ea ./src/Infrastructure/WordPress/WordPressOptionsAdapter.php
|
||||||
0cc96b252ac86e91a724e73beecfe6f57a1afdb6f5d9a26e44aed00a3f0e20f0 ./src/Plugin.php
|
b0e95460e705b9059bf2f197c17f17c51c9bd5492ca69eaa073ebbb808542fe0 ./src/Plugin.php
|
||||||
4e6930c79d9ce1045be6863ddd7546e19f7ef7e839d41a41014a3efb3183eaf0 ./src/bootstrap.php
|
4e6930c79d9ce1045be6863ddd7546e19f7ef7e839d41a41014a3efb3183eaf0 ./src/bootstrap.php
|
||||||
893c6df62beed87a981d372c473e5012d1b5d1c254d23b39cda44ae8a08cd16c ./uninstall.php
|
893c6df62beed87a981d372c473e5012d1b5d1c254d23b39cda44ae8a08cd16c ./uninstall.php
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -2014,12 +2014,20 @@ HTML
|
||||||
|
|
||||||
private function serveCalDavPath(string $path, string $method): void
|
private function serveCalDavPath(string $path, string $method): void
|
||||||
{
|
{
|
||||||
|
$userAgent = (string) ($_SERVER['HTTP_USER_AGENT'] ?? '');
|
||||||
$caldavUser = $this->resolveCalDavUserForRequest(null);
|
$caldavUser = $this->resolveCalDavUserForRequest(null);
|
||||||
|
$this->caldavTrace('request', [
|
||||||
|
'method' => $method,
|
||||||
|
'path' => $path,
|
||||||
|
'user_agent' => $userAgent,
|
||||||
|
'authorized' => $caldavUser !== null,
|
||||||
|
]);
|
||||||
if ($caldavUser === null) {
|
if ($caldavUser === null) {
|
||||||
http_response_code(401);
|
http_response_code(401);
|
||||||
header('WWW-Authenticate: Basic realm="Calendar CalDAV"');
|
header('WWW-Authenticate: Basic realm="Calendar CalDAV"');
|
||||||
header('Content-Type: application/xml; charset=utf-8');
|
header('Content-Type: application/xml; charset=utf-8');
|
||||||
echo '<error><message>auth required</message></error>';
|
echo '<error><message>auth required</message></error>';
|
||||||
|
$this->caldavTrace('response', ['method' => $method, 'path' => $path, 'status' => 401, 'reason' => 'auth_required']);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2046,6 +2054,7 @@ HTML
|
||||||
header('Allow: OPTIONS, PROPFIND, REPORT, GET, PUT, DELETE, HEAD');
|
header('Allow: OPTIONS, PROPFIND, REPORT, GET, PUT, DELETE, HEAD');
|
||||||
header('DAV: 1, calendar-access');
|
header('DAV: 1, calendar-access');
|
||||||
http_response_code(200);
|
http_response_code(200);
|
||||||
|
$this->caldavTrace('response', ['method' => $method, 'path' => $path, 'status' => 200, 'kind' => 'collection_head']);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (str_starts_with($path, $resourcePrefix) && str_ends_with($path, '.ics')) {
|
if (str_starts_with($path, $resourcePrefix) && str_ends_with($path, '.ics')) {
|
||||||
|
|
@ -2053,11 +2062,13 @@ HTML
|
||||||
$obj = $this->calDavService->getObject($resource);
|
$obj = $this->calDavService->getObject($resource);
|
||||||
if ($obj === null) {
|
if ($obj === null) {
|
||||||
http_response_code(404);
|
http_response_code(404);
|
||||||
|
$this->caldavTrace('response', ['method' => $method, 'path' => $path, 'status' => 404, 'resource' => $resource]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
header('Content-Type: text/calendar; charset=utf-8');
|
header('Content-Type: text/calendar; charset=utf-8');
|
||||||
header('ETag: ' . (string) ($obj['etag'] ?? ''));
|
header('ETag: ' . (string) ($obj['etag'] ?? ''));
|
||||||
http_response_code(200);
|
http_response_code(200);
|
||||||
|
$this->caldavTrace('response', ['method' => $method, 'path' => $path, 'status' => 200, 'resource' => $resource]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2078,6 +2089,7 @@ HTML
|
||||||
header('Allow: OPTIONS, PROPFIND, REPORT, GET, PUT, DELETE, HEAD');
|
header('Allow: OPTIONS, PROPFIND, REPORT, GET, PUT, DELETE, HEAD');
|
||||||
header('DAV: 1, calendar-access');
|
header('DAV: 1, calendar-access');
|
||||||
http_response_code(200);
|
http_response_code(200);
|
||||||
|
$this->caldavTrace('response', ['method' => $method, 'path' => $path, 'status' => 200, 'kind' => 'collection_get']);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2086,6 +2098,7 @@ HTML
|
||||||
header('Allow: OPTIONS, PROPFIND, REPORT, GET, PUT, DELETE, HEAD');
|
header('Allow: OPTIONS, PROPFIND, REPORT, GET, PUT, DELETE, HEAD');
|
||||||
header('DAV: 1, calendar-access');
|
header('DAV: 1, calendar-access');
|
||||||
http_response_code(200);
|
http_response_code(200);
|
||||||
|
$this->caldavTrace('response', ['method' => $method, 'path' => $path, 'status' => 200, 'kind' => 'options']);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2094,18 +2107,22 @@ HTML
|
||||||
http_response_code(207);
|
http_response_code(207);
|
||||||
if ($path === $root || $path === $root . '/') {
|
if ($path === $root || $path === $root . '/') {
|
||||||
echo $this->caldavPropfindRootXml($root, $principal, $calendarsRoot, $collection);
|
echo $this->caldavPropfindRootXml($root, $principal, $calendarsRoot, $collection);
|
||||||
|
$this->caldavTrace('response', ['method' => $method, 'path' => $path, 'status' => 207, 'kind' => 'propfind_root']);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($path === $principalCollection || $path === rtrim($principalCollection, '/')) {
|
if ($path === $principalCollection || $path === rtrim($principalCollection, '/')) {
|
||||||
echo $this->caldavPropfindPrincipalCollectionXml($principalCollection, $principal);
|
echo $this->caldavPropfindPrincipalCollectionXml($principalCollection, $principal);
|
||||||
|
$this->caldavTrace('response', ['method' => $method, 'path' => $path, 'status' => 207, 'kind' => 'propfind_principal_collection']);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($path === $principal || $path === rtrim($principal, '/')) {
|
if ($path === $principal || $path === rtrim($principal, '/')) {
|
||||||
echo $this->caldavPropfindPrincipalXml($principal, $calendarsRoot);
|
echo $this->caldavPropfindPrincipalXml($principal, $calendarsRoot);
|
||||||
|
$this->caldavTrace('response', ['method' => $method, 'path' => $path, 'status' => 207, 'kind' => 'propfind_principal']);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($path === $calendarsRoot || $path === rtrim($calendarsRoot, '/')) {
|
if ($path === $calendarsRoot || $path === rtrim($calendarsRoot, '/')) {
|
||||||
echo $this->caldavPropfindCalendarsRootXml($calendarsRoot, $collection);
|
echo $this->caldavPropfindCalendarsRootXml($calendarsRoot, $collection);
|
||||||
|
$this->caldavTrace('response', ['method' => $method, 'path' => $path, 'status' => 207, 'kind' => 'propfind_calendars_root']);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($path === rtrim($collection, '/')) {
|
if ($path === rtrim($collection, '/')) {
|
||||||
|
|
@ -2113,6 +2130,7 @@ HTML
|
||||||
}
|
}
|
||||||
if ($path === $collection) {
|
if ($path === $collection) {
|
||||||
echo $this->caldavPropfindCollectionXml($collection, $this->caldavSyncToken(), true);
|
echo $this->caldavPropfindCollectionXml($collection, $this->caldavSyncToken(), true);
|
||||||
|
$this->caldavTrace('response', ['method' => $method, 'path' => $path, 'status' => 207, 'kind' => 'propfind_collection']);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (str_starts_with($path, $resourcePrefix) && str_ends_with($path, '.ics')) {
|
if (str_starts_with($path, $resourcePrefix) && str_ends_with($path, '.ics')) {
|
||||||
|
|
@ -2121,21 +2139,33 @@ HTML
|
||||||
if ($obj === null) {
|
if ($obj === null) {
|
||||||
http_response_code(404);
|
http_response_code(404);
|
||||||
echo '<error><message>not found</message></error>';
|
echo '<error><message>not found</message></error>';
|
||||||
|
$this->caldavTrace('response', ['method' => $method, 'path' => $path, 'status' => 404, 'resource' => $resource, 'kind' => 'propfind_object']);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
echo $this->caldavPropfindObjectXml($collection . $resource, (string) ($obj['etag'] ?? ''));
|
echo $this->caldavPropfindObjectXml($collection . $resource, (string) ($obj['etag'] ?? ''));
|
||||||
|
$this->caldavTrace('response', ['method' => $method, 'path' => $path, 'status' => 207, 'resource' => $resource, 'kind' => 'propfind_object']);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
http_response_code(404);
|
http_response_code(404);
|
||||||
echo '<error><message>not found</message></error>';
|
echo '<error><message>not found</message></error>';
|
||||||
|
$this->caldavTrace('response', ['method' => $method, 'path' => $path, 'status' => 404, 'kind' => 'propfind_not_found']);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($method === 'REPORT' && ($path === $collection || $path === rtrim($collection, '/'))) {
|
if ($method === 'REPORT' && ($path === $collection || $path === rtrim($collection, '/'))) {
|
||||||
$body = (string) file_get_contents('php://input');
|
$body = (string) file_get_contents('php://input');
|
||||||
|
$reportType = $this->caldavReportType($body);
|
||||||
header('Content-Type: application/xml; charset=utf-8');
|
header('Content-Type: application/xml; charset=utf-8');
|
||||||
http_response_code(207);
|
http_response_code(207);
|
||||||
echo $this->caldavReportXml($collection, $body, $this->caldavSyncToken());
|
echo $this->caldavReportXml($collection, $body, $this->caldavSyncToken());
|
||||||
|
$this->caldavTrace('response', [
|
||||||
|
'method' => $method,
|
||||||
|
'path' => $path,
|
||||||
|
'status' => 207,
|
||||||
|
'kind' => 'report',
|
||||||
|
'report_type' => $reportType,
|
||||||
|
'body_bytes' => strlen($body),
|
||||||
|
]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2145,12 +2175,14 @@ HTML
|
||||||
$obj = $this->calDavService->getObject($resource);
|
$obj = $this->calDavService->getObject($resource);
|
||||||
if ($obj === null) {
|
if ($obj === null) {
|
||||||
http_response_code(404);
|
http_response_code(404);
|
||||||
|
$this->caldavTrace('response', ['method' => $method, 'path' => $path, 'status' => 404, 'resource' => $resource, 'kind' => 'object_get']);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
header('Content-Type: text/calendar; charset=utf-8');
|
header('Content-Type: text/calendar; charset=utf-8');
|
||||||
header('ETag: ' . (string) ($obj['etag'] ?? ''));
|
header('ETag: ' . (string) ($obj['etag'] ?? ''));
|
||||||
http_response_code(200);
|
http_response_code(200);
|
||||||
echo (string) ($obj['ics'] ?? '');
|
echo (string) ($obj['ics'] ?? '');
|
||||||
|
$this->caldavTrace('response', ['method' => $method, 'path' => $path, 'status' => 200, 'resource' => $resource, 'kind' => 'object_get']);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($method === 'PUT') {
|
if ($method === 'PUT') {
|
||||||
|
|
@ -2169,6 +2201,14 @@ HTML
|
||||||
http_response_code((int) ($error['status'] ?? 500));
|
http_response_code((int) ($error['status'] ?? 500));
|
||||||
header('Content-Type: application/xml; charset=utf-8');
|
header('Content-Type: application/xml; charset=utf-8');
|
||||||
echo '<error><message>' . esc_html((string) ($error['message'] ?? 'error')) . '</message></error>';
|
echo '<error><message>' . esc_html((string) ($error['message'] ?? 'error')) . '</message></error>';
|
||||||
|
$this->caldavTrace('response', [
|
||||||
|
'method' => $method,
|
||||||
|
'path' => $path,
|
||||||
|
'status' => (int) ($error['status'] ?? 500),
|
||||||
|
'resource' => $resource,
|
||||||
|
'kind' => 'object_put',
|
||||||
|
'body_bytes' => strlen($raw),
|
||||||
|
]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$status = (int) ($result['status'] ?? 204);
|
$status = (int) ($result['status'] ?? 204);
|
||||||
|
|
@ -2177,6 +2217,14 @@ HTML
|
||||||
header('ETag: ' . (string) $event['etag']);
|
header('ETag: ' . (string) $event['etag']);
|
||||||
}
|
}
|
||||||
http_response_code($status);
|
http_response_code($status);
|
||||||
|
$this->caldavTrace('response', [
|
||||||
|
'method' => $method,
|
||||||
|
'path' => $path,
|
||||||
|
'status' => $status,
|
||||||
|
'resource' => $resource,
|
||||||
|
'kind' => 'object_put',
|
||||||
|
'body_bytes' => strlen($raw),
|
||||||
|
]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($method === 'DELETE') {
|
if ($method === 'DELETE') {
|
||||||
|
|
@ -2184,15 +2232,24 @@ HTML
|
||||||
if (isset($result['error'])) {
|
if (isset($result['error'])) {
|
||||||
$error = (array) $result['error'];
|
$error = (array) $result['error'];
|
||||||
http_response_code((int) ($error['status'] ?? 500));
|
http_response_code((int) ($error['status'] ?? 500));
|
||||||
|
$this->caldavTrace('response', [
|
||||||
|
'method' => $method,
|
||||||
|
'path' => $path,
|
||||||
|
'status' => (int) ($error['status'] ?? 500),
|
||||||
|
'resource' => $resource,
|
||||||
|
'kind' => 'object_delete',
|
||||||
|
]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
http_response_code(204);
|
http_response_code(204);
|
||||||
|
$this->caldavTrace('response', ['method' => $method, 'path' => $path, 'status' => 204, 'resource' => $resource, 'kind' => 'object_delete']);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
http_response_code(405);
|
http_response_code(405);
|
||||||
header('Allow: OPTIONS, PROPFIND, REPORT, GET, PUT, DELETE, HEAD');
|
header('Allow: OPTIONS, PROPFIND, REPORT, GET, PUT, DELETE, HEAD');
|
||||||
|
$this->caldavTrace('response', ['method' => $method, 'path' => $path, 'status' => 405, 'reason' => 'method_not_allowed']);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function caldavPropfindRootXml(string $root, string $principal, string $calendarsRoot, string $collection): string
|
private function caldavPropfindRootXml(string $root, string $principal, string $calendarsRoot, string $collection): string
|
||||||
|
|
@ -2319,6 +2376,10 @@ HTML
|
||||||
$bodyLower = strtolower($xmlBody);
|
$bodyLower = strtolower($xmlBody);
|
||||||
$resources = array_map(static fn(array $r): string => (string) ($r['resource'] ?? ''), $this->calDavService->listResources());
|
$resources = array_map(static fn(array $r): string => (string) ($r['resource'] ?? ''), $this->calDavService->listResources());
|
||||||
$items = [];
|
$items = [];
|
||||||
|
$reportType = $this->caldavReportType($xmlBody);
|
||||||
|
$includeDeleted = false;
|
||||||
|
$deletedCount = 0;
|
||||||
|
$clientSyncToken = '';
|
||||||
|
|
||||||
if (str_contains($bodyLower, 'sync-collection')) {
|
if (str_contains($bodyLower, 'sync-collection')) {
|
||||||
$clientSyncToken = $this->extractSyncCollectionToken($xmlBody);
|
$clientSyncToken = $this->extractSyncCollectionToken($xmlBody);
|
||||||
|
|
@ -2326,9 +2387,18 @@ HTML
|
||||||
if ($clientSyncToken !== '' && $clientSyncToken === $syncToken) {
|
if ($clientSyncToken !== '' && $clientSyncToken === $syncToken) {
|
||||||
$items = [];
|
$items = [];
|
||||||
} else {
|
} else {
|
||||||
// Fallback implementation: emit current objects for initial/out-of-date tokens.
|
// Emit current objects for initial/out-of-date tokens.
|
||||||
// Avoid emitting historical tombstones because many clients treat large 404 sets as transient failures.
|
|
||||||
$items = $this->calDavService->multiget($resources);
|
$items = $this->calDavService->multiget($resources);
|
||||||
|
// For incremental syncs, include a bounded set of deleted hrefs so clients can remove local copies.
|
||||||
|
if ($clientSyncToken !== '') {
|
||||||
|
$includeDeleted = true;
|
||||||
|
// Keep incremental deletes bounded to avoid overwhelming strict clients.
|
||||||
|
$deletedResources = $this->calDavService->listDeletedResources(20);
|
||||||
|
$deletedCount = count($deletedResources);
|
||||||
|
foreach ($deletedResources as $deletedResource) {
|
||||||
|
$items[] = ['resource' => $deletedResource, 'status' => 404];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} elseif (str_contains($bodyLower, 'calendar-query')) {
|
} elseif (str_contains($bodyLower, 'calendar-query')) {
|
||||||
$items = $this->calDavService->multiget($resources);
|
$items = $this->calDavService->multiget($resources);
|
||||||
|
|
@ -2359,17 +2429,38 @@ HTML
|
||||||
}
|
}
|
||||||
|
|
||||||
$responses = '';
|
$responses = '';
|
||||||
|
$okCount = 0;
|
||||||
|
$notFoundCount = 0;
|
||||||
foreach ($items as $item) {
|
foreach ($items as $item) {
|
||||||
$status = (int) ($item['status'] ?? 404);
|
$status = (int) ($item['status'] ?? 404);
|
||||||
$resource = (string) ($item['resource'] ?? '');
|
$resource = (string) ($item['resource'] ?? '');
|
||||||
$responses .= '<D:response><D:href>' . htmlspecialchars($collection . $resource, ENT_XML1 | ENT_QUOTES, 'UTF-8') . '</D:href><D:propstat><D:prop>';
|
$responses .= '<D:response><D:href>' . htmlspecialchars($collection . $resource, ENT_XML1 | ENT_QUOTES, 'UTF-8') . '</D:href>';
|
||||||
if ($status === 200) {
|
if ($status === 200) {
|
||||||
|
$okCount++;
|
||||||
|
$responses .= '<D:propstat><D:prop>';
|
||||||
$responses .= '<D:getetag>' . htmlspecialchars((string) ($item['etag'] ?? ''), ENT_XML1 | ENT_QUOTES, 'UTF-8') . '</D:getetag>'
|
$responses .= '<D:getetag>' . htmlspecialchars((string) ($item['etag'] ?? ''), ENT_XML1 | ENT_QUOTES, 'UTF-8') . '</D:getetag>'
|
||||||
. '<C:calendar-data xmlns:C="urn:ietf:params:xml:ns:caldav">' . htmlspecialchars((string) ($item['ics'] ?? ''), ENT_XML1 | ENT_QUOTES, 'UTF-8') . '</C:calendar-data>';
|
. '<C:calendar-data xmlns:C="urn:ietf:params:xml:ns:caldav">' . htmlspecialchars((string) ($item['ics'] ?? ''), ENT_XML1 | ENT_QUOTES, 'UTF-8') . '</C:calendar-data>';
|
||||||
|
$responses .= '</D:prop><D:status>HTTP/1.1 200 OK</D:status></D:propstat>';
|
||||||
|
} else {
|
||||||
|
$notFoundCount++;
|
||||||
|
// For sync-collection deletions, clients expect bare status (no propstat block).
|
||||||
|
if ($reportType === 'sync-collection') {
|
||||||
|
$responses .= '<D:status>HTTP/1.1 404 Not Found</D:status>';
|
||||||
|
} else {
|
||||||
|
$responses .= '<D:propstat><D:prop></D:prop><D:status>HTTP/1.1 404 Not Found</D:status></D:propstat>';
|
||||||
}
|
}
|
||||||
$responses .= '</D:prop><D:status>HTTP/1.1 ' . $status . ($status === 200 ? ' OK' : ' Not Found')
|
|
||||||
. '</D:status></D:propstat></D:response>';
|
|
||||||
}
|
}
|
||||||
|
$responses .= '</D:response>';
|
||||||
|
}
|
||||||
|
$this->caldavTrace('report', [
|
||||||
|
'report_type' => $reportType,
|
||||||
|
'client_sync_token_present' => $clientSyncToken !== '',
|
||||||
|
'client_sync_token_matches' => $clientSyncToken !== '' && $clientSyncToken === $syncToken,
|
||||||
|
'include_deleted' => $includeDeleted,
|
||||||
|
'deleted_included_count' => $deletedCount,
|
||||||
|
'response_ok_count' => $okCount,
|
||||||
|
'response_not_found_count' => $notFoundCount,
|
||||||
|
]);
|
||||||
return '<?xml version="1.0" encoding="utf-8"?><D:multistatus xmlns:D="DAV:"><D:sync-token>'
|
return '<?xml version="1.0" encoding="utf-8"?><D:multistatus xmlns:D="DAV:"><D:sync-token>'
|
||||||
. htmlspecialchars($syncToken, ENT_XML1 | ENT_QUOTES, 'UTF-8')
|
. htmlspecialchars($syncToken, ENT_XML1 | ENT_QUOTES, 'UTF-8')
|
||||||
. '</D:sync-token>'
|
. '</D:sync-token>'
|
||||||
|
|
@ -2425,6 +2516,34 @@ HTML
|
||||||
return $token;
|
return $token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function caldavReportType(string $xmlBody): string
|
||||||
|
{
|
||||||
|
$bodyLower = strtolower($xmlBody);
|
||||||
|
if (str_contains($bodyLower, 'sync-collection')) {
|
||||||
|
return 'sync-collection';
|
||||||
|
}
|
||||||
|
if (str_contains($bodyLower, 'calendar-query')) {
|
||||||
|
return 'calendar-query';
|
||||||
|
}
|
||||||
|
if (str_contains($bodyLower, 'calendar-multiget')) {
|
||||||
|
return 'calendar-multiget';
|
||||||
|
}
|
||||||
|
return 'other';
|
||||||
|
}
|
||||||
|
|
||||||
|
private function caldavTrace(string $event, array $context = []): void
|
||||||
|
{
|
||||||
|
if (!$this->isDiagnosticsEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$payload = [
|
||||||
|
'event' => $event,
|
||||||
|
'at' => gmdate('c'),
|
||||||
|
'context' => $context,
|
||||||
|
];
|
||||||
|
error_log('[calendar-plugin][caldav] ' . wp_json_encode($payload));
|
||||||
|
}
|
||||||
|
|
||||||
private function unwrapServiceResult(array $result): array|\WP_Error
|
private function unwrapServiceResult(array $result): array|\WP_Error
|
||||||
{
|
{
|
||||||
if (isset($result['error']) && is_array($result['error'])) {
|
if (isset($result['error']) && is_array($result['error'])) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue