Calendar V3

This commit is contained in:
2026-03-11 13:15:20 +01:00
parent a4815fc672
commit 94215fdd65
13 changed files with 190 additions and 61 deletions

View File

@@ -20,6 +20,9 @@ class CalDavClient
/** @var int HTTP timeout in seconds */
protected const TIMEOUT = 30;
/** @var string Last request error message for diagnostics */
protected static string $lastRequestError = '';
/**
* Update the STATUS of a specific event or task on the remote CalDAV server.
*
@@ -33,7 +36,7 @@ class CalDavClient
* @param string $recurrenceId Recurrence ID (empty for non-recurring)
* @param string $newStatus New status value (e.g. COMPLETED, TODO)
* @param string $dateIso Occurrence date YYYY-MM-DD
* @return bool True if the remote update succeeded
* @return string Empty string on success, error message on failure
*/
public static function updateEventStatus(
string $caldavUrl,
@@ -43,13 +46,17 @@ class CalDavClient
string $recurrenceId,
string $newStatus,
string $dateIso
): bool {
if ($caldavUrl === '' || $uid === '') return false;
): string {
if ($caldavUrl === '' || $uid === '') return 'Missing CalDAV URL or UID';
try {
// Find the calendar object href for this UID via REPORT
$objectInfo = self::findObjectByUid($caldavUrl, $username, $password, $uid);
if ($objectInfo === null) return false;
if ($objectInfo === null) {
$msg = "CalDAV: Could not find object with UID '$uid' on server";
dbglog($msg);
return $msg;
}
$objectHref = $objectInfo['href'];
$etag = $objectInfo['etag'];
@@ -57,19 +64,35 @@ class CalDavClient
// Parse and update the status
$calendar = Reader::read($calendarData, Reader::OPTION_FORGIVING);
if (!($calendar instanceof VCalendar)) return false;
if (!($calendar instanceof VCalendar)) {
$msg = "CalDAV: Failed to parse calendar data for UID '$uid'";
dbglog($msg);
return $msg;
}
$updated = IcsWriter::applyStatusUpdateToCalendar(
$calendar, $uid, $recurrenceId, $newStatus, $dateIso
);
if (!$updated) return false;
if (!$updated) {
$msg = "CalDAV: applyStatusUpdateToCalendar failed for UID '$uid'";
dbglog($msg);
return $msg;
}
$newData = $calendar->serialize();
// PUT the updated object back with If-Match for conflict detection
return self::putCalendarObject($objectHref, $username, $password, $newData, $etag);
$putError = self::putCalendarObject($objectHref, $username, $password, $newData, $etag);
if ($putError !== '') {
dbglog($putError);
return $putError;
}
return '';
} catch (Throwable $e) {
return false;
$msg = 'CalDAV: Exception during updateEventStatus: ' . $e->getMessage();
dbglog($msg);
return $msg;
}
}
@@ -243,7 +266,7 @@ class CalDavClient
* @param string $password
* @param string $data ICS data to write
* @param string $etag ETag for If-Match header (empty to skip)
* @return bool
* @return string Empty string on success, error message on failure
*/
protected static function putCalendarObject(
string $href,
@@ -251,7 +274,7 @@ class CalDavClient
string $password,
string $data,
string $etag
): bool {
): string {
$headers = [
'Content-Type: text/calendar; charset=utf-8',
];
@@ -260,9 +283,10 @@ class CalDavClient
}
$response = self::request('PUT', $href, $username, $password, $data, $headers);
// PUT returns null body but we check by HTTP status via the request method
// A successful PUT returns 2xx
return $response !== null;
if ($response === null) {
return self::$lastRequestError ?: 'CalDAV PUT failed (unknown error)';
}
return '';
}
/**
@@ -293,8 +317,6 @@ class CalDavClient
$etags = $resp->xpath('.//d:getetag');
$etag = ($etags && count($etags) > 0) ? trim((string)$etags[0]) : '';
// Strip surrounding quotes from etag if present
$etag = trim($etag, '"');
$caldata = $resp->xpath('.//cal:calendar-data');
$data = ($caldata && count($caldata) > 0) ? trim((string)$caldata[0]) : '';
@@ -392,10 +414,18 @@ class CalDavClient
string $body = '',
array $headers = []
): ?string {
if (!function_exists('curl_init')) return null;
self::$lastRequestError = '';
if (!function_exists('curl_init')) {
self::$lastRequestError = 'CalDAV: curl extension not available';
return null;
}
$ch = curl_init();
if ($ch === false) return null;
if ($ch === false) {
self::$lastRequestError = 'CalDAV: curl_init() failed';
return null;
}
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
@@ -423,9 +453,13 @@ class CalDavClient
// Capture HTTP status code
$responseBody = curl_exec($ch);
$httpCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
curl_close($ch);
if (!is_string($responseBody)) return null;
if (!is_string($responseBody)) {
self::$lastRequestError = "CalDAV $method failed: curl error: $curlError";
return null;
}
// Accept 2xx and 207 (multistatus) responses
if ($httpCode >= 200 && $httpCode < 300) {
@@ -435,6 +469,7 @@ class CalDavClient
return $responseBody;
}
self::$lastRequestError = "CalDAV $method failed: HTTP $httpCode";
return null;
}
}